diff --git a/.circleci/config.yml b/.circleci/config.yml index 6d154602cf7..d72cb595142 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 @@ -1546,7 +1546,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 +1585,7 @@ jobs: - when: condition: and: - - equal: [main, << pipeline.git.branch >>] + - equal: [integrationTesting, << pipeline.git.branch >>] - when: always steps: - run: @@ -1602,7 +1602,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 +1664,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 +1686,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 +1724,7 @@ jobs: - when: condition: and: - - equal: [main, << pipeline.git.branch >>] + - equal: [integrationTesting, << pipeline.git.branch >>] steps: - run: name: 'Copy coverage to baseline' @@ -1735,7 +1739,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 +1756,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 @@ -2777,4 +2780,4 @@ experimental: notify: branches: only: - - main + - main \ No newline at end of file diff --git a/.envrc b/.envrc index c5e72d09ad2..6e2aed70eb9 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 @@ -444,4 +444,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 6ff130ec1cc..94691fb7d38 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -28,16 +28,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 RUNNER_TAG: &runner_tag milmove DOCKER_RUNNER_TAG: &docker_runner_tag eks_cluster_runner diff --git a/Dockerfile b/Dockerfile index 18f55ac4c63..c76b5ad3ec0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,6 +17,7 @@ COPY bin/milmove /bin/milmove COPY config/tls/milmove-cert-bundle.p7b /config/tls/milmove-cert-bundle.p7b COPY config/tls/dod-sw-ca-75.pem /config/tls/dod-sw-ca-75.pem +COPY config/tls/dod-sw-ca-66.pem /config/tls/dod-sw-ca-66.pem COPY swagger/* /swagger/ COPY build /build diff --git a/Dockerfile.dp3 b/Dockerfile.dp3 index bd32b33446e..0ba3b50f1c3 100644 --- a/Dockerfile.dp3 +++ b/Dockerfile.dp3 @@ -20,6 +20,11 @@ COPY config/tls/api.loadtest.dp3.us.crt /config/tls/api.loadtest.dp3.us.crt COPY config/tls/api.exp.dp3.us.chain.der.p7b /config/tls/api.exp.dp3.us.chain.der.p7b COPY config/tls/api.exp.dp3.us.crt /config/tls/api.exp.dp3.us.crt +#copy dod certs +COPY config/tls/milmove-cert-bundle.p7b /config/tls/milmove-cert-bundle.p7b +COPY config/tls/dod-sw-ca-75.pem /config/tls/dod-sw-ca-75.pem +COPY config/tls/dod-sw-ca-66.pem /config/tls/dod-sw-ca-66.pem + COPY swagger/* /swagger/ COPY build /build COPY public/static/react-file-viewer /public/static/react-file-viewer diff --git a/Dockerfile.local b/Dockerfile.local index bcbcd46c6bc..728a6a6f4c0 100644 --- a/Dockerfile.local +++ b/Dockerfile.local @@ -27,7 +27,8 @@ COPY --from=builder --chown=root:root /home/circleci/project/bin/rds-ca-2019-roo COPY --from=builder --chown=root:root /home/circleci/project/bin/milmove /bin/milmove COPY config/tls/milmove-cert-bundle.p7b /config/tls/milmove-cert-bundle.p7b -COPY config/tls/dod-sw-ca-66.pem /config/tls/dod-sw-ca-75.pem +COPY config/tls/dod-sw-ca-75.pem /config/tls/dod-sw-ca-75.pem +COPY config/tls/dod-sw-ca-66.pem /config/tls/dod-sw-ca-66.pem # While it's ok to have these certs copied locally, they should never be copied into Dockerfile. COPY config/tls/devlocal-ca.key /config/tls/devlocal-ca.key 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-20250130134615-83d2d75ad4cd diff --git a/go.sum b/go.sum index 8dbbb90bbe0..5078a8d5622 100644 --- a/go.sum +++ b/go.sum @@ -511,8 +511,6 @@ github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+ github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/patrickmn/go-cache v0.0.0-20180815053127-5633e0862627 h1:pSCLCl6joCFRnjpeojzOpEYs4q7Vditq8fySFG5ap3Y= github.com/patrickmn/go-cache v0.0.0-20180815053127-5633e0862627/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= -github.com/pdfcpu/pdfcpu v0.9.2-0.20250122004437-b05d39596f8f h1:pbR5oPqr7t9jhmy/804XHl/JjsxJuNHumdWA+lFkU4E= -github.com/pdfcpu/pdfcpu v0.9.2-0.20250122004437-b05d39596f8f/go.mod h1:8EAma3IBIS7ssMiPlcNIPWwISTuP31WToXfGvc327vI= github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/peterbourgon/diskv/v3 v3.0.1 h1:x06SQA46+PKIUftmEujdwSEpIx8kR+M9eLYsUxeYveU= @@ -631,6 +629,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-20250130134615-83d2d75ad4cd h1:S7LmrtJ4oAMZxseXCDrB/Iu6qLx+JwPvEJO1AY+sThE= +github.com/transcom/pdfcpu v0.0.0-20250130134615-83d2d75ad4cd/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 +723,8 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/crypto v0.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 +831,8 @@ golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.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 +849,8 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= -golang.org/x/term v0.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 0f22ee918ce..ec8404f9561 100644 --- a/migrations/app/migrations_manifest.txt +++ b/migrations/app/migrations_manifest.txt @@ -1059,6 +1059,7 @@ 20241216190428_update_get_zip_code_function_and_update_pricing_proc.up.sql 20241217163231_update_duty_locations_bad_zips.up.sql 20241217180136_add_AK_zips_to_zip3_distances.up.sql +20241217191012_update_move_to_gbloc_for_ak.up.sql 20241218201833_add_PPPO_BASE_ELIZABETH.up.sql 20241218204620_add_international_nts_service_items.up.sql 20241220171035_add_additional_AK_zips_to_zip3_distances.up.sql @@ -1070,11 +1071,18 @@ 20241230150644_student_travel_weight_limit_param.up.sql 20241230190638_remove_AK_zips_from_zip3.up.sql 20241230190647_add_missing_AK_zips_to_zip3_distances.up.sql +20241231155337_add_payment_params_for_international_shuttle.up.sql +20250103130619_revert_data_change_for_gbloc_for_ak.up.sql +20250103142533_update_postal_codes_and_gblocs_for_ak.up.sql 20250103180420_update_pricing_proc_to_use_local_price_variable.up.sql +20250109194140_create_audit_history_table_for_payment_service_items.up.sql 20250110001339_update_nts_release_enum_name.up.sql 20250110153428_add_shipment_address_updates_to_move_history.up.sql -20250110214012_homesafeconnect_cert.up.sql 20250113152050_rename_ubp.up.sql +20250113160816_updating_create_accessorial_service_item_proc.up.sql 20250113201232_update_estimated_pricing_procs_add_is_peak_func.up.sql +20250114164752_add_ppm_estimated_incentive_proc.up.sql 20250116200912_disable_homesafe_stg_cert.up.sql 20250120144247_update_pricing_proc_to_use_110_percent_weight.up.sql +20250121153007_update_pricing_proc_to_handle_international_shuttle.up.sql +20250123210535_update_re_intl_transit_times_for_ak_hhg.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/20241217191012_update_move_to_gbloc_for_ak.up.sql b/migrations/app/schema/20241217191012_update_move_to_gbloc_for_ak.up.sql new file mode 100644 index 00000000000..282ad1b0b16 --- /dev/null +++ b/migrations/app/schema/20241217191012_update_move_to_gbloc_for_ak.up.sql @@ -0,0 +1,43 @@ +delete from postal_code_to_gblocs where postal_code in ( +select uspr_zip_id from v_locations where state = 'AK'); + +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' + when s.affiliation = 'NAVY' THEN 'NAVY_AND_MARINES' + when s.affiliation = 'MARINES' THEN 'NAVY_AND_MARINES' + 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; \ No newline at end of file diff --git a/migrations/app/schema/20241231155337_add_payment_params_for_international_shuttle.up.sql b/migrations/app/schema/20241231155337_add_payment_params_for_international_shuttle.up.sql new file mode 100644 index 00000000000..e14357ac6a4 --- /dev/null +++ b/migrations/app/schema/20241231155337_add_payment_params_for_international_shuttle.up.sql @@ -0,0 +1,15 @@ +INSERT INTO service_params +(id,service_id,service_item_param_key_id,created_at,updated_at) +VALUES +('379c6d36-56ed-4469-8d17-cc5060b02fa3', (SELECT id FROM re_services WHERE code='IOSHUT'), (SELECT id FROM service_item_param_keys WHERE key='ContractCode'), now(), now()), +('1925f884-e66a-4c5b-91b5-f953072dadfc', (SELECT id FROM re_services WHERE code='IOSHUT'), (SELECT id FROM service_item_param_keys WHERE key='ContractYearName'), now(), now()), +('590786bd-c608-429b-9382-bcb12e165512', (SELECT id FROM re_services WHERE code='IOSHUT'), (SELECT id FROM service_item_param_keys WHERE key='EscalationCompounded'), now(), now()), +('8c2d6c08-2521-40d5-bb8b-4998d6c43ceb', (SELECT id FROM re_services WHERE code='IOSHUT'), (SELECT id FROM service_item_param_keys WHERE key='PriceRateOrFactor'), now(), now()); + +INSERT INTO service_params +(id,service_id,service_item_param_key_id,created_at,updated_at) +VALUES +('b2588961-21af-416d-bb89-fcff62230991', (SELECT id FROM re_services WHERE code='IDSHUT'), (SELECT id FROM service_item_param_keys WHERE key='ContractCode'), now(), now()), +('1ee015d0-ae1a-4f0f-b228-de2537816a4b', (SELECT id FROM re_services WHERE code='IDSHUT'), (SELECT id FROM service_item_param_keys WHERE key='ContractYearName'), now(), now()), +('4eab020b-7df0-42db-b285-2ad2fc0c213c', (SELECT id FROM re_services WHERE code='IDSHUT'), (SELECT id FROM service_item_param_keys WHERE key='EscalationCompounded'), now(), now()), +('4bb8cc94-b2e2-417e-a512-a361bcadd9ba', (SELECT id FROM re_services WHERE code='IDSHUT'), (SELECT id FROM service_item_param_keys WHERE key='PriceRateOrFactor'), now(), now()); diff --git a/migrations/app/schema/20250103130619_revert_data_change_for_gbloc_for_ak.up.sql b/migrations/app/schema/20250103130619_revert_data_change_for_gbloc_for_ak.up.sql new file mode 100644 index 00000000000..2ee43e98f43 --- /dev/null +++ b/migrations/app/schema/20250103130619_revert_data_change_for_gbloc_for_ak.up.sql @@ -0,0 +1,840 @@ +drop view move_to_gbloc; +CREATE OR REPLACE VIEW move_to_gbloc AS +SELECT DISTINCT ON (sh.move_id) sh.move_id AS move_id, COALESCE(pctg.gbloc, pctg_ppm.gbloc) AS gbloc +FROM mto_shipments sh + -- try the pickup_address path + LEFT JOIN + ( + SELECT a.id address_id, pctg.gbloc + FROM addresses a + JOIN postal_code_to_gblocs pctg ON a.postal_code = pctg.postal_code + ) pctg ON pctg.address_id = sh.pickup_address_id + -- try the ppm_shipments path + LEFT JOIN + ( + SELECT ppm.shipment_id, pctg.gbloc + FROM ppm_shipments ppm + JOIN addresses ppm_address ON ppm.pickup_postal_address_id = ppm_address.id + JOIN postal_code_to_gblocs pctg ON ppm_address.postal_code = pctg.postal_code + ) pctg_ppm ON pctg_ppm.shipment_id = sh.id +WHERE sh.deleted_at IS NULL +ORDER BY sh.move_id, sh.created_at; + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99501', 'MBFL', now(), now(), 'd8697416-e345-46a8-9767-47b7abbb3c06'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99502', 'MBFL', now(), now(), '0e060122-7cea-4bcd-b636-31c453088a5d'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99503', 'MBFL', now(), now(), '1bc710db-3f4f-4177-9656-a99956a8b06e'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99504', 'MBFL', now(), now(), 'e35c4fd3-b8d7-46f4-a559-ddfc8e0ad4e9'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99505', 'MBFL', now(), now(), 'f2e40ed3-bc7b-428c-9693-3bc1cc1a9c57'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99506', 'MBFL', now(), now(), 'cf3890e6-16df-46a7-aabb-57010b999ee7'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99507', 'MBFL', now(), now(), 'c869c7aa-e0fd-4933-a4e9-09bd6191b25a'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99508', 'MBFL', now(), now(), 'ca8554c3-d21c-4e26-a77f-6e965c7d31c5'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99509', 'MBFL', now(), now(), '54ea9592-d93d-4102-84ce-7a9c57b6aff8'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99510', 'MBFL', now(), now(), 'f8d0e922-ab5d-4871-bd0c-1f7484b13981'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99511', 'MBFL', now(), now(), '7098c10e-edbf-4cf6-82dd-c09dd7a8f226'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99513', 'MBFL', now(), now(), '8f2d3b79-718e-4d96-b5d2-6b0c40487332'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99514', 'MBFL', now(), now(), '8ff6d888-935c-488c-9b62-33b8f4053b4a'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99515', 'MBFL', now(), now(), 'e3927bca-6677-49d7-a622-ee20f386c28c'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99516', 'MBFL', now(), now(), 'fc5ee3c2-d0b6-4e56-9bcd-e802e14dca7e'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99517', 'MBFL', now(), now(), '84c51c78-972a-4714-8218-8169ee541159'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99518', 'MBFL', now(), now(), '7b3e4eed-3584-4036-9da5-9bc6a7163e4e'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99519', 'MBFL', now(), now(), '62b97b86-e8e7-410a-a267-d101ce5f9a3c'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99520', 'MBFL', now(), now(), 'bce10502-90fb-4f28-8489-f5e6d2ac00ab'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99521', 'MBFL', now(), now(), 'b8c51570-d048-41f8-a1fc-58e6b41d367a'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99522', 'MBFL', now(), now(), '6311d142-f02b-4f49-b719-608d78c91489'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99523', 'MBFL', now(), now(), '967028d9-3a7b-4949-b1a4-e94a1aa25a73'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99524', 'MBFL', now(), now(), 'afd3cc8b-2c02-4e63-8fd1-eb81a1a19b5d'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99529', 'MBFL', now(), now(), 'f08e218c-e55e-43fc-bbdf-bc57aaa65726'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99530', 'MBFL', now(), now(), '65eeaf36-e786-43dd-a18c-fb470a4468f9'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99540', 'MBFL', now(), now(), '2380496a-9087-48bc-a0e2-9cd5ac19f470'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99545', 'MBFL', now(), now(), '6df5025e-e730-427a-8c10-ff6d131d0567'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99546', 'MBFL', now(), now(), '02163620-1da6-4509-b8bb-d8b3336e5c2a'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99547', 'MBFL', now(), now(), 'f869b729-88cd-4770-a435-2dfd4b50c330'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99548', 'MBFL', now(), now(), 'db80b48b-25bd-4ef1-8d5a-33d740af6b0e'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99549', 'MBFL', now(), now(), '09983e53-ae83-41b1-b459-e0ab411ea87e'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99550', 'MAPS', now(), now(), '643db073-5e92-43c0-9b42-a8d9cc800623'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99551', 'MBFL', now(), now(), '9bc29931-880b-42df-a758-017d5abee32d'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99552', 'MBFL', now(), now(), 'f6fa0bb7-fd5c-494e-b62c-b7bc509b632f'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99553', 'MBFL', now(), now(), 'abcb2cbf-389d-4830-ae16-037c55b7bc2c'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99554', 'MBFL', now(), now(), '2d4d9111-f59b-4506-935d-b6601275311a'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99555', 'MBFL', now(), now(), '00c2d5ed-2364-490d-86b9-40cbad4679fb'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99556', 'MBFL', now(), now(), 'b5c75c77-59cf-4089-815b-c0d15d9440f0'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99557', 'MBFL', now(), now(), '999e2c9e-04ed-47da-aca1-2b320ed52666'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99558', 'MBFL', now(), now(), 'cdc2383c-9ead-4501-a6cc-b736ea72ecef'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99559', 'MBFL', now(), now(), '2ca0657c-c121-41e4-a741-b563dee6838e'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99561', 'MBFL', now(), now(), '0c8df634-b6e6-457f-93d7-7d64909bc7cb'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99563', 'MBFL', now(), now(), 'e9a0ebee-3ae3-4d66-b18b-cc3ea417887d'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99564', 'MBFL', now(), now(), '77dd08d9-0997-4d16-babd-1ffccc4222d4'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99565', 'MBFL', now(), now(), '59b6a797-b2e8-4bd7-affc-a5639308a9b3'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99566', 'MBFL', now(), now(), 'cf5b630e-964a-4389-9fa2-e1ed0e6a3041'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99567', 'MBFL', now(), now(), 'f71a916b-e55f-4230-869e-038c319037ce'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99568', 'MBFL', now(), now(), '9719d0c5-1bd0-42fe-89ab-f4dbddcfa588'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99569', 'MBFL', now(), now(), 'fba4bdcf-7707-4826-bf2a-3e7aaf81f309'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99571', 'MBFL', now(), now(), '75e598ff-6d6c-4a80-80bc-de36bc37a3be'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99572', 'MBFL', now(), now(), 'f7b9b7d5-6730-4e8f-9364-e5b1eb1d2c2b'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99573', 'MBFL', now(), now(), '74dcb198-d7c8-4dbc-81ab-33a16559e100'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99574', 'MAPK', now(), now(), '9b9d03a0-e069-48cd-ae65-a66dbd8a3214'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99575', 'MBFL', now(), now(), 'f6ffb9e8-8976-4288-88cd-d10420d1894e'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99576', 'MBFL', now(), now(), '0883cf7f-c2f9-4865-91c3-34df59486f01'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99577', 'MBFL', now(), now(), 'a5e58cce-5375-498b-bfdb-d2e4b50ee2b7'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99578', 'MBFL', now(), now(), '63113a71-6f32-4531-991b-015d51be0ef7'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99579', 'MBFL', now(), now(), 'be2f7dfb-a020-45b5-afa6-0e3b6fd669bf'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99580', 'MBFL', now(), now(), '29b1f268-8ad5-400c-8aa6-0b4b851f8588'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99581', 'MBFL', now(), now(), '5e1dee94-93f9-4b7d-8445-7de967031bfa'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99583', 'MBFL', now(), now(), '2ab06e55-ae2b-4c19-b679-81ee89d098a5'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99585', 'MBFL', now(), now(), 'd177b210-8362-4073-9862-43333203abfc'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99586', 'MBFL', now(), now(), 'c8bdc299-d65d-4b4b-a0ad-0ef6c8ca905d'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99587', 'MBFL', now(), now(), '9543c328-16d2-484b-9e62-1965a63f1d2e'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99588', 'MBFL', now(), now(), '08d82a7f-8417-407d-b352-9e0f3ef9e9cd'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99589', 'MBFL', now(), now(), 'e09e4805-d0a9-4347-8f63-7e221c4b6d7e'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99590', 'MBFL', now(), now(), '3022675c-9e50-403e-987c-7e79762274c2'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99591', 'MBFL', now(), now(), '57eebbd9-4b15-4b6f-923a-1d1db65d72bc'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99599', 'MBFL', now(), now(), '78a46448-b22a-4da0-a42a-07af993848bc'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99602', 'MBFL', now(), now(), '101c94fc-11cf-4317-a63b-c3c903d1f9b3'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99603', 'MBFL', now(), now(), '67ebaf88-8131-4215-b6a4-3b3ecb3862f0'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99604', 'MBFL', now(), now(), '45e5dfdd-9dbc-439a-96ac-292b820aa292'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99605', 'MBFL', now(), now(), 'd403b760-8ad8-409b-b8d4-e0ad9dfd3947'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99606', 'MBFL', now(), now(), 'ac7a083e-3300-4881-874b-6bbd206a4e92'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99607', 'MBFL', now(), now(), 'f8d072a2-099d-4767-9b1d-b5dd48a79a19'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99608', 'MAPS', now(), now(), '889e4f60-6352-443c-8705-734ed91dc7a4'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99609', 'MBFL', now(), now(), 'da00dfc3-a372-4e9f-8ddb-f0d9c792ab36'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99610', 'MBFL', now(), now(), '53b8b7d8-337a-43fe-9a97-e2f5083210aa'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99611', 'MBFL', now(), now(), 'f7211d4f-8ffb-482f-b268-07b9d79c467e'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99612', 'MBFL', now(), now(), 'e7340a1b-3278-42aa-a067-021863d676dd'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99613', 'MBFL', now(), now(), '7de673ca-c91d-4bc1-b450-fdaa0c199723'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99614', 'MBFL', now(), now(), 'fd72416c-c88b-4c0f-a3e0-28fd924fd7d7'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99615', 'MAPS', now(), now(), 'b0597a99-16d7-4e1e-9820-042971b2551f'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99619', 'MAPS', now(), now(), '8d05d190-fda5-4930-838d-76f37892417c'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99620', 'MBFL', now(), now(), 'fc2456f7-d2ca-4cb3-b16b-f50388c09a12'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99621', 'MBFL', now(), now(), 'da74789a-b710-4332-a973-cc8ff104f80f'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99622', 'MBFL', now(), now(), '52b0c551-7d40-496b-9e5a-3aeb7e68f415'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99623', 'MBFL', now(), now(), '4613c99b-da96-4649-8de5-8e445af78f99'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99624', 'MAPS', now(), now(), '13e0d2ba-852e-4970-b60c-464740975141'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99625', 'MBFL', now(), now(), '772070b7-2029-40cf-82b7-e8b839f2740f'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99626', 'MBFL', now(), now(), 'a6a4b405-e1af-455f-89d5-088de0afebfa'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99627', 'MBFL', now(), now(), 'e0778102-5fb8-4366-bbd2-413a96946a6d'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99628', 'MBFL', now(), now(), '4dad6942-608b-415b-86b1-4575cbe92b14'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99629', 'MBFL', now(), now(), '534a58f3-e96b-41cd-86f5-d3c3f7553063'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99630', 'MBFL', now(), now(), 'aca18cb8-d348-4dfa-bfce-3508ed050ce2'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99631', 'MBFL', now(), now(), '90fd8694-e43b-49f7-a22b-27f95170069c'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99632', 'MBFL', now(), now(), 'b6736f3e-b561-4658-a22a-822b4d141db6'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99633', 'MBFL', now(), now(), 'e622bb3a-8c83-4230-b953-a256a7ed2b50'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99634', 'MBFL', now(), now(), 'a042efbf-24da-489f-ab6d-15179847cec1'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99635', 'MBFL', now(), now(), '153a77b9-ae9f-4e3c-8b7e-35631300491d'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99636', 'MBFL', now(), now(), '988b8373-ca1d-459f-a0cc-598c20ad1e07'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99637', 'MBFL', now(), now(), 'eb47a507-5f66-4344-bed9-d165d8275fa9'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99638', 'MBFL', now(), now(), '0858bd59-a16b-47ca-9cb8-670411737d4d'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99639', 'MBFL', now(), now(), '52b0ac8b-3931-47d1-aa66-2283a9a1650a'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99640', 'MBFL', now(), now(), '9040a7e0-de08-494b-8d69-27cd4a53bc90'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99641', 'MBFL', now(), now(), '6e1d8641-0448-40cc-88e3-67d4f8466a8e'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99643', 'MAPS', now(), now(), '15c1aea9-9b9b-4f6e-93cf-a114ac9175f8'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99644', 'MAPS', now(), now(), '6f294618-1e61-42fc-8d6b-cd870b0b08fc'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99645', 'MBFL', now(), now(), '82d2557f-263b-4108-a75b-ff745aea08f0'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99647', 'MBFL', now(), now(), 'c432e858-d177-4faa-8d0b-97c5305b61e7'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99648', 'MBFL', now(), now(), '273ecc7f-0b37-439e-97e6-86a2f7ea7114'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99649', 'MBFL', now(), now(), '213d21e6-0b04-4f5c-90f8-4ddc9938381d'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99650', 'MBFL', now(), now(), 'f2a5f071-205f-4cc1-9448-44058d9af3f5'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99651', 'MBFL', now(), now(), 'c16c38b5-eb50-4dc7-8a40-aa9e7e80bf2f'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99652', 'MBFL', now(), now(), 'ef17b53b-f840-4b2d-9f66-3f80c413b3c1'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99653', 'MBFL', now(), now(), '7307956d-84f3-4c22-ad2f-40b62dc0a0d7'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99654', 'MBFL', now(), now(), '47ccb8d8-8df2-4293-9f86-f5703aea0dba'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99655', 'MBFL', now(), now(), '69d0d1e7-8c8e-4cde-82dc-b8374b8b4861'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99656', 'MBFL', now(), now(), 'e0be218f-2d2f-4bac-b8b5-e09ef3f28742'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99657', 'MBFL', now(), now(), 'bd121ed0-6592-427a-9ba9-c06ab3a703dd'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99658', 'MBFL', now(), now(), '6acfa9fe-137e-4a92-b21c-39e627cc378a'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99659', 'MBFL', now(), now(), 'f131070d-b5d8-453e-bac7-ad436ea124dc'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99660', 'MBFL', now(), now(), 'cab88f64-c8c1-42f1-be62-7b4cce31aeb3'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99661', 'MBFL', now(), now(), 'e5a62394-0278-42a3-84e3-57ff2240dc60'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99662', 'MBFL', now(), now(), '8c0d1e5d-6afe-4552-b7cc-3b5a6fbdc3db'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99663', 'MBFL', now(), now(), '44beec24-6856-43f7-ab55-b48286d2829b'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99664', 'MBFL', now(), now(), 'c35ed3e5-c3a6-43e2-9794-56709f93620f'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99665', 'MBFL', now(), now(), '06b6f8ed-7f4e-4b7a-94b6-def7b619a03d'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99666', 'MBFL', now(), now(), '959c959f-b829-471c-a63e-b3cffa15f6c7'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99667', 'MBFL', now(), now(), '75ef0a3a-cc89-4a3b-809f-6cdb6753bc25'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99668', 'MBFL', now(), now(), 'c2f65e88-5c6a-4e46-95c6-89e79385c163'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99669', 'MBFL', now(), now(), '6c1d51b0-9213-404a-b7d8-c4fc384bd304'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99670', 'MBFL', now(), now(), '523b6896-d660-42c7-a8f9-e4603e75ccc9'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99671', 'MBFL', now(), now(), '2c1d481b-6ef3-49ab-8ae9-a9e6429575fd'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99672', 'MBFL', now(), now(), '702c69d2-5231-4267-8d8e-85d5817077d7'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99674', 'MBFL', now(), now(), '66b7f4fb-9098-40a8-a018-a5217ed9878d'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99675', 'MBFL', now(), now(), '2ab46ff9-9120-48af-ac05-73663dfd1571'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99676', 'MBFL', now(), now(), '488978c8-3c52-4a1a-9966-c04bba23ee37'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99677', 'MBFL', now(), now(), '30ceccbf-25b6-4862-b358-78f12e22ba06'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99678', 'MBFL', now(), now(), 'a4ed1190-85d9-4b20-8390-9a1b9a3bc0c4'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99679', 'MBFL', now(), now(), '1c87ddc2-ad88-4607-b826-f37da5fc8762'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99680', 'MBFL', now(), now(), '3b5dca39-d435-413c-9edf-3bb3a2b5005d'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99681', 'MBFL', now(), now(), '93acc862-3107-4f22-b270-e0c7d1f5f80b'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99682', 'MBFL', now(), now(), 'd5224770-83e3-4dc8-97af-24c147669dcc'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99683', 'MBFL', now(), now(), 'b9dcbc66-53d9-4f14-b51d-02171a3fdf5f'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99684', 'MBFL', now(), now(), '65a07b11-25cf-4fae-baf4-59d89677cfc2'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99685', 'MBFL', now(), now(), '0cc24afe-3579-41bf-8181-08725b45e3b0'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99686', 'MBFL', now(), now(), 'fd40734b-f683-43b2-9e6e-93eeda219aa9'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99687', 'MBFL', now(), now(), 'fe3c676b-a3d7-4d58-88d4-5a6ab3dc3aae'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99688', 'MBFL', now(), now(), '83cc5417-31e0-468c-a5b3-0495734e9504'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99689', 'MAPK', now(), now(), '68962696-2ba8-40ae-946d-8ab29dd3cd4b'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99690', 'MBFL', now(), now(), '92d71dc8-6601-4cc0-bd76-3584e402ceed'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99691', 'MBFL', now(), now(), '839bfff4-f486-462f-bf2c-560aa95f5b6e'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99692', 'MBFL', now(), now(), 'b3e45fb6-3e5f-452e-b355-08fa49e1f52b'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99693', 'MBFL', now(), now(), 'd3843234-0168-402a-821d-9a4666a54273'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99694', 'MBFL', now(), now(), '3ca39ea7-1f6d-4112-886e-cd4c6fc07476'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99695', 'MBFL', now(), now(), 'b865dfa9-c1c4-42bd-9253-8fa643ebf582'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99697', 'MAPS', now(), now(), '8e71753d-c45a-4a84-912e-cb6bb8a0ccef'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99701', 'MBFL', now(), now(), '951282d4-0523-450b-a636-c2bdadf2a38a'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99702', 'MBFL', now(), now(), 'fcab1e10-4645-4d3e-a4b7-00a9f54a2157'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99703', 'JEAT', now(), now(), '5f47676a-520d-4106-9889-009b0ab29726'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99704', 'MBFL', now(), now(), '328fd045-2e70-4bc2-a466-1319e2425b1f'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99705', 'MBFL', now(), now(), '37727f3a-6c6e-4d9e-890c-ec0279c7de0b'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99706', 'MBFL', now(), now(), 'c84810a5-660f-4200-a86f-46a17f038adf'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99707', 'MBFL', now(), now(), '7d090646-098b-4096-9012-b68182cf2e35'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99708', 'MBFL', now(), now(), '7a16c7b2-e624-4933-9dc4-b3be69849fe5'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99709', 'MBFL', now(), now(), 'bd590cc5-a624-4fb2-b37b-4b37a7ac863a'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99710', 'MBFL', now(), now(), '5fab3c20-38ee-44b6-9ae1-384b93462637'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99711', 'MBFL', now(), now(), '1d3a88e9-a0e0-4cce-b20a-9f0b6996b7e1'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99712', 'MBFL', now(), now(), 'acec5c52-6709-48cb-a6cb-0e58b05860ec'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99714', 'MBFL', now(), now(), 'ce621370-6656-480d-a071-89de4607d716'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99716', 'MBFL', now(), now(), 'b65f161a-0f33-4360-8905-c716b17943d4'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99720', 'MBFL', now(), now(), '19aa130f-0dee-46db-a054-729cddf9a257'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99721', 'MBFL', now(), now(), '715fde06-a1a7-4240-8e59-4b76dffdb144'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99722', 'MBFL', now(), now(), '4be97e0b-4237-4906-bb0d-8e1704e0b65b'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99723', 'MBFL', now(), now(), 'aad84c1d-7ba2-40de-92e5-74b2dd558fa9'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99724', 'MBFL', now(), now(), '8b8157b2-0c64-477f-a173-e26818caeffd'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99725', 'MBFL', now(), now(), '8f258372-966a-4565-9e22-61cdb5f88ef9'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99726', 'MBFL', now(), now(), '02f07ef0-ebad-4fb8-a50c-bdd140d87c49'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99727', 'MBFL', now(), now(), '63de42c5-eb6e-4471-9170-8d4d3fd88d87'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99729', 'MBFL', now(), now(), 'c12cf5c5-b3e0-4dff-a88f-39b8a22a950b'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99730', 'MBFL', now(), now(), '820b8fe5-2c4c-459a-850a-8326fd50ea57'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99731', 'JEAT', now(), now(), 'dd366219-6c3b-45c1-a59a-e04d0efe1a6d'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99732', 'MBFL', now(), now(), '32e2f9ec-805a-419b-a850-e4868c8eb13f'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99733', 'MBFL', now(), now(), '9762e6a0-a2dd-4da7-9cc5-4dfdfd21fad2'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99734', 'MBFL', now(), now(), 'f35587d4-41be-4e7a-8cc3-07e1f2a9e4b9'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99736', 'MBFL', now(), now(), 'a71189c1-784c-4d9c-925b-dcc6432c6a28'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99737', 'MBFL', now(), now(), '80a884f0-058a-46c0-8fdb-90d1d027f002'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99738', 'MBFL', now(), now(), '34cf2f93-66b3-43ab-a428-2cc2e4c6b143'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99739', 'MBFL', now(), now(), 'eadc1ca9-6de2-46ab-a015-acb813599670'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99740', 'MBFL', now(), now(), 'cc3f07af-485b-4256-8703-bf41a5967497'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99741', 'MBFL', now(), now(), '61409776-e0fa-47f4-825e-9c6f91ce2b10'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99742', 'MBFL', now(), now(), 'b46d81ef-9faa-49ec-85a3-4c3734d07126'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99743', 'MBFL', now(), now(), 'acb28741-502a-40c2-aa45-4a9b42d39405'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99744', 'MBFL', now(), now(), 'baf46567-31ea-481d-9ab9-e974ea261c2f'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99745', 'MBFL', now(), now(), 'babaac73-1c6b-4209-baf3-1e0c5d41dc7f'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99746', 'MBFL', now(), now(), 'ddc32535-73e0-4f13-ad2e-6294700c17cc'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99747', 'MBFL', now(), now(), '40a3c816-79a0-4f26-aa6a-2847835a45d1'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99748', 'MBFL', now(), now(), '5a3a2cce-4bc0-4bd2-98fe-7f71bc09c99e'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99749', 'MBFL', now(), now(), '212653a0-4a5a-4e48-b301-78b3f294fe16'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99750', 'MBFL', now(), now(), '2013baa1-1f2f-47f7-beb0-98f3171c6bf1'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99751', 'MBFL', now(), now(), 'a19e22ef-194e-4b48-858c-59ea04d48fcf'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99752', 'MBFL', now(), now(), 'e1ce6564-37d3-4fe4-8c19-f39e31c9d7cd'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99753', 'MBFL', now(), now(), '2dd6633b-4a67-4bff-879f-32bf88ec64a5'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99754', 'MBFL', now(), now(), '5fe4e773-573e-4052-b4b7-d5686366d03b'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99755', 'MBFL', now(), now(), '0383ed06-ff31-4fa7-851a-962dc7adf7d3'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99756', 'MBFL', now(), now(), '61726154-40a0-4f7d-82d0-b09f28a2f209'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99757', 'MBFL', now(), now(), '3bd35ddb-4d89-4e01-8dd1-e938bc5c0388'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99758', 'MBFL', now(), now(), '31c3a401-cc17-4e5a-bf22-d66e00a05528'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99759', 'MBFL', now(), now(), 'cc534b49-e72f-4a19-bbad-4dc5101c1373'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99760', 'MBFL', now(), now(), '56e2c9c0-f914-40f3-988c-7b88593f5513'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99761', 'MBFL', now(), now(), 'ccd849d7-e2ed-40ce-a353-9c93d57b4f92'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99762', 'MBFL', now(), now(), '1981bcff-5f4b-4358-92ea-93177783215f'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99763', 'MBFL', now(), now(), '298d0d8a-a19a-4498-937f-1ab233a85730'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99764', 'MBFL', now(), now(), '37352211-832f-4af7-8655-414ac02b8544'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99765', 'MBFL', now(), now(), 'c2dd65ee-9875-4e7d-9d34-46f529f8959b'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99766', 'MBFL', now(), now(), 'ad3283f4-74cb-403f-84a2-c14f510f83cd'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99767', 'MBFL', now(), now(), 'eb25b5f7-ed3b-4860-b337-5ee68b6c4534'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99768', 'MBFL', now(), now(), '92116116-8a7a-420c-83cd-5f9977401941'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99769', 'MBFL', now(), now(), 'c5a38d76-3bef-4e9d-8244-1741aa20ed10'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99770', 'MBFL', now(), now(), 'e7f0f266-019d-4bce-8921-6804e5e8cb0c'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99771', 'MBFL', now(), now(), '74f9319a-0970-4721-8fda-bfe4f777896b'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99772', 'MBFL', now(), now(), 'db7dc663-1443-4209-9a01-82f694a4dca1'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99773', 'MBFL', now(), now(), '474d4d25-1314-4bbf-ae04-25ffae18a7d4'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99774', 'MBFL', now(), now(), 'ea503792-8747-465c-ad89-c513004e1345'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99775', 'MBFL', now(), now(), 'e33ba93c-8ba2-4a3b-b8bc-f331a989e388'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99776', 'MBFL', now(), now(), 'ffc00def-ec8b-413d-9601-bf5587b510f4'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99777', 'MBFL', now(), now(), 'ce0ff3db-7d59-43af-923d-efdf4feb72cf'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99778', 'MBFL', now(), now(), '4235599e-4fd3-46e9-b733-de29dca74700'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99780', 'MBFL', now(), now(), '0aa94d7f-81bb-42f4-be8a-e14b2bc630c8'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99781', 'MBFL', now(), now(), '1b98f0bd-711c-48f0-87ed-9e5bff30ad60'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99782', 'MBFL', now(), now(), 'c7f7dd5b-7674-4b78-b1a5-0013fd979231'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99783', 'MBFL', now(), now(), '20152935-a8c8-45ad-a4cd-af2a945a8a17'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99784', 'MBFL', now(), now(), '58ec9aaa-e2d0-4732-9ffd-3b7563bb1c05'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99785', 'MBFL', now(), now(), '13f3eb0b-1534-4773-9fae-a86d39a829ae'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99786', 'MBFL', now(), now(), 'eeb04da3-f067-469b-8703-c3e9de365ad7'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99788', 'MBFL', now(), now(), '224088e2-afba-4b23-8361-9126c00445a8'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99789', 'MBFL', now(), now(), '8c123f36-f68d-461b-bd60-01cbd501ae17'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99790', 'MBFL', now(), now(), '1492fc25-8a4a-4fbc-a9be-433ad03d878e'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99791', 'MBFL', now(), now(), 'fda0d6c4-7a68-425b-9567-da8d368dbfa0'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99801', 'MAPK', now(), now(), '095789bc-136f-44e6-bf3f-b5a5a4ffad5c'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99802', 'MAPK', now(), now(), '1e73fbe5-0924-42dc-aedd-9c4aab511b90'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99803', 'MAPK', now(), now(), '281f58c0-fe6e-4e77-9035-6f1db1d4a8fd'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99811', 'MAPK', now(), now(), '6dcd4248-9c66-4bf3-8f4b-30dfd4add731'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99812', 'MAPK', now(), now(), '82579769-f363-4d68-b6f4-2586ac3f974c'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99820', 'MAPK', now(), now(), 'aecd46d3-dea5-4d0e-8409-b5b300ddded8'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99821', 'MAPK', now(), now(), '5a3ff675-8c07-4058-b5c6-8c19400f2f07'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99824', 'MAPK', now(), now(), 'e0cd0a98-4c4e-4d21-9adc-416fb4ce2449'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99825', 'MAPK', now(), now(), 'aa515936-aa46-4016-a487-c925b4dc16b2'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99826', 'MAPK', now(), now(), '553f410a-ed32-4517-a766-c665369d9369'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99827', 'MAPK', now(), now(), 'a275e877-48ba-4473-8064-43b3ab7b042b'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99829', 'MAPK', now(), now(), '7cb1babd-df8b-400f-9e5c-6c595bbcaa2f'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99830', 'MAPK', now(), now(), 'dcc9dd70-0984-4555-a371-e8f8bf583ecf'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99832', 'MAPK', now(), now(), 'a3740468-c12a-465d-b627-fd80bc921eb5'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99833', 'MAPK', now(), now(), '38eecaf7-4c2d-44c5-a8bd-ccde44ec6b89'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99835', 'MAPK', now(), now(), 'c914333a-4c88-404b-a17a-f51b4df9dac7'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99836', 'MAPK', now(), now(), 'fe065064-61f7-4c01-80f4-945fb1d6d26f'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99840', 'MAPK', now(), now(), '7c72b250-4d4b-41a2-b548-97a5b1e1c526'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99841', 'MAPK', now(), now(), '1a1c28b2-d5c7-4180-93ea-d66887ce7e26'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99850', 'MAPK', now(), now(), '21f9854e-2a61-4810-a4e3-ea09f1f2c59f'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99901', 'MAPK', now(), now(), '36ef16e6-d9a5-4706-a115-e11691ff1c37'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99903', 'MAPK', now(), now(), '76d5df09-e4f1-4b96-8df4-68bbeee29b00'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99918', 'MAPK', now(), now(), '4b6b34d9-40ec-459a-a337-5fbdde3aaa84'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99919', 'MAPK', now(), now(), 'af79bdf9-f4cf-494e-ad67-ddbb836e98d0'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99921', 'MAPK', now(), now(), '742e78df-6615-406b-b0f2-55deaec1fc09'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99922', 'MAPK', now(), now(), '99701216-788c-41d7-bdbb-98ff0f8e2224'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99923', 'MAPK', now(), now(), '3978cb58-70b6-4c23-a97b-d6e00cf7abcf'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99925', 'MAPK', now(), now(), 'bc7cb635-6287-4ecb-8f8c-9e6f66858441'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99926', 'MAPK', now(), now(), '9abec1b8-3f19-4616-aae2-8c8a54b44e48'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99927', 'MAPK', now(), now(), 'ba7adb39-9897-4e4d-b042-c4360cc25691'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99928', 'MAPK', now(), now(), '4f721fb2-7bcc-4a2a-a24b-cd3d00532311'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99929', 'MAPK', now(), now(), '0ed1ee93-f720-4e4c-b28a-4572e847ad15'); + +INSERT INTO postal_code_to_gblocs(postal_code, gbloc, created_at, updated_at, id) +VALUES ('99950', 'MAPK', now(), now(), '23c0f299-2b5c-44ff-b604-5efd70019f8d'); diff --git a/migrations/app/schema/20250103142533_update_postal_codes_and_gblocs_for_ak.up.sql b/migrations/app/schema/20250103142533_update_postal_codes_and_gblocs_for_ak.up.sql new file mode 100644 index 00000000000..a150e692932 --- /dev/null +++ b/migrations/app/schema/20250103142533_update_postal_codes_and_gblocs_for_ak.up.sql @@ -0,0 +1,159 @@ +delete from postal_code_to_gblocs where postal_code in ( +select uspr_zip_id from v_locations where state = 'AK'); + +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' + when s.affiliation = 'NAVY' THEN 'NAVY_AND_MARINES' + when s.affiliation = 'MARINES' THEN 'NAVY_AND_MARINES' + 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; + + +DROP FUNCTION IF EXISTS get_address_gbloc; + +CREATE OR REPLACE FUNCTION public.get_address_gbloc( + address_id UUID, + affiliation TEXT, + OUT gbloc TEXT +) +RETURNS TEXT AS $$ +DECLARE + is_oconus BOOLEAN; + v_count INT; + v_bos_count INT; + v_dept_ind TEXT; +BEGIN + is_oconus := get_is_oconus(address_id); + + IF affiliation in ('AIR_FORCE','SPACE_FORCE') THEN + v_dept_ind := 'AIR_AND_SPACE_FORCE'; + ELSIF affiliation in ('MARINES','NAVY') THEN + v_dept_ind := 'NAVY_AND_MARINES'; + ELSE v_dept_ind := affiliation; + END IF; + + IF is_oconus THEN + + SELECT count(*) + INTO v_count + FROM addresses a, + re_oconus_rate_areas o, + jppso_regions j, + gbloc_aors g + WHERE a.us_post_region_cities_id = o.us_post_region_cities_id + and o.id = g.oconus_rate_area_id + and j.id = g.jppso_regions_id + and a.id = address_id; + + IF v_count > 1 THEN + + --check for gbloc by bos + SELECT count(*) + INTO v_bos_count + FROM addresses a, + re_oconus_rate_areas o, + jppso_regions j, + gbloc_aors g + WHERE a.us_post_region_cities_id = o.us_post_region_cities_id + and o.id = g.oconus_rate_area_id + and j.id = g.jppso_regions_id + and a.id = address_id + and g.department_indicator = v_dept_ind; + + IF v_bos_count = 1 THEN + + SELECT j.code + INTO gbloc + FROM addresses a, + re_oconus_rate_areas o, + jppso_regions j, + gbloc_aors g + WHERE a.us_post_region_cities_id = o.us_post_region_cities_id + and o.id = g.oconus_rate_area_id + and j.id = g.jppso_regions_id + and a.id = address_id + and g.department_indicator = v_dept_ind; + + ELSE + + SELECT j.code + INTO gbloc + FROM addresses a, + re_oconus_rate_areas o, + jppso_regions j, + gbloc_aors g + WHERE a.us_post_region_cities_id = o.us_post_region_cities_id + and o.id = g.oconus_rate_area_id + and j.id = g.jppso_regions_id + and a.id = address_id + and g.department_indicator IS NULL; + + END IF; + + ELSE + + SELECT j.code + INTO gbloc + FROM addresses a, + re_oconus_rate_areas o, + jppso_regions j, + gbloc_aors g + WHERE a.us_post_region_cities_id = o.us_post_region_cities_id + and o.id = g.oconus_rate_area_id + and j.id = g.jppso_regions_id + and a.id = address_id; + + END IF; + + ELSE --is conus + + SELECT j.gbloc + INTO gbloc + FROM addresses a, + v_locations o, + postal_code_to_gblocs j + WHERE a.us_post_region_cities_id = o.uprc_id + and o.uspr_zip_id = j.postal_code + and a.id = address_id; + + END IF; + + IF gbloc IS NULL THEN + RAISE EXCEPTION 'GBLOC not found for address ID % for affiliation %', address_id, affiiation; + END IF; +END; +$$ LANGUAGE plpgsql; \ No newline at end of file diff --git a/migrations/app/schema/20250109194140_create_audit_history_table_for_payment_service_items.up.sql b/migrations/app/schema/20250109194140_create_audit_history_table_for_payment_service_items.up.sql new file mode 100644 index 00000000000..8318441ef8d --- /dev/null +++ b/migrations/app/schema/20250109194140_create_audit_history_table_for_payment_service_items.up.sql @@ -0,0 +1 @@ +SELECT add_audit_history_table(target_table := 'payment_service_items', audit_rows := BOOLEAN 't', audit_query_text := BOOLEAN 't', ignored_cols := ARRAY['created_at', 'updated_at', 'denied_at', 'requested_at', 'sent_to_gex_at', 'approved_at']); \ No newline at end of file diff --git a/migrations/app/schema/20250113160816_updating_create_accessorial_service_item_proc.up.sql b/migrations/app/schema/20250113160816_updating_create_accessorial_service_item_proc.up.sql new file mode 100644 index 00000000000..ff31e1769c1 --- /dev/null +++ b/migrations/app/schema/20250113160816_updating_create_accessorial_service_item_proc.up.sql @@ -0,0 +1,121 @@ +DO ' +BEGIN + IF EXISTS (SELECT 1 FROM pg_type WHERE typname = ''mto_service_item_type'') THEN + ALTER TYPE mto_service_item_type DROP ATTRIBUTE "re_service_id"; + ALTER TYPE mto_service_item_type ADD ATTRIBUTE "re_service_code" text; + END IF; +END +'; + +DROP PROCEDURE create_accessorial_service_items_for_shipment(uuid,mto_service_item_type[]); +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 + IF NOT does_service_item_exist(service_item.re_service_id, shipment_id) THEN + INSERT INTO mto_service_items ( + mto_shipment_id, + move_id, + re_service_id, + service_location, + status, + created_at, + updated_at, + sit_postal_code, + sit_entry_date, + sit_customer_contacted, + reason, + estimated_weight, + actual_weight, + pickup_postal_code, + description, + sit_destination_original_address_id, + sit_destination_final_address_id, + sit_requested_delivery, + sit_departure_date, + sit_origin_hhg_original_address_id, + sit_origin_hhg_actual_address_id, + customer_expense, + customer_expense_reason, + sit_delivery_miles, + standalone_crate + ) + VALUES ( + shipment_id, + move_id, + service_item.re_service_id, + service_item.service_location, + ''SUBMITTED''::service_item_status, + NOW(), + NOW(), + (item).sit_postal_code, + (item).sit_entry_date, + (item).sit_customer_contacted, + (item).reason, + (item).estimated_weight, + (item).actual_weight, + (item).pickup_postal_code, + (item).description, + (item).sit_destination_original_address_id, + (item).sit_destination_final_address_id, + (item).sit_requested_delivery, + (item).sit_departure_date, + (item).sit_origin_hhg_original_address_id, + (item).sit_origin_hhg_actual_address_id, + (item).customer_expense, + (item).customer_expense_reason, + (item).sit_delivery_miles, + (item).standalone_crate + ) 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/20250114164752_add_ppm_estimated_incentive_proc.up.sql b/migrations/app/schema/20250114164752_add_ppm_estimated_incentive_proc.up.sql new file mode 100644 index 00000000000..c0d7b3c9407 --- /dev/null +++ b/migrations/app/schema/20250114164752_add_ppm_estimated_incentive_proc.up.sql @@ -0,0 +1,249 @@ +-- IDFSIT PerUnitCents +INSERT INTO service_params (id,service_id,service_item_param_key_id,created_at,updated_at,is_optional) VALUES + ('fb7925e7-ebfe-49d9-9cf4-7219e68ec686'::uuid,'bd6064ca-e780-4ab4-a37b-0ae98eebb244','597bb77e-0ce7-4ba2-9624-24300962625f','2024-01-17 15:55:50.041957','2024-01-17 15:55:50.041957',false); -- PerUnitCents + +-- IDASIT PerUnitCents +INSERT INTO service_params (id,service_id,service_item_param_key_id,created_at,updated_at,is_optional) VALUES + ('51393ee1-f505-4f7b-96c4-135f771af814'::uuid,'806c6d59-57ff-4a3f-9518-ebf29ba9cb10','597bb77e-0ce7-4ba2-9624-24300962625f','2024-01-17 15:55:50.041957','2024-01-17 15:55:50.041957',false); -- PerUnitCents + +-- IOFSIT PerUnitCents +INSERT INTO service_params (id,service_id,service_item_param_key_id,created_at,updated_at,is_optional) VALUES + ('7518ec84-0c40-4c17-86dd-3ce04e2fe701'::uuid,'b488bf85-ea5e-49c8-ba5c-e2fa278ac806','597bb77e-0ce7-4ba2-9624-24300962625f','2024-01-17 15:55:50.041957','2024-01-17 15:55:50.041957',false); -- PerUnitCents + +-- IOASIT PerUnitCents +INSERT INTO service_params (id,service_id,service_item_param_key_id,created_at,updated_at,is_optional) VALUES + ('cff34123-e2a5-40ed-9cf3-451701850a26'::uuid,'bd424e45-397b-4766-9712-de4ae3a2da36','597bb77e-0ce7-4ba2-9624-24300962625f','2024-01-17 15:55:50.041957','2024-01-17 15:55:50.041957',false); -- PerUnitCents + +-- inserting PortZip param for FSC +-- we need this for international PPMs since they only get reimbursed for the CONUS -> Port portion +INSERT INTO service_params (id,service_id,service_item_param_key_id,created_at,updated_at,is_optional) VALUES + ('bb53e034-80c2-420e-8492-f54d2018fff1'::uuid,'4780b30c-e846-437a-b39a-c499a6b09872','d9ad3878-4b94-4722-bbaf-d4b8080f339d','2024-01-17 15:55:50.041957','2024-01-17 15:55:50.041957',true); -- PortZip + +-- remove PriceAreaIntlOrigin, we don't need it +DELETE FROM service_params +WHERE service_item_param_key_id = '6d44624c-b91b-4226-8fcd-98046e2f433d'; + +DELETE FROM service_item_param_keys +WHERE key = 'PriceAreaIntlOrigin'; + +-- remove PriceAreaIntlDest, we don't need it +DELETE FROM service_params +WHERE service_item_param_key_id = '4736f489-dfda-4df1-a303-8c434a120d5d'; + +DELETE FROM service_item_param_keys +WHERE key = 'PriceAreaIntlDest'; + +-- adding port info that PPMs will consume +INSERT INTO public.ports +(id, port_code, port_type, port_name, created_at, updated_at) +VALUES('d8776c6b-bc5e-45d8-ac50-ab60c34c022d'::uuid, '4E1', 'S','TACOMA, PUGET SOUND', now(), now()); + +INSERT INTO public.port_locations +(id, port_id, cities_id, us_post_region_cities_id, country_id, is_active, created_at, updated_at) +VALUES('ee3a97dc-112e-4805-8518-f56f2d9c6cc6'::uuid, 'd8776c6b-bc5e-45d8-ac50-ab60c34c022d'::uuid, 'baaf6ab1-6142-4fb7-b753-d0a142c75baf'::uuid, '86fef297-d61f-44ea-afec-4f679ce686b7'::uuid, '791899e6-cd77-46f2-981b-176ecb8d7098'::uuid, true, now(), now()); + +-- func to fetch a service id from re_services by providing the service code +CREATE OR REPLACE FUNCTION get_service_id(service_code TEXT) RETURNS UUID AS $$ +DECLARE + service_id UUID; +BEGIN + SELECT rs.id INTO service_id FROM re_services rs WHERE rs.code = service_code; + IF service_id IS NULL THEN + RAISE EXCEPTION 'Service code % not found in re_services', service_code; + END IF; + RETURN service_id; +END; +$$ LANGUAGE plpgsql; + + +-- db func that will calculate a PPM's incentives +-- this is used for estimated/final/max incentives +CREATE OR REPLACE FUNCTION calculate_ppm_incentive( + ppm_id UUID, + pickup_address_id UUID, + destination_address_id UUID, + move_date DATE, + mileage INT, + weight INT, + is_estimated BOOLEAN, + is_actual BOOLEAN, + is_max BOOLEAN +) RETURNS TABLE ( + total_incentive NUMERIC, + price_islh NUMERIC, + price_ihpk NUMERIC, + price_ihupk NUMERIC, + price_fsc NUMERIC +) AS +$$ +DECLARE + ppm RECORD; + contract_id UUID; + o_rate_area_id UUID; + d_rate_area_id UUID; + service_id UUID; + estimated_fsc_multiplier NUMERIC; + fuel_price NUMERIC; + price_difference NUMERIC; + cents_above_baseline NUMERIC; +BEGIN + + IF NOT is_estimated AND NOT is_actual AND NOT is_max THEN + RAISE EXCEPTION 'is_estimated, is_actual, and is_max cannot all be FALSE. No update will be performed.'; + END IF; + + -- validating it's a real PPM + SELECT ppms.id INTO ppm FROM ppm_shipments ppms WHERE ppms.id = ppm_id; + IF ppm IS NULL THEN + RAISE EXCEPTION 'PPM with ID % not found', ppm_id; + END IF; + + contract_id := get_contract_id(move_date); + IF contract_id IS NULL THEN + RAISE EXCEPTION 'Contract not found for date: %', move_date; + END IF; + + o_rate_area_id := get_rate_area_id(pickup_address_id, NULL, contract_id); + IF o_rate_area_id IS NULL THEN + RAISE EXCEPTION 'Origin rate area is NULL for address ID %', pickup_address_id; + END IF; + + d_rate_area_id := get_rate_area_id(destination_address_id, NULL, contract_id); + IF d_rate_area_id IS NULL THEN + RAISE EXCEPTION 'Destination rate area is NULL for address ID %', destination_address_id; + END IF; + + -- ISLH calculation + service_id := get_service_id('ISLH'); + price_islh := ROUND( + calculate_escalated_price( + o_rate_area_id, + d_rate_area_id, + service_id, + contract_id, + 'ISLH', + move_date + ) * (weight / 100)::NUMERIC * 100, 0 + ); + + -- IHPK calculation + service_id := get_service_id('IHPK'); + price_ihpk := ROUND( + calculate_escalated_price( + o_rate_area_id, + NULL, + service_id, + contract_id, + 'IHPK', + move_date + ) * (weight / 100)::NUMERIC * 100, 0 + ); + + -- IHUPK calculation + service_id := get_service_id('IHUPK'); + price_ihupk := ROUND( + calculate_escalated_price( + NULL, + d_rate_area_id, + service_id, + contract_id, + 'IHUPK', + move_date + ) * (weight / 100)::NUMERIC * 100, 0 + ); + + -- FSC calculation + estimated_fsc_multiplier := get_fsc_multiplier(weight); + fuel_price := get_fuel_price(move_date); + price_difference := calculate_price_difference(fuel_price); + cents_above_baseline := mileage * estimated_fsc_multiplier; + price_fsc := ROUND((cents_above_baseline * price_difference) * 100); + + total_incentive := price_islh + price_ihpk + price_ihupk + price_fsc; + + UPDATE ppm_shipments + SET estimated_incentive = CASE WHEN is_estimated THEN total_incentive ELSE estimated_incentive END, + final_incentive = CASE WHEN is_actual THEN total_incentive ELSE final_incentive END, + max_incentive = CASE WHEN is_max THEN total_incentive ELSE max_incentive END + WHERE id = ppm_id; + + -- returning a table so we can use this data in the breakdown for the service member + RETURN QUERY SELECT total_incentive, price_islh, price_ihpk, price_ihupk, price_fsc; +END; +$$ LANGUAGE plpgsql; + + +-- db func that will calculate a PPM's SIT cost +-- returns a table with total cost and the cost of each first day/add'l day SIT service item +CREATE OR REPLACE FUNCTION calculate_ppm_sit_cost( + ppm_id UUID, + address_id UUID, + is_origin BOOLEAN, + move_date DATE, + weight INT, + sit_days INT +) RETURNS TABLE ( + total_cost INT, + price_first_day INT, + price_addl_day INT +) AS +$$ +DECLARE + ppm RECORD; + contract_id UUID; + sit_rate_area_id UUID; + service_id UUID; +BEGIN + -- make sure we validate parameters + IF sit_days IS NULL OR sit_days < 0 THEN + RAISE EXCEPTION 'SIT days must be a positive integer. Provided value: %', sit_days; + END IF; + + SELECT ppms.id INTO ppm FROM ppm_shipments ppms WHERE ppms.id = ppm_id; + IF ppm IS NULL THEN + RAISE EXCEPTION 'PPM with ID % not found', ppm_id; + END IF; + + contract_id := get_contract_id(move_date); + IF contract_id IS NULL THEN + RAISE EXCEPTION 'Contract not found for date: %', move_date; + END IF; + + sit_rate_area_id := get_rate_area_id(address_id, NULL, contract_id); + IF sit_rate_area_id IS NULL THEN + RAISE EXCEPTION 'Rate area is NULL for address ID % and contract ID %', address_id, contract_id; + END IF; + + -- calculate first day SIT cost + service_id := get_service_id(CASE WHEN is_origin THEN 'IOFSIT' ELSE 'IDFSIT' END); + price_first_day := ( + calculate_escalated_price( + CASE WHEN is_origin THEN sit_rate_area_id ELSE NULL END, + CASE WHEN NOT is_origin THEN sit_rate_area_id ELSE NULL END, + service_id, + contract_id, + CASE WHEN is_origin THEN 'IOFSIT' ELSE 'IDFSIT' END, + move_date + ) * (weight / 100)::NUMERIC * 100 + )::INT; + + -- calculate additional day SIT cost + service_id := get_service_id(CASE WHEN is_origin THEN 'IOASIT' ELSE 'IDASIT' END); + price_addl_day := ( + calculate_escalated_price( + CASE WHEN is_origin THEN sit_rate_area_id ELSE NULL END, + CASE WHEN NOT is_origin THEN sit_rate_area_id ELSE NULL END, + service_id, + contract_id, + CASE WHEN is_origin THEN 'IOASIT' ELSE 'IDASIT' END, + move_date + ) * (weight / 100)::NUMERIC * 100 * sit_days + )::INT; + + -- add em up + total_cost := price_first_day + price_addl_day; + + RETURN QUERY SELECT total_cost, price_first_day, price_addl_day; +END; +$$ LANGUAGE plpgsql; + diff --git a/migrations/app/schema/20250121153007_update_pricing_proc_to_handle_international_shuttle.up.sql b/migrations/app/schema/20250121153007_update_pricing_proc_to_handle_international_shuttle.up.sql new file mode 100644 index 00000000000..834525f7a6d --- /dev/null +++ b/migrations/app/schema/20250121153007_update_pricing_proc_to_handle_international_shuttle.up.sql @@ -0,0 +1,213 @@ +-- function to calculate the escalated price, takes in: +-- origin rate area +-- dest rate area +-- re_services id +-- contract id +-- adding the is_peak_period check to refine the price query further +CREATE OR REPLACE FUNCTION calculate_escalated_price( + o_rate_area_id UUID, + d_rate_area_id UUID, + re_service_id UUID, + c_id UUID, + service_code TEXT, + requested_pickup_date DATE +) RETURNS NUMERIC AS $$ +DECLARE + per_unit_cents NUMERIC; + escalation_factor NUMERIC; + escalated_price NUMERIC; + is_oconus BOOLEAN; + peak_period BOOLEAN; +BEGIN + -- we need to query the appropriate table based on the service code + -- need to establish if the shipment is being moved during peak period + peak_period := is_peak_period(requested_pickup_date); + IF service_code IN ('IOSHUT','IDSHUT') THEN + IF service_code = 'IOSHUT' THEN + SELECT ra.is_oconus + INTO is_oconus + FROM re_rate_areas ra + WHERE ra.id = o_rate_area_id; + ELSE + SELECT ra.is_oconus + INTO is_oconus + FROM re_rate_areas ra + WHERE ra.id = d_rate_area_id; + END IF; + + SELECT rip.per_unit_cents + INTO per_unit_cents + FROM re_intl_accessorial_prices rip + WHERE + rip.market = (CASE + WHEN is_oconus THEN 'O' + ELSE 'C' + END) + AND rip.service_id = re_service_id + AND rip.contract_id = c_id; + ELSIF service_code IN ('ISLH', 'UBP') THEN + SELECT rip.per_unit_cents + INTO per_unit_cents + FROM re_intl_prices rip + WHERE rip.origin_rate_area_id = o_rate_area_id AND rip.destination_rate_area_id = d_rate_area_id + AND rip.service_id = re_service_id + AND rip.contract_id = c_id + AND rip.is_peak_period = peak_period; + ELSE + SELECT riop.per_unit_cents + INTO per_unit_cents + FROM re_intl_other_prices riop + WHERE (riop.rate_area_id = o_rate_area_id OR riop.rate_area_id = d_rate_area_id OR + (o_rate_area_id IS NULL AND d_rate_area_id IS NULL)) + AND riop.service_id = re_service_id + AND riop.contract_id = c_id + AND riop.is_peak_period = peak_period; + END IF; + + RAISE NOTICE '% per unit cents: %', service_code, per_unit_cents; + IF per_unit_cents IS NULL THEN + RAISE EXCEPTION 'No per unit cents found for service item id: %, origin rate area: %, dest rate area: %, and contract_id: %', re_service_id, o_rate_area_id, d_rate_area_id, c_id; + END IF; + + SELECT rcy.escalation_compounded + INTO escalation_factor + FROM re_contract_years rcy + WHERE rcy.contract_id = c_id + AND requested_pickup_date BETWEEN rcy.start_date AND rcy.end_date; + + IF escalation_factor IS NULL THEN + RAISE EXCEPTION 'Escalation factor not found for contract_id %', c_id; + END IF; + -- calculate the escalated price, return in dollars (dividing by 100) + per_unit_cents := per_unit_cents / 100; -- putting in dollars + escalated_price := ROUND(per_unit_cents * escalation_factor, 2); -- rounding to two decimals (100.00) + + RETURN escalated_price; +END; +$$ LANGUAGE plpgsql; + +--Updating proc to handle IDSHUT and IOSHUT service items +CREATE OR REPLACE PROCEDURE update_service_item_pricing( + shipment_id UUID, + mileage INT +) AS +' +DECLARE + shipment RECORD; + service_item RECORD; + escalated_price NUMERIC; + estimated_price NUMERIC; + o_rate_area_id UUID; + d_rate_area_id UUID; + contract_id UUID; + service_code TEXT; + o_zip_code TEXT; + d_zip_code TEXT; + distance NUMERIC; + estimated_fsc_multiplier NUMERIC; + fuel_price NUMERIC; + cents_above_baseline NUMERIC; + price_difference NUMERIC; +BEGIN + SELECT ms.id, ms.pickup_address_id, ms.destination_address_id, ms.requested_pickup_date, ms.prime_estimated_weight + INTO shipment + FROM mto_shipments ms + WHERE ms.id = shipment_id; + + IF shipment IS NULL THEN + RAISE EXCEPTION ''Shipment with ID % not found'', shipment_id; + END IF; + + -- exit the proc if prime_estimated_weight is NULL + IF shipment.prime_estimated_weight IS NULL THEN + RETURN; + END IF; + + -- loop through service items in the shipment + FOR service_item IN + SELECT si.id, si.re_service_id + FROM mto_service_items si + WHERE si.mto_shipment_id = shipment_id + LOOP + -- get the service code for the current service item to determine calculation + SELECT code + INTO service_code + FROM re_services + WHERE id = service_item.re_service_id; + + CASE + WHEN service_code IN (''ISLH'', ''UBP'') THEN + contract_id := get_contract_id(shipment.requested_pickup_date); + o_rate_area_id := get_rate_area_id(shipment.pickup_address_id, service_item.re_service_id, contract_id); + d_rate_area_id := get_rate_area_id(shipment.destination_address_id, service_item.re_service_id, contract_id); + escalated_price := calculate_escalated_price(o_rate_area_id, d_rate_area_id, service_item.re_service_id, contract_id, service_code, shipment.requested_pickup_date); + + IF shipment.prime_estimated_weight IS NOT NULL THEN + -- multiply by 110% of estimated weight + estimated_price := ROUND((escalated_price * (shipment.prime_estimated_weight * 1.1) / 100), 2) * 100; + RAISE NOTICE ''%: Received estimated price of % (% * (% * 1.1) / 100)) cents'', service_code, estimated_price, escalated_price, shipment.prime_estimated_weight; + -- update the pricing_estimate value in mto_service_items + UPDATE mto_service_items + SET pricing_estimate = estimated_price + WHERE id = service_item.id; + END IF; + + WHEN service_code IN (''IHPK'', ''IUBPK'', ''IOSHUT'') THEN + -- perform IHPK/IUBPK-specific logic (no destination rate area) + contract_id := get_contract_id(shipment.requested_pickup_date); + o_rate_area_id := get_rate_area_id(shipment.pickup_address_id, service_item.re_service_id, contract_id); + escalated_price := calculate_escalated_price(o_rate_area_id, NULL, service_item.re_service_id, contract_id, service_code, shipment.requested_pickup_date); + + IF shipment.prime_estimated_weight IS NOT NULL THEN + -- multiply by 110% of estimated weight + estimated_price := ROUND((escalated_price * (shipment.prime_estimated_weight * 1.1) / 100), 2) * 100; + RAISE NOTICE ''%: Received estimated price of % (% * (% * 1.1) / 100)) cents'', service_code, estimated_price, escalated_price, shipment.prime_estimated_weight; + -- update the pricing_estimate value in mto_service_items + UPDATE mto_service_items + SET pricing_estimate = estimated_price + WHERE id = service_item.id; + END IF; + + WHEN service_code IN (''IHUPK'', ''IUBUPK'', ''IDSHUT'') THEN + -- perform IHUPK/IUBUPK-specific logic (no origin rate area) + contract_id := get_contract_id(shipment.requested_pickup_date); + d_rate_area_id := get_rate_area_id(shipment.destination_address_id, service_item.re_service_id, contract_id); + escalated_price := calculate_escalated_price(NULL, d_rate_area_id, service_item.re_service_id, contract_id, service_code, shipment.requested_pickup_date); + + IF shipment.prime_estimated_weight IS NOT NULL THEN + -- multiply by 110% of estimated weight + estimated_price := ROUND((escalated_price * (shipment.prime_estimated_weight * 1.1) / 100), 2) * 100; + RAISE NOTICE ''%: Received estimated price of % (% * (% * 1.1) / 100)) cents'', service_code, estimated_price, escalated_price, shipment.prime_estimated_weight; + -- update the pricing_estimate value in mto_service_items + UPDATE mto_service_items + SET pricing_estimate = estimated_price + WHERE id = service_item.id; + END IF; + + WHEN service_code IN (''POEFSC'', ''PODFSC'') THEN + distance = mileage; + + -- getting FSC multiplier from re_fsc_multipliers + estimated_fsc_multiplier := get_fsc_multiplier(shipment.prime_estimated_weight); + + fuel_price := get_fuel_price(shipment.requested_pickup_date); + + price_difference := calculate_price_difference(fuel_price); + + IF estimated_fsc_multiplier IS NOT NULL AND distance IS NOT NULL THEN + cents_above_baseline := distance * estimated_fsc_multiplier; + RAISE NOTICE ''Distance: % * FSC Multipler: % = $% cents above baseline of $2.50'', distance, estimated_fsc_multiplier, cents_above_baseline; + RAISE NOTICE ''The fuel price is % above the baseline (% - 250000 baseline)'', price_difference, fuel_price; + estimated_price := ROUND((cents_above_baseline * price_difference) * 100); + RAISE NOTICE ''Received estimated price of % cents for service_code: %.'', estimated_price, service_code; + + -- update the pricing_estimate value in mto_service_items + UPDATE mto_service_items + SET pricing_estimate = estimated_price + WHERE id = service_item.id; + END IF; + END CASE; + END LOOP; +END; +' +LANGUAGE plpgsql; \ No newline at end of file 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/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 222b9519685..5fb79cde889 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,5 @@ { "dependencies": { - "canvas": "^2.11.2", "@fortawesome/fontawesome-svg-core": "^6.5.2", "@fortawesome/free-regular-svg-icons": "^6.4.0", "@fortawesome/free-solid-svg-icons": "^6.4.0", @@ -15,6 +14,7 @@ "@trussworks/react-uswds": "3.2.0", "axe-playwright": "^1.2.3", "bytes": "^3.1.2", + "canvas": "^2.11.2", "classnames": "^2.5.1", "connected-react-router": "^6.9.3", "core-js": "^3.38.1", @@ -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", @@ -176,7 +176,7 @@ "react-select-event": "^5.5.1", "react-test-renderer": "^17.0.1", "source-map-explorer": "^2.5.3", - "storybook": "^7.6.7", + "storybook": "^7.6.20", "stream-http": "^3.2.0", "typescript": "^5.1.6" }, diff --git a/pkg/assets/sql_scripts/move_history_fetcher.sql b/pkg/assets/sql_scripts/move_history_fetcher.sql index 28f88eab583..fc293e20377 100644 --- a/pkg/assets/sql_scripts/move_history_fetcher.sql +++ b/pkg/assets/sql_scripts/move_history_fetcher.sql @@ -52,14 +52,18 @@ WITH move AS ( 'closeout_office_name', (SELECT transportation_offices.name FROM transportation_offices WHERE transportation_offices.id = uuid(c.closeout_office_id)), 'counseling_office_name', - (SELECT transportation_offices.name FROM transportation_offices WHERE transportation_offices.id = uuid(c.counseling_transportation_office_id)) + (SELECT transportation_offices.name FROM transportation_offices WHERE transportation_offices.id = uuid(c.counseling_transportation_office_id)), + 'assigned_office_user_first_name', + (SELECT office_users.first_name FROM office_users WHERE office_users.id IN (uuid(c.sc_assigned_id), uuid(c.too_assigned_id), uuid(c.tio_assigned_id))), + 'assigned_office_user_last_name', + (SELECT office_users.last_name FROM office_users WHERE office_users.id IN (uuid(c.sc_assigned_id), uuid(c.too_assigned_id), uuid(c.tio_assigned_id))) )) )::TEXT AS context, NULL AS context_id FROM audit_history JOIN move ON audit_history.object_id = move.id - JOIN jsonb_to_record(audit_history.changed_data) as c(closeout_office_id TEXT, counseling_transportation_office_id TEXT) on TRUE + JOIN jsonb_to_record(audit_history.changed_data) as c(closeout_office_id TEXT, counseling_transportation_office_id TEXT, sc_assigned_id TEXT, too_assigned_id TEXT, tio_assigned_id TEXT) ON TRUE WHERE audit_history.table_name = 'moves' -- Remove log for when shipment_seq_num updates AND NOT (audit_history.event_name = NULL AND audit_history.changed_data::TEXT LIKE '%shipment_seq_num%' AND LENGTH(audit_history.changed_data::TEXT) < 25) @@ -212,7 +216,8 @@ WITH move AS ( 'shipment_id', move_shipments.id::TEXT, 'shipment_id_abbr', move_shipments.shipment_id_abbr, 'shipment_type', move_shipments.shipment_type, - 'shipment_locator', move_shipments.shipment_locator + 'shipment_locator', move_shipments.shipment_locator, + 'rejection_reason', payment_service_items.rejection_reason ) )::TEXT AS context, payment_requests.id AS id, @@ -239,6 +244,42 @@ WITH move AS ( JOIN move_payment_requests ON move_payment_requests.id = audit_history.object_id WHERE audit_history.table_name = 'payment_requests' ), + move_payment_service_items AS ( + SELECT + jsonb_agg(jsonb_build_object( + 'name', re_services.name, + 'price', payment_service_items.price_cents::TEXT, + 'status', payment_service_items.status, + 'rejection_reason', payment_service_items.rejection_reason, + 'paid_at', payment_service_items.paid_at, + 'shipment_id', move_shipments.id::TEXT, + 'shipment_id_abbr', move_shipments.shipment_id_abbr, + 'shipment_type', move_shipments.shipment_type, + 'shipment_locator', move_shipments.shipment_locator + ) + )::TEXT AS context, + payment_service_items.id AS id + FROM + payment_requests + JOIN payment_service_items ON payment_service_items.payment_request_id = payment_requests.id + JOIN move_service_items ON move_service_items.id = payment_service_items.mto_service_item_id + LEFT JOIN move_shipments ON move_shipments.id = move_service_items.mto_shipment_id + JOIN re_services ON move_service_items.re_service_id = re_services.id + WHERE + payment_requests.move_id = (SELECT move.id FROM move) + GROUP BY + payment_service_items.id + ), + payment_service_items_logs AS ( + SELECT DISTINCT + audit_history.*, + context AS context, + NULL AS context_id + FROM + audit_history + JOIN move_payment_service_items ON move_payment_service_items.id = audit_history.object_id + WHERE audit_history.table_name = 'payment_service_items' + ), move_proof_of_service_docs AS ( SELECT proof_of_service_docs.*, @@ -717,6 +758,11 @@ WITH move AS ( FROM payment_requests_logs UNION + SELECT + * + FROM + payment_service_items_logs + UNION SELECT * FROM diff --git a/pkg/factory/address_factory.go b/pkg/factory/address_factory.go index 27d92999d00..ad4ce46507f 100644 --- a/pkg/factory/address_factory.go +++ b/pkg/factory/address_factory.go @@ -201,3 +201,75 @@ 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), + }, + }, + } +} diff --git a/pkg/factory/admin_user_factory_test.go b/pkg/factory/admin_user_factory_test.go index cea44d54de2..91583ef4951 100644 --- a/pkg/factory/admin_user_factory_test.go +++ b/pkg/factory/admin_user_factory_test.go @@ -81,15 +81,9 @@ func (suite *FactorySuite) TestBuildAdminUserExtra() { suite.Run("Successful creation of TIO Admin User", func() { - // Create the TIO Role tioRole := roles.Role{ - ID: uuid.Must(uuid.NewV4()), RoleType: roles.RoleTypeTIO, - RoleName: "Task Invoicing Officer", } - verrs, err := suite.DB().ValidateAndCreate(&tioRole) - suite.NoError(err) - suite.False(verrs.HasAny()) // FUNCTION UNDER TEST adminUser := BuildAdminUser(suite.DB(), []Customization{ @@ -211,3 +205,72 @@ func (suite *FactorySuite) TestBuildAdminUserExtra() { suite.Equal(adminUser.Email, adminUser.User.OktaEmail) }) } +func (suite *FactorySuite) TestSuperBuildAdminUser() { + defaultEmail := "first.last@okta.mil" + suite.Run("Successful creation of super admin user", func() { + // Under test: BuildSuperAdminUser + // Mocked: None + // Set up: Create a User with no customizations or traits + // Expected outcome:User should be created with default values + defaultAdmin := models.AdminUser{ + FirstName: "Leo", + LastName: "Spaceman", + Email: "super_leo_spaceman_admin@example.com", + Role: "SYSTEM_ADMIN", + Super: true, + } + + adminUser := BuildSuperAdminUser(suite.DB(), nil, nil) + suite.Equal(defaultEmail, adminUser.User.OktaEmail) + suite.Equal(defaultAdmin.FirstName, adminUser.FirstName) + suite.Equal(defaultAdmin.LastName, adminUser.LastName) + suite.Equal(defaultAdmin.Email, adminUser.Email) + suite.Equal(defaultAdmin.Role, adminUser.Role) + suite.Equal(defaultAdmin.Super, adminUser.Super) + suite.True(adminUser.User.Active) + }) + + suite.Run("Successful creation of a super adminUser with trait", func() { + // Under test: BuildSuperAdminUser + // Mocked: None + // Set up: Create a User but pass in a trait that sets + // both the adminuser and user email to a random + // value, as adminuser has uniqueness constraints + // Expected outcome:AdminUser should have the same random email as User + + adminUser := BuildSuperAdminUser(suite.DB(), nil, []Trait{ + GetTraitAdminUserEmail, + }) + suite.Equal(adminUser.Email, adminUser.User.OktaEmail) + suite.True(adminUser.User.Active) + }) + + suite.Run("Successful creation of user with customization", func() { + // Under test: BuildSuperAdminUser + // Set up: Create an adminUser and pass in specified emails + // Expected outcome:adminUser and User should be created with specified emails + customAdmin := models.AdminUser{ + FirstName: "Leo", + LastName: "Spaceman", + Email: "super_leo_spaceman_admin@example.com", + Role: "SYSTEM_ADMIN", + Super: true, + Active: true, + } + customEmail := "leospaceman456@example.com" + adminUser := BuildSuperAdminUser(suite.DB(), []Customization{ + { + Model: models.User{ + OktaEmail: customEmail, + }, + }, + {Model: customAdmin}, + }, nil) + suite.Equal(customEmail, adminUser.User.OktaEmail) + suite.Equal(customAdmin.Email, adminUser.Email) + suite.Equal(customAdmin.FirstName, adminUser.FirstName) + suite.Equal(customAdmin.LastName, adminUser.LastName) + suite.Equal(customAdmin.Role, adminUser.Role) + suite.True(adminUser.User.Active) + }) +} diff --git a/pkg/factory/mto_shipment_factory.go b/pkg/factory/mto_shipment_factory.go index 0dc95a166e6..c017a2f54cd 100644 --- a/pkg/factory/mto_shipment_factory.go +++ b/pkg/factory/mto_shipment_factory.go @@ -111,7 +111,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)) } @@ -208,8 +212,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 + } } } @@ -230,7 +238,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/factory/office_user_factory_test.go b/pkg/factory/office_user_factory_test.go index 31540b49662..b5314dda74c 100644 --- a/pkg/factory/office_user_factory_test.go +++ b/pkg/factory/office_user_factory_test.go @@ -237,10 +237,6 @@ func (suite *FactorySuite) TestBuildOfficeUserExtra() { // Set up: Use BuildOfficeUserWithRoles helper function to create // an OfficeUser with multiple roles // Expected outcome: officeUser and User should be returned as expected - precountRoles, err := suite.DB().Count(&roles.Role{}) - suite.NoError(err) - precountUsersRoles, err := suite.DB().Count(&models.UsersRoles{}) - suite.NoError(err) officeUser := BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO, roles.RoleTypeTIO}) @@ -249,14 +245,6 @@ func (suite *FactorySuite) TestBuildOfficeUserExtra() { suite.True(hasRole) _, hasRole = officeUser.User.Roles.GetRole(roles.RoleTypeTIO) suite.True(hasRole) - // Check that only 2 new roles were created - countRoles, err := suite.DB().Count(&roles.Role{}) - suite.NoError(err) - suite.Equal(precountRoles+2, countRoles) - // Check that only 2 new usersRoles were created - countUsersRoles, err := suite.DB().Count(&models.UsersRoles{}) - suite.NoError(err) - suite.Equal(precountUsersRoles+2, countUsersRoles) }) suite.Run("Successful creation of Stubbed OfficeUser using BuildOfficeUserWithRoles", func() { diff --git a/pkg/factory/ppm_shipment_factory.go b/pkg/factory/ppm_shipment_factory.go index 0306aebe547..cc87032285e 100644 --- a/pkg/factory/ppm_shipment_factory.go +++ b/pkg/factory/ppm_shipment_factory.go @@ -87,42 +87,60 @@ func buildPPMShipmentWithBuildType(db *pop.Connection, customs []Customization, ppmShipment.W2Address = &w2AddressResult } - oldDutyLocationAddress := ppmShipment.Shipment.MoveTaskOrder.Orders.OriginDutyLocation.Address - pickupAddress := BuildAddress(db, []Customization{ - { - Model: models.Address{ - StreetAddress1: "987 New Street", - City: oldDutyLocationAddress.City, - State: oldDutyLocationAddress.State, - PostalCode: oldDutyLocationAddress.PostalCode, + pickupAddressResult := findValidCustomization(customs, Addresses.PickupAddress) + if pickupAddressResult != nil { + pickupAddressResultCustoms := convertCustomizationInList(customs, Addresses.PickupAddress, Address) + + pickupAddressResult := BuildAddress(db, pickupAddressResultCustoms, traits) + ppmShipment.PickupAddressID = &pickupAddressResult.ID + ppmShipment.PickupAddress = &pickupAddressResult + } else { + oldDutyLocationAddress := ppmShipment.Shipment.MoveTaskOrder.Orders.OriginDutyLocation.Address + pickupAddress := BuildAddress(db, []Customization{ + { + Model: models.Address{ + StreetAddress1: "987 New Street", + City: oldDutyLocationAddress.City, + State: oldDutyLocationAddress.State, + PostalCode: oldDutyLocationAddress.PostalCode, + }, }, - }, - }, nil) - ppmShipment.PickupAddressID = &pickupAddress.ID - ppmShipment.PickupAddress = &pickupAddress + }, nil) + ppmShipment.PickupAddressID = &pickupAddress.ID + ppmShipment.PickupAddress = &pickupAddress + } - newDutyLocationAddress := ppmShipment.Shipment.MoveTaskOrder.Orders.NewDutyLocation.Address - destinationAddress := BuildAddress(db, []Customization{ - { - Model: models.Address{ - StreetAddress1: "123 New Street", - City: newDutyLocationAddress.City, - State: newDutyLocationAddress.State, - PostalCode: newDutyLocationAddress.PostalCode, + deliveryAddressResult := findValidCustomization(customs, Addresses.DeliveryAddress) + if deliveryAddressResult != nil { + deliveryAddressResultCustoms := convertCustomizationInList(customs, Addresses.DeliveryAddress, Address) + + deliveryAddressResult := BuildAddress(db, deliveryAddressResultCustoms, traits) + ppmShipment.DestinationAddressID = &deliveryAddressResult.ID + ppmShipment.DestinationAddress = &deliveryAddressResult + } else { + newDutyLocationAddress := ppmShipment.Shipment.MoveTaskOrder.Orders.NewDutyLocation.Address + destinationAddress := BuildAddress(db, []Customization{ + { + Model: models.Address{ + StreetAddress1: "123 New Street", + City: newDutyLocationAddress.City, + State: newDutyLocationAddress.State, + PostalCode: newDutyLocationAddress.PostalCode, + }, }, - }, - }, nil) - ppmShipment.DestinationAddressID = &destinationAddress.ID - ppmShipment.DestinationAddress = &destinationAddress + }, nil) + ppmShipment.DestinationAddressID = &destinationAddress.ID + ppmShipment.DestinationAddress = &destinationAddress + } if buildType == ppmBuildFullAddress { secondaryPickupAddress := BuildAddress(db, []Customization{ { Model: models.Address{ StreetAddress1: "123 Main Street", - City: pickupAddress.City, - State: pickupAddress.State, - PostalCode: pickupAddress.PostalCode, + City: ppmShipment.PickupAddress.City, + State: ppmShipment.PickupAddress.State, + PostalCode: ppmShipment.PickupAddress.PostalCode, }, }, }, nil) @@ -130,9 +148,9 @@ func buildPPMShipmentWithBuildType(db *pop.Connection, customs []Customization, { Model: models.Address{ StreetAddress1: "1234 Main Street", - City: destinationAddress.City, - State: destinationAddress.State, - PostalCode: destinationAddress.PostalCode, + City: ppmShipment.DestinationAddress.City, + State: ppmShipment.DestinationAddress.State, + PostalCode: ppmShipment.DestinationAddress.PostalCode, }, }, }, nil) @@ -140,9 +158,9 @@ func buildPPMShipmentWithBuildType(db *pop.Connection, customs []Customization, { Model: models.Address{ StreetAddress1: "123 Third Street", - City: pickupAddress.City, - State: pickupAddress.State, - PostalCode: pickupAddress.PostalCode, + City: ppmShipment.PickupAddress.City, + State: ppmShipment.PickupAddress.State, + PostalCode: ppmShipment.PickupAddress.PostalCode, }, }, }, nil) @@ -150,9 +168,9 @@ func buildPPMShipmentWithBuildType(db *pop.Connection, customs []Customization, { Model: models.Address{ StreetAddress1: "1234 Third Street", - City: destinationAddress.City, - State: destinationAddress.State, - PostalCode: destinationAddress.PostalCode, + City: ppmShipment.DestinationAddress.City, + State: ppmShipment.DestinationAddress.State, + PostalCode: ppmShipment.DestinationAddress.PostalCode, }, }, }, nil) diff --git a/pkg/factory/role_factory_test.go b/pkg/factory/role_factory_test.go index 72bee0e5d10..ab542561848 100644 --- a/pkg/factory/role_factory_test.go +++ b/pkg/factory/role_factory_test.go @@ -1,44 +1,11 @@ package factory import ( - "github.com/gofrs/uuid" - "github.com/transcom/mymove/pkg/models/roles" ) func (suite *FactorySuite) TestBuildRole() { - suite.Run("Successful creation of default role (customer)", func() { - // Under test: BuildRole - // Mocked: None - // Set up: Create a Role with no customizations or traits - // Expected outcome:Role should be created with default values - defaultRoleType := roles.RoleTypeCustomer - defaultRoleName := roles.RoleName("Customer") - role := BuildRole(suite.DB(), nil, nil) - suite.Equal(defaultRoleType, role.RoleType) - suite.Equal(defaultRoleName, role.RoleName) - }) - - suite.Run("Successful creation of role with customization", func() { - // Under test: BuildRole - // Set up: Create a Role with a customized email and no trait - // Expected outcome:Role should be created with email and inactive status - customRoleName := roles.RoleName("custom role name") - customID := uuid.Must(uuid.NewV4()) - role := BuildRole(suite.DB(), []Customization{ - { - Model: roles.Role{ - ID: customID, - RoleName: customRoleName, - }, - }, - }, nil) - suite.Equal(customID, role.ID) - suite.Equal(customRoleName, role.RoleName) - suite.Equal(roles.RoleTypeCustomer, role.RoleType) - }) - suite.Run("Successful creation of stubbed role", func() { // Under test: BuildRole // Set up: Create a customized role, but don't pass in a db @@ -67,123 +34,14 @@ func (suite *FactorySuite) TestBuildRole() { } -func (suite *FactorySuite) TestBuildRoleTraits() { - suite.Run("Successful creation of role with customer trait", func() { - // Under test: BuildRole - // Set up: Create a Role with a trait (GetTraitCustomerRole) - // Expected outcome:Role should be created with TIO RoleType and RoleName - - role := BuildRole(suite.DB(), nil, - []Trait{ - GetTraitCustomerRole, - }) - suite.Equal(roles.RoleName("Customer"), role.RoleName) - suite.Equal(roles.RoleTypeCustomer, role.RoleType) - }) - - suite.Run("Successful creation of role with Services Counselor trait", func() { - // Under test: BuildRole - // Set up: Create a Role with a trait (GetTraitServicesCounselorRole) - // Expected outcome:Role should be created with SC RoleType and RoleName - - role := BuildRole(suite.DB(), nil, - []Trait{ - GetTraitServicesCounselorRole, - }) - suite.Equal(roles.RoleName("Services Counselor"), role.RoleName) - suite.Equal(roles.RoleTypeServicesCounselor, role.RoleType) - }) - - suite.Run("Successful creation of role with TIO trait", func() { - // Under test: BuildRole - // Set up: Create a Role with a trait (GetTraitTIORole) - // Expected outcome:Role should be created with TIO RoleType and RoleName - - role := BuildRole(suite.DB(), nil, - []Trait{ - GetTraitTIORole, - }) - suite.Equal(roles.RoleName("Task Invoicing Officer"), role.RoleName) - suite.Equal(roles.RoleTypeTIO, role.RoleType) - }) - - suite.Run("Successful creation of role with TOO trait", func() { - // Under test: BuildRole - // Set up: Create a Role with a trait (GetTraitTOORole) - // Expected outcome:Role should be created with TIO RoleType and RoleName - - role := BuildRole(suite.DB(), nil, - []Trait{ - GetTraitTOORole, - }) - suite.Equal(roles.RoleName("Task Ordering Officer"), role.RoleName) - suite.Equal(roles.RoleTypeTOO, role.RoleType) - }) - - suite.Run("Successful creation of role with HQ trait", func() { - // Under test: BuildRole - // Set up: Create a Role with a trait (GetTraitHQRole) - // Expected outcome:Role should be created with HQ RoleType and RoleName - - role := BuildRole(suite.DB(), nil, - []Trait{ - GetTraitHQRole, - }) - suite.Equal(roles.RoleName("Headquarters"), role.RoleName) - suite.Equal(roles.RoleTypeHQ, role.RoleType) - }) - - suite.Run("Successful creation of role with Qae trait", func() { - // Under test: BuildRole - // Set up: Create a Role with a trait (GetTraitQaeRole) - // Expected outcome:Role should be created with TIO RoleType and RoleName - - role := BuildRole(suite.DB(), nil, - []Trait{ - GetTraitQaeRole, - }) - suite.Equal(roles.RoleName("Quality Assurance Evaluator"), role.RoleName) - suite.Equal(roles.RoleTypeQae, role.RoleType) - }) - - suite.Run("Successful creation of role with Contracting Officer trait", func() { - // Under test: BuildRole - // Set up: Create a Role with a trait (GetTraitContractingOfficerRole) - // Expected outcome:Role should be created with TIO RoleType and RoleName - - role := BuildRole(suite.DB(), nil, - []Trait{ - GetTraitContractingOfficerRole, - }) - suite.Equal(roles.RoleName("Contracting Officer"), role.RoleName) - suite.Equal(roles.RoleTypeContractingOfficer, role.RoleType) - }) - - suite.Run("Successful creation of role with GSR trait", func() { - // Under test: BuildRole - // Set up: Create a Role with a trait (GetTraitGSRRole) - // Expected outcome:Role should be created with GSR RoleType and RoleName - - role := BuildRole(suite.DB(), nil, - []Trait{ - GetTraitGSRRole, - }) - suite.Equal(roles.RoleName("Government Surveillance Representative"), role.RoleName) - suite.Equal(roles.RoleTypeGSR, role.RoleType) - }) -} - func (suite *FactorySuite) TestBuildRoleHelpers() { suite.Run("FetchOrBuildRoleByRoleType - role exists", func() { // Under test: FetchOrBuildRoleByRoleType - // Set up: Create a role, then call FetchOrBuildRoleByRoleType + // Set up: Tr to create a role, then call FetchOrBuildRoleByRoleType // Expected outcome:Existing Role should be returned // No new role should be created in database - ServicesCounselorRole := BuildRole(suite.DB(), nil, - []Trait{ - GetTraitServicesCounselorRole, - }) + ServicesCounselorRole := FetchOrBuildRoleByRoleType(suite.DB(), roles.RoleTypeServicesCounselor) precount, err := suite.DB().Count(&roles.Role{}) suite.NoError(err) @@ -200,30 +58,6 @@ func (suite *FactorySuite) TestBuildRoleHelpers() { suite.Equal(precount, count) }) - suite.Run("FetchOrBuildRoleByRoleType - role does not exists", func() { - // Under test: FetchOrBuildRoleByRoleType - // Set up: Call FetchOrBuildRoleByRoleType with a non-existent role - // Expected outcome:new role is created - - precount, err := suite.DB().Count(&roles.Role{}) - suite.NoError(err) - - ServicesCounselorRole := roles.Role{ - RoleType: roles.RoleTypeServicesCounselor, - RoleName: "Services_counselor", - } - role := FetchOrBuildRoleByRoleType(suite.DB(), ServicesCounselorRole.RoleType) - suite.NoError(err) - - suite.Equal(ServicesCounselorRole.RoleName, role.RoleName) - suite.Equal(ServicesCounselorRole.RoleType, role.RoleType) - - // Count how many roles are in the DB, new role should have been created. - count, err := suite.DB().Count(&roles.Role{}) - suite.NoError(err) - suite.Equal(precount+1, count) - }) - suite.Run("FetchOrBuildRoleByRoleType - stubbed role", func() { // Under test: FetchOrBuildRoleByRoleType // Set up: Call FetchOrBuildRoleByRoleType without a db diff --git a/pkg/factory/user_factory_test.go b/pkg/factory/user_factory_test.go index dbc86670475..0108a3a7af4 100644 --- a/pkg/factory/user_factory_test.go +++ b/pkg/factory/user_factory_test.go @@ -125,9 +125,6 @@ func (suite *FactorySuite) TestBuildDefaultUser() { precountRole, err := suite.DB().Count(&roles.Role{}) suite.NoError(err) - precountUsersRoles, err := suite.DB().Count(&models.UsersRoles{}) - suite.NoError(err) - tioRole := roles.Role{ RoleType: roles.RoleTypeTIO, } @@ -148,12 +145,7 @@ func (suite *FactorySuite) TestBuildDefaultUser() { // Count how many roles are in the DB, new role should have been created. count, err := suite.DB().Count(&roles.Role{}) suite.NoError(err) - suite.Equal(precountRole+1, count) - - // Count how many UsersRoles are in the DB, new UsersRoles should have been created. - count, err = suite.DB().Count(&roles.Role{}) - suite.NoError(err) - suite.Equal(precountUsersRoles+1, count) + suite.Equal(precountRole, count) }) } diff --git a/pkg/gen/adminapi/adminoperations/mymove_api.go b/pkg/gen/adminapi/adminoperations/mymove_api.go index e2ed2892e29..30c51883742 100644 --- a/pkg/gen/adminapi/adminoperations/mymove_api.go +++ b/pkg/gen/adminapi/adminoperations/mymove_api.go @@ -70,6 +70,9 @@ func NewMymoveAPI(spec *loads.Document) *MymoveAPI { WebhookSubscriptionsCreateWebhookSubscriptionHandler: webhook_subscriptions.CreateWebhookSubscriptionHandlerFunc(func(params webhook_subscriptions.CreateWebhookSubscriptionParams) middleware.Responder { return middleware.NotImplemented("operation webhook_subscriptions.CreateWebhookSubscription has not yet been implemented") }), + OfficeUsersDeleteOfficeUserHandler: office_users.DeleteOfficeUserHandlerFunc(func(params office_users.DeleteOfficeUserParams) middleware.Responder { + return middleware.NotImplemented("operation office_users.DeleteOfficeUser has not yet been implemented") + }), AdminUsersGetAdminUserHandler: admin_users.GetAdminUserHandlerFunc(func(params admin_users.GetAdminUserParams) middleware.Responder { return middleware.NotImplemented("operation admin_users.GetAdminUser has not yet been implemented") }), @@ -214,6 +217,8 @@ type MymoveAPI struct { OfficeUsersCreateOfficeUserHandler office_users.CreateOfficeUserHandler // WebhookSubscriptionsCreateWebhookSubscriptionHandler sets the operation handler for the create webhook subscription operation WebhookSubscriptionsCreateWebhookSubscriptionHandler webhook_subscriptions.CreateWebhookSubscriptionHandler + // OfficeUsersDeleteOfficeUserHandler sets the operation handler for the delete office user operation + OfficeUsersDeleteOfficeUserHandler office_users.DeleteOfficeUserHandler // AdminUsersGetAdminUserHandler sets the operation handler for the get admin user operation AdminUsersGetAdminUserHandler admin_users.GetAdminUserHandler // ClientCertificatesGetClientCertificateHandler sets the operation handler for the get client certificate operation @@ -367,6 +372,9 @@ func (o *MymoveAPI) Validate() error { if o.WebhookSubscriptionsCreateWebhookSubscriptionHandler == nil { unregistered = append(unregistered, "webhook_subscriptions.CreateWebhookSubscriptionHandler") } + if o.OfficeUsersDeleteOfficeUserHandler == nil { + unregistered = append(unregistered, "office_users.DeleteOfficeUserHandler") + } if o.AdminUsersGetAdminUserHandler == nil { unregistered = append(unregistered, "admin_users.GetAdminUserHandler") } @@ -567,6 +575,10 @@ func (o *MymoveAPI) initHandlerCache() { o.handlers["POST"] = make(map[string]http.Handler) } o.handlers["POST"]["/webhook-subscriptions"] = webhook_subscriptions.NewCreateWebhookSubscription(o.context, o.WebhookSubscriptionsCreateWebhookSubscriptionHandler) + if o.handlers["DELETE"] == nil { + o.handlers["DELETE"] = make(map[string]http.Handler) + } + o.handlers["DELETE"]["/office-users/{officeUserId}"] = office_users.NewDeleteOfficeUser(o.context, o.OfficeUsersDeleteOfficeUserHandler) if o.handlers["GET"] == nil { o.handlers["GET"] = make(map[string]http.Handler) } diff --git a/pkg/gen/adminapi/adminoperations/office_users/delete_office_user.go b/pkg/gen/adminapi/adminoperations/office_users/delete_office_user.go new file mode 100644 index 00000000000..2d4920bffde --- /dev/null +++ b/pkg/gen/adminapi/adminoperations/office_users/delete_office_user.go @@ -0,0 +1,58 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package office_users + +// 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" +) + +// DeleteOfficeUserHandlerFunc turns a function with the right signature into a delete office user handler +type DeleteOfficeUserHandlerFunc func(DeleteOfficeUserParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn DeleteOfficeUserHandlerFunc) Handle(params DeleteOfficeUserParams) middleware.Responder { + return fn(params) +} + +// DeleteOfficeUserHandler interface for that can handle valid delete office user params +type DeleteOfficeUserHandler interface { + Handle(DeleteOfficeUserParams) middleware.Responder +} + +// NewDeleteOfficeUser creates a new http.Handler for the delete office user operation +func NewDeleteOfficeUser(ctx *middleware.Context, handler DeleteOfficeUserHandler) *DeleteOfficeUser { + return &DeleteOfficeUser{Context: ctx, Handler: handler} +} + +/* + DeleteOfficeUser swagger:route DELETE /office-users/{officeUserId} Office users deleteOfficeUser + +# Deletes an Office User + +Deletes a single office user in any status. This endpoint is used in the Admin UI that will allow the admin user to delete an office user. +*/ +type DeleteOfficeUser struct { + Context *middleware.Context + Handler DeleteOfficeUserHandler +} + +func (o *DeleteOfficeUser) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewDeleteOfficeUserParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/pkg/gen/adminapi/adminoperations/office_users/delete_office_user_parameters.go b/pkg/gen/adminapi/adminoperations/office_users/delete_office_user_parameters.go new file mode 100644 index 00000000000..72576103a4c --- /dev/null +++ b/pkg/gen/adminapi/adminoperations/office_users/delete_office_user_parameters.go @@ -0,0 +1,91 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package office_users + +// 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" +) + +// NewDeleteOfficeUserParams creates a new DeleteOfficeUserParams object +// +// There are no default values defined in the spec. +func NewDeleteOfficeUserParams() DeleteOfficeUserParams { + + return DeleteOfficeUserParams{} +} + +// DeleteOfficeUserParams contains all the bound params for the delete office user operation +// typically these are obtained from a http.Request +// +// swagger:parameters deleteOfficeUser +type DeleteOfficeUserParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /* + Required: true + In: path + */ + OfficeUserID 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 NewDeleteOfficeUserParams() beforehand. +func (o *DeleteOfficeUserParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + rOfficeUserID, rhkOfficeUserID, _ := route.Params.GetOK("officeUserId") + if err := o.bindOfficeUserID(rOfficeUserID, rhkOfficeUserID, route.Formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindOfficeUserID binds and validates parameter OfficeUserID from path. +func (o *DeleteOfficeUserParams) bindOfficeUserID(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("officeUserId", "path", "strfmt.UUID", raw) + } + o.OfficeUserID = *(value.(*strfmt.UUID)) + + if err := o.validateOfficeUserID(formats); err != nil { + return err + } + + return nil +} + +// validateOfficeUserID carries on validations for parameter OfficeUserID +func (o *DeleteOfficeUserParams) validateOfficeUserID(formats strfmt.Registry) error { + + if err := validate.FormatOf("officeUserId", "path", "uuid", o.OfficeUserID.String(), formats); err != nil { + return err + } + return nil +} diff --git a/pkg/gen/adminapi/adminoperations/office_users/delete_office_user_responses.go b/pkg/gen/adminapi/adminoperations/office_users/delete_office_user_responses.go new file mode 100644 index 00000000000..f180e378abc --- /dev/null +++ b/pkg/gen/adminapi/adminoperations/office_users/delete_office_user_responses.go @@ -0,0 +1,112 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package office_users + +// 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" +) + +// DeleteOfficeUserNoContentCode is the HTTP code returned for type DeleteOfficeUserNoContent +const DeleteOfficeUserNoContentCode int = 204 + +/* +DeleteOfficeUserNoContent deleted + +swagger:response deleteOfficeUserNoContent +*/ +type DeleteOfficeUserNoContent struct { +} + +// NewDeleteOfficeUserNoContent creates DeleteOfficeUserNoContent with default headers values +func NewDeleteOfficeUserNoContent() *DeleteOfficeUserNoContent { + + return &DeleteOfficeUserNoContent{} +} + +// WriteResponse to the client +func (o *DeleteOfficeUserNoContent) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses + + rw.WriteHeader(204) +} + +// DeleteOfficeUserUnauthorizedCode is the HTTP code returned for type DeleteOfficeUserUnauthorized +const DeleteOfficeUserUnauthorizedCode int = 401 + +/* +DeleteOfficeUserUnauthorized request requires user authentication + +swagger:response deleteOfficeUserUnauthorized +*/ +type DeleteOfficeUserUnauthorized struct { +} + +// NewDeleteOfficeUserUnauthorized creates DeleteOfficeUserUnauthorized with default headers values +func NewDeleteOfficeUserUnauthorized() *DeleteOfficeUserUnauthorized { + + return &DeleteOfficeUserUnauthorized{} +} + +// WriteResponse to the client +func (o *DeleteOfficeUserUnauthorized) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses + + rw.WriteHeader(401) +} + +// DeleteOfficeUserNotFoundCode is the HTTP code returned for type DeleteOfficeUserNotFound +const DeleteOfficeUserNotFoundCode int = 404 + +/* +DeleteOfficeUserNotFound Office User not found + +swagger:response deleteOfficeUserNotFound +*/ +type DeleteOfficeUserNotFound struct { +} + +// NewDeleteOfficeUserNotFound creates DeleteOfficeUserNotFound with default headers values +func NewDeleteOfficeUserNotFound() *DeleteOfficeUserNotFound { + + return &DeleteOfficeUserNotFound{} +} + +// WriteResponse to the client +func (o *DeleteOfficeUserNotFound) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses + + rw.WriteHeader(404) +} + +// DeleteOfficeUserInternalServerErrorCode is the HTTP code returned for type DeleteOfficeUserInternalServerError +const DeleteOfficeUserInternalServerErrorCode int = 500 + +/* +DeleteOfficeUserInternalServerError server error + +swagger:response deleteOfficeUserInternalServerError +*/ +type DeleteOfficeUserInternalServerError struct { +} + +// NewDeleteOfficeUserInternalServerError creates DeleteOfficeUserInternalServerError with default headers values +func NewDeleteOfficeUserInternalServerError() *DeleteOfficeUserInternalServerError { + + return &DeleteOfficeUserInternalServerError{} +} + +// WriteResponse to the client +func (o *DeleteOfficeUserInternalServerError) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses + + rw.WriteHeader(500) +} diff --git a/pkg/gen/adminapi/adminoperations/office_users/delete_office_user_urlbuilder.go b/pkg/gen/adminapi/adminoperations/office_users/delete_office_user_urlbuilder.go new file mode 100644 index 00000000000..9ccfb5f1a0e --- /dev/null +++ b/pkg/gen/adminapi/adminoperations/office_users/delete_office_user_urlbuilder.go @@ -0,0 +1,101 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package office_users + +// 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" +) + +// DeleteOfficeUserURL generates an URL for the delete office user operation +type DeleteOfficeUserURL struct { + OfficeUserID 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 *DeleteOfficeUserURL) WithBasePath(bp string) *DeleteOfficeUserURL { + 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 *DeleteOfficeUserURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *DeleteOfficeUserURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/office-users/{officeUserId}" + + officeUserID := o.OfficeUserID.String() + if officeUserID != "" { + _path = strings.Replace(_path, "{officeUserId}", officeUserID, -1) + } else { + return nil, errors.New("officeUserId is required on DeleteOfficeUserURL") + } + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/admin/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *DeleteOfficeUserURL) 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 *DeleteOfficeUserURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *DeleteOfficeUserURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on DeleteOfficeUserURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on DeleteOfficeUserURL") + } + + 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 *DeleteOfficeUserURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/pkg/gen/adminapi/configure_mymove.go b/pkg/gen/adminapi/configure_mymove.go index 1e8322fbfe9..7e67c48c936 100644 --- a/pkg/gen/adminapi/configure_mymove.go +++ b/pkg/gen/adminapi/configure_mymove.go @@ -72,6 +72,11 @@ func configureAPI(api *adminoperations.MymoveAPI) http.Handler { return middleware.NotImplemented("operation webhook_subscriptions.CreateWebhookSubscription has not yet been implemented") }) } + if api.OfficeUsersDeleteOfficeUserHandler == nil { + api.OfficeUsersDeleteOfficeUserHandler = office_users.DeleteOfficeUserHandlerFunc(func(params office_users.DeleteOfficeUserParams) middleware.Responder { + return middleware.NotImplemented("operation office_users.DeleteOfficeUser has not yet been implemented") + }) + } if api.AdminUsersGetAdminUserHandler == nil { api.AdminUsersGetAdminUserHandler = admin_users.GetAdminUserHandlerFunc(func(params admin_users.GetAdminUserParams) middleware.Responder { return middleware.NotImplemented("operation admin_users.GetAdminUser has not yet been implemented") diff --git a/pkg/gen/adminapi/embedded_spec.go b/pkg/gen/adminapi/embedded_spec.go index c92c3d50045..3433894752a 100644 --- a/pkg/gen/adminapi/embedded_spec.go +++ b/pkg/gen/adminapi/embedded_spec.go @@ -996,6 +996,40 @@ func init() { } } }, + "delete": { + "description": "Deletes a single office user in any status. This endpoint is used in the Admin UI that will allow the admin user to delete an office user.", + "produces": [ + "application/json" + ], + "tags": [ + "Office users" + ], + "summary": "Deletes an Office User", + "operationId": "deleteOfficeUser", + "parameters": [ + { + "type": "string", + "format": "uuid", + "name": "officeUserId", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "deleted" + }, + "401": { + "description": "request requires user authentication" + }, + "404": { + "description": "Office User not found" + }, + "500": { + "description": "server error" + } + } + }, "patch": { "description": "This endpoint updates a single Office User by ID. Do not use this\nendpoint directly as it is meant to be used with the Admin UI exclusively.\n", "consumes": [ @@ -4653,6 +4687,40 @@ func init() { } } }, + "delete": { + "description": "Deletes a single office user in any status. This endpoint is used in the Admin UI that will allow the admin user to delete an office user.", + "produces": [ + "application/json" + ], + "tags": [ + "Office users" + ], + "summary": "Deletes an Office User", + "operationId": "deleteOfficeUser", + "parameters": [ + { + "type": "string", + "format": "uuid", + "name": "officeUserId", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "deleted" + }, + "401": { + "description": "request requires user authentication" + }, + "404": { + "description": "Office User not found" + }, + "500": { + "description": "server error" + } + } + }, "patch": { "description": "This endpoint updates a single Office User by ID. Do not use this\nendpoint directly as it is meant to be used with the Admin UI exclusively.\n", "consumes": [ diff --git a/pkg/gen/ghcapi/configure_mymove.go b/pkg/gen/ghcapi/configure_mymove.go index 32eb5174c09..dca67dece74 100644 --- a/pkg/gen/ghcapi/configure_mymove.go +++ b/pkg/gen/ghcapi/configure_mymove.go @@ -472,6 +472,11 @@ func configureAPI(api *ghcoperations.MymoveAPI) http.Handler { return middleware.NotImplemented("operation ppm.ShowAOAPacket has not yet been implemented") }) } + if api.TransportationOfficeShowCounselingOfficesHandler == nil { + api.TransportationOfficeShowCounselingOfficesHandler = transportation_office.ShowCounselingOfficesHandlerFunc(func(params transportation_office.ShowCounselingOfficesParams) middleware.Responder { + return middleware.NotImplemented("operation transportation_office.ShowCounselingOffices has not yet been implemented") + }) + } if api.PpmShowPaymentPacketHandler == nil { api.PpmShowPaymentPacketHandler = ppm.ShowPaymentPacketHandlerFunc(func(params ppm.ShowPaymentPacketParams) middleware.Responder { return middleware.NotImplemented("operation ppm.ShowPaymentPacket has not yet been implemented") diff --git a/pkg/gen/ghcapi/embedded_spec.go b/pkg/gen/ghcapi/embedded_spec.go index 7f8a82f24d8..96bddbddbc9 100644 --- a/pkg/gen/ghcapi/embedded_spec.go +++ b/pkg/gen/ghcapi/embedded_spec.go @@ -4617,6 +4617,12 @@ func init() { "description": "Used to illustrate which user is assigned to this payment request.\n", "name": "assignedTo", "in": "query" + }, + { + "type": "string", + "description": "user's actively logged in role", + "name": "activeRole", + "in": "query" } ], "responses": { @@ -4820,6 +4826,12 @@ func init() { "description": "filters using a counselingOffice name of the move", "name": "counselingOffice", "in": "query" + }, + { + "type": "string", + "description": "user's actively logged in role", + "name": "activeRole", + "in": "query" } ], "responses": { @@ -4974,6 +4986,12 @@ func init() { "description": "Used to return a queue for a GBLOC other than the default of the current user. Requires the HQ role or a secondary transportation office assignment. The parameter is ignored if the requesting user does not have the necessary role or assignment.\n", "name": "viewAsGBLOC", "in": "query" + }, + { + "type": "string", + "description": "user's actively logged in role", + "name": "activeRole", + "in": "query" } ], "responses": { @@ -5775,8 +5793,7 @@ func init() { "application/json" ], "tags": [ - "shipment", - "shipment_address_updates" + "shipment" ], "summary": "Allows TOO to review a shipment address update", "operationId": "reviewShipmentAddressUpdate", @@ -6275,6 +6292,57 @@ func init() { } } }, + "/transportation_offices/{dutyLocationId}/counseling_offices/{serviceMemberId}": { + "get": { + "description": "Returns the counseling locations matching the GBLOC from the selected duty location", + "produces": [ + "application/json" + ], + "tags": [ + "transportationOffice" + ], + "summary": "Returns the counseling locations in the GBLOC matching the duty location", + "operationId": "showCounselingOffices", + "parameters": [ + { + "type": "string", + "format": "uuid", + "description": "UUID of the duty location", + "name": "dutyLocationId", + "in": "path", + "required": true + }, + { + "type": "string", + "format": "uuid", + "description": "UUID of the service member, some counseling offices are branch specific", + "name": "serviceMemberId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Successfully retrieved counseling offices", + "schema": { + "$ref": "#/definitions/CounselingOffices" + } + }, + "400": { + "$ref": "#/responses/InvalidRequest" + }, + "403": { + "$ref": "#/responses/PermissionDenied" + }, + "404": { + "$ref": "#/responses/NotFound" + }, + "500": { + "description": "internal server error" + } + } + } + }, "/uploads": { "post": { "description": "Uploads represent a single digital file, such as a JPEG or PDF. Currently, office application uploads are only for Services Counselors to upload files for orders, but this may be expanded in the future.", @@ -6809,7 +6877,8 @@ func init() { "example": "c56a4180-65aa-42ec-a945-5fd21dec0538" }, "workload": { - "type": "integer" + "type": "integer", + "x-omitempty": false } } }, @@ -6991,6 +7060,30 @@ func init() { } } }, + "CounselingOffice": { + "type": "object", + "required": [ + "id", + "name" + ], + "properties": { + "id": { + "type": "string", + "format": "uuid", + "example": "c56a4180-65aa-42ec-a945-5fd21dec0538" + }, + "name": { + "type": "string", + "example": "Fort Bragg North Station" + } + } + }, + "CounselingOffices": { + "type": "array", + "items": { + "$ref": "#/definitions/CounselingOffice" + } + }, "CounselingUpdateAllowancePayload": { "type": "object", "properties": { @@ -7062,6 +7155,12 @@ func init() { "type": "integer", "x-nullable": true, "example": 500 + }, + "weightRestriction": { + "description": "Indicates the weight restriction for a move to a particular location.", + "type": "integer", + "x-nullable": true, + "example": 1500 } } }, @@ -7597,6 +7696,12 @@ func init() { "x-nullable": true, "example": true }, + "counselingOfficeId": { + "type": "string", + "format": "uuid", + "x-nullable": true, + "example": "cf1addea-a4f9-4173-8506-2bb82a064cb7" + }, "departmentIndicator": { "$ref": "#/definitions/DeptIndicator" }, @@ -8269,6 +8374,12 @@ func init() { "type": "integer", "x-nullable": true, "example": 3 + }, + "weightRestriction": { + "type": "integer", + "x-formatting": "weight", + "x-nullable": true, + "example": 1500 } } }, @@ -10121,6 +10232,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" @@ -11349,6 +11469,27 @@ func init() { "readOnly": true, "example": "1f2270c7-7166-40ae-981e-b200ebdf3054" }, + "intlLinehaulPrice": { + "description": "The full price of international shipping and linehaul (ISLH)", + "type": "integer", + "format": "cents", + "x-nullable": true, + "x-omitempty": false + }, + "intlPackPrice": { + "description": "The full price of international packing (IHPK)", + "type": "integer", + "format": "cents", + "x-nullable": true, + "x-omitempty": false + }, + "intlUnpackPrice": { + "description": "The full price of international unpacking (IHUPK)", + "type": "integer", + "format": "cents", + "x-nullable": true, + "x-omitempty": false + }, "miles": { "description": "The distance between the old address and the new address in miles.", "type": "integer", @@ -14135,6 +14276,12 @@ func init() { "type": "integer", "x-nullable": true, "example": 500 + }, + "weightRestriction": { + "description": "Indicates the weight restriction for the move to a particular location.", + "type": "integer", + "x-nullable": true, + "example": 1500 } } }, @@ -21270,6 +21417,12 @@ func init() { "description": "Used to illustrate which user is assigned to this payment request.\n", "name": "assignedTo", "in": "query" + }, + { + "type": "string", + "description": "user's actively logged in role", + "name": "activeRole", + "in": "query" } ], "responses": { @@ -21485,6 +21638,12 @@ func init() { "description": "filters using a counselingOffice name of the move", "name": "counselingOffice", "in": "query" + }, + { + "type": "string", + "description": "user's actively logged in role", + "name": "activeRole", + "in": "query" } ], "responses": { @@ -21645,6 +21804,12 @@ func init() { "description": "Used to return a queue for a GBLOC other than the default of the current user. Requires the HQ role or a secondary transportation office assignment. The parameter is ignored if the requesting user does not have the necessary role or assignment.\n", "name": "viewAsGBLOC", "in": "query" + }, + { + "type": "string", + "description": "user's actively logged in role", + "name": "activeRole", + "in": "query" } ], "responses": { @@ -22674,8 +22839,7 @@ func init() { "application/json" ], "tags": [ - "shipment", - "shipment_address_updates" + "shipment" ], "summary": "Allows TOO to review a shipment address update", "operationId": "reviewShipmentAddressUpdate", @@ -23306,6 +23470,66 @@ func init() { } } }, + "/transportation_offices/{dutyLocationId}/counseling_offices/{serviceMemberId}": { + "get": { + "description": "Returns the counseling locations matching the GBLOC from the selected duty location", + "produces": [ + "application/json" + ], + "tags": [ + "transportationOffice" + ], + "summary": "Returns the counseling locations in the GBLOC matching the duty location", + "operationId": "showCounselingOffices", + "parameters": [ + { + "type": "string", + "format": "uuid", + "description": "UUID of the duty location", + "name": "dutyLocationId", + "in": "path", + "required": true + }, + { + "type": "string", + "format": "uuid", + "description": "UUID of the service member, some counseling offices are branch specific", + "name": "serviceMemberId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Successfully retrieved counseling offices", + "schema": { + "$ref": "#/definitions/CounselingOffices" + } + }, + "400": { + "description": "The request payload is invalid", + "schema": { + "$ref": "#/definitions/Error" + } + }, + "403": { + "description": "The request was denied", + "schema": { + "$ref": "#/definitions/Error" + } + }, + "404": { + "description": "The requested resource wasn't found", + "schema": { + "$ref": "#/definitions/Error" + } + }, + "500": { + "description": "internal server error" + } + } + } + }, "/uploads": { "post": { "description": "Uploads represent a single digital file, such as a JPEG or PDF. Currently, office application uploads are only for Services Counselors to upload files for orders, but this may be expanded in the future.", @@ -23856,7 +24080,8 @@ func init() { "example": "c56a4180-65aa-42ec-a945-5fd21dec0538" }, "workload": { - "type": "integer" + "type": "integer", + "x-omitempty": false } } }, @@ -24038,6 +24263,30 @@ func init() { } } }, + "CounselingOffice": { + "type": "object", + "required": [ + "id", + "name" + ], + "properties": { + "id": { + "type": "string", + "format": "uuid", + "example": "c56a4180-65aa-42ec-a945-5fd21dec0538" + }, + "name": { + "type": "string", + "example": "Fort Bragg North Station" + } + } + }, + "CounselingOffices": { + "type": "array", + "items": { + "$ref": "#/definitions/CounselingOffice" + } + }, "CounselingUpdateAllowancePayload": { "type": "object", "properties": { @@ -24113,6 +24362,12 @@ func init() { "type": "integer", "x-nullable": true, "example": 500 + }, + "weightRestriction": { + "description": "Indicates the weight restriction for a move to a particular location.", + "type": "integer", + "x-nullable": true, + "example": 1500 } } }, @@ -24648,6 +24903,12 @@ func init() { "x-nullable": true, "example": true }, + "counselingOfficeId": { + "type": "string", + "format": "uuid", + "x-nullable": true, + "example": "cf1addea-a4f9-4173-8506-2bb82a064cb7" + }, "departmentIndicator": { "$ref": "#/definitions/DeptIndicator" }, @@ -25320,6 +25581,12 @@ func init() { "type": "integer", "x-nullable": true, "example": 3 + }, + "weightRestriction": { + "type": "integer", + "x-formatting": "weight", + "x-nullable": true, + "example": 1500 } } }, @@ -27172,6 +27439,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" @@ -28400,6 +28676,27 @@ func init() { "readOnly": true, "example": "1f2270c7-7166-40ae-981e-b200ebdf3054" }, + "intlLinehaulPrice": { + "description": "The full price of international shipping and linehaul (ISLH)", + "type": "integer", + "format": "cents", + "x-nullable": true, + "x-omitempty": false + }, + "intlPackPrice": { + "description": "The full price of international packing (IHPK)", + "type": "integer", + "format": "cents", + "x-nullable": true, + "x-omitempty": false + }, + "intlUnpackPrice": { + "description": "The full price of international unpacking (IHUPK)", + "type": "integer", + "format": "cents", + "x-nullable": true, + "x-omitempty": false + }, "miles": { "description": "The distance between the old address and the new address in miles.", "type": "integer", @@ -31318,6 +31615,12 @@ func init() { "type": "integer", "x-nullable": true, "example": 500 + }, + "weightRestriction": { + "description": "Indicates the weight restriction for the move to a particular location.", + "type": "integer", + "x-nullable": true, + "example": 1500 } } }, diff --git a/pkg/gen/ghcapi/ghcoperations/mymove_api.go b/pkg/gen/ghcapi/ghcoperations/mymove_api.go index c53c0fec4d7..7d9bb87acf6 100644 --- a/pkg/gen/ghcapi/ghcoperations/mymove_api.go +++ b/pkg/gen/ghcapi/ghcoperations/mymove_api.go @@ -311,6 +311,9 @@ func NewMymoveAPI(spec *loads.Document) *MymoveAPI { PpmShowAOAPacketHandler: ppm.ShowAOAPacketHandlerFunc(func(params ppm.ShowAOAPacketParams) middleware.Responder { return middleware.NotImplemented("operation ppm.ShowAOAPacket has not yet been implemented") }), + TransportationOfficeShowCounselingOfficesHandler: transportation_office.ShowCounselingOfficesHandlerFunc(func(params transportation_office.ShowCounselingOfficesParams) middleware.Responder { + return middleware.NotImplemented("operation transportation_office.ShowCounselingOffices has not yet been implemented") + }), PpmShowPaymentPacketHandler: ppm.ShowPaymentPacketHandlerFunc(func(params ppm.ShowPaymentPacketParams) middleware.Responder { return middleware.NotImplemented("operation ppm.ShowPaymentPacket has not yet been implemented") }), @@ -601,6 +604,8 @@ type MymoveAPI struct { MoveSetFinancialReviewFlagHandler move.SetFinancialReviewFlagHandler // PpmShowAOAPacketHandler sets the operation handler for the show a o a packet operation PpmShowAOAPacketHandler ppm.ShowAOAPacketHandler + // TransportationOfficeShowCounselingOfficesHandler sets the operation handler for the show counseling offices operation + TransportationOfficeShowCounselingOfficesHandler transportation_office.ShowCounselingOfficesHandler // PpmShowPaymentPacketHandler sets the operation handler for the show payment packet operation PpmShowPaymentPacketHandler ppm.ShowPaymentPacketHandler // EvaluationReportsSubmitEvaluationReportHandler sets the operation handler for the submit evaluation report operation @@ -980,6 +985,9 @@ func (o *MymoveAPI) Validate() error { if o.PpmShowAOAPacketHandler == nil { unregistered = append(unregistered, "ppm.ShowAOAPacketHandler") } + if o.TransportationOfficeShowCounselingOfficesHandler == nil { + unregistered = append(unregistered, "transportation_office.ShowCounselingOfficesHandler") + } if o.PpmShowPaymentPacketHandler == nil { unregistered = append(unregistered, "ppm.ShowPaymentPacketHandler") } @@ -1479,6 +1487,10 @@ func (o *MymoveAPI) initHandlerCache() { if o.handlers["GET"] == nil { o.handlers["GET"] = make(map[string]http.Handler) } + o.handlers["GET"]["/transportation_offices/{dutyLocationId}/counseling_offices/{serviceMemberId}"] = transportation_office.NewShowCounselingOffices(o.context, o.TransportationOfficeShowCounselingOfficesHandler) + if o.handlers["GET"] == nil { + o.handlers["GET"] = make(map[string]http.Handler) + } o.handlers["GET"]["/ppm-shipments/{ppmShipmentId}/payment-packet"] = ppm.NewShowPaymentPacket(o.context, o.PpmShowPaymentPacketHandler) if o.handlers["POST"] == nil { o.handlers["POST"] = make(map[string]http.Handler) diff --git a/pkg/gen/ghcapi/ghcoperations/queues/get_moves_queue_parameters.go b/pkg/gen/ghcapi/ghcoperations/queues/get_moves_queue_parameters.go index f1cfe32cfae..2123cc801d9 100644 --- a/pkg/gen/ghcapi/ghcoperations/queues/get_moves_queue_parameters.go +++ b/pkg/gen/ghcapi/ghcoperations/queues/get_moves_queue_parameters.go @@ -34,6 +34,10 @@ type GetMovesQueueParams struct { // HTTP Request Object HTTPRequest *http.Request `json:"-"` + /*user's actively logged in role + In: query + */ + ActiveRole *string /* In: query */ @@ -124,6 +128,11 @@ func (o *GetMovesQueueParams) BindRequest(r *http.Request, route *middleware.Mat qs := runtime.Values(r.URL.Query()) + qActiveRole, qhkActiveRole, _ := qs.GetOK("activeRole") + if err := o.bindActiveRole(qActiveRole, qhkActiveRole, route.Formats); err != nil { + res = append(res, err) + } + qAppearedInTooAt, qhkAppearedInTooAt, _ := qs.GetOK("appearedInTooAt") if err := o.bindAppearedInTooAt(qAppearedInTooAt, qhkAppearedInTooAt, route.Formats); err != nil { res = append(res, err) @@ -219,6 +228,24 @@ func (o *GetMovesQueueParams) BindRequest(r *http.Request, route *middleware.Mat return nil } +// bindActiveRole binds and validates parameter ActiveRole from query. +func (o *GetMovesQueueParams) bindActiveRole(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + o.ActiveRole = &raw + + return nil +} + // bindAppearedInTooAt binds and validates parameter AppearedInTooAt from query. func (o *GetMovesQueueParams) bindAppearedInTooAt(rawData []string, hasKey bool, formats strfmt.Registry) error { var raw string diff --git a/pkg/gen/ghcapi/ghcoperations/queues/get_moves_queue_urlbuilder.go b/pkg/gen/ghcapi/ghcoperations/queues/get_moves_queue_urlbuilder.go index ec05a50a3eb..7d809005df8 100644 --- a/pkg/gen/ghcapi/ghcoperations/queues/get_moves_queue_urlbuilder.go +++ b/pkg/gen/ghcapi/ghcoperations/queues/get_moves_queue_urlbuilder.go @@ -16,6 +16,7 @@ import ( // GetMovesQueueURL generates an URL for the get moves queue operation type GetMovesQueueURL struct { + ActiveRole *string AppearedInTooAt *strfmt.DateTime AssignedTo *string Branch *string @@ -69,6 +70,14 @@ func (o *GetMovesQueueURL) Build() (*url.URL, error) { qs := make(url.Values) + var activeRoleQ string + if o.ActiveRole != nil { + activeRoleQ = *o.ActiveRole + } + if activeRoleQ != "" { + qs.Set("activeRole", activeRoleQ) + } + var appearedInTooAtQ string if o.AppearedInTooAt != nil { appearedInTooAtQ = o.AppearedInTooAt.String() diff --git a/pkg/gen/ghcapi/ghcoperations/queues/get_payment_requests_queue_parameters.go b/pkg/gen/ghcapi/ghcoperations/queues/get_payment_requests_queue_parameters.go index fe0d201031e..423c3f7eaa6 100644 --- a/pkg/gen/ghcapi/ghcoperations/queues/get_payment_requests_queue_parameters.go +++ b/pkg/gen/ghcapi/ghcoperations/queues/get_payment_requests_queue_parameters.go @@ -34,6 +34,10 @@ type GetPaymentRequestsQueueParams struct { // HTTP Request Object HTTPRequest *http.Request `json:"-"` + /*user's actively logged in role + In: query + */ + ActiveRole *string /*Used to illustrate which user is assigned to this payment request. In: query @@ -118,6 +122,11 @@ func (o *GetPaymentRequestsQueueParams) BindRequest(r *http.Request, route *midd qs := runtime.Values(r.URL.Query()) + qActiveRole, qhkActiveRole, _ := qs.GetOK("activeRole") + if err := o.bindActiveRole(qActiveRole, qhkActiveRole, route.Formats); err != nil { + res = append(res, err) + } + qAssignedTo, qhkAssignedTo, _ := qs.GetOK("assignedTo") if err := o.bindAssignedTo(qAssignedTo, qhkAssignedTo, route.Formats); err != nil { res = append(res, err) @@ -208,6 +217,24 @@ func (o *GetPaymentRequestsQueueParams) BindRequest(r *http.Request, route *midd return nil } +// bindActiveRole binds and validates parameter ActiveRole from query. +func (o *GetPaymentRequestsQueueParams) bindActiveRole(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + o.ActiveRole = &raw + + return nil +} + // bindAssignedTo binds and validates parameter AssignedTo from query. func (o *GetPaymentRequestsQueueParams) bindAssignedTo(rawData []string, hasKey bool, formats strfmt.Registry) error { var raw string diff --git a/pkg/gen/ghcapi/ghcoperations/queues/get_payment_requests_queue_urlbuilder.go b/pkg/gen/ghcapi/ghcoperations/queues/get_payment_requests_queue_urlbuilder.go index 1b5aa0e8b3b..45ac4629c59 100644 --- a/pkg/gen/ghcapi/ghcoperations/queues/get_payment_requests_queue_urlbuilder.go +++ b/pkg/gen/ghcapi/ghcoperations/queues/get_payment_requests_queue_urlbuilder.go @@ -16,6 +16,7 @@ import ( // GetPaymentRequestsQueueURL generates an URL for the get payment requests queue operation type GetPaymentRequestsQueueURL struct { + ActiveRole *string AssignedTo *string Branch *string CounselingOffice *string @@ -68,6 +69,14 @@ func (o *GetPaymentRequestsQueueURL) Build() (*url.URL, error) { qs := make(url.Values) + var activeRoleQ string + if o.ActiveRole != nil { + activeRoleQ = *o.ActiveRole + } + if activeRoleQ != "" { + qs.Set("activeRole", activeRoleQ) + } + var assignedToQ string if o.AssignedTo != nil { assignedToQ = *o.AssignedTo diff --git a/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_parameters.go b/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_parameters.go index 2b03f53918f..3f596c1f60c 100644 --- a/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_parameters.go +++ b/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_parameters.go @@ -34,6 +34,10 @@ type GetServicesCounselingQueueParams struct { // HTTP Request Object HTTPRequest *http.Request `json:"-"` + /*user's actively logged in role + In: query + */ + ActiveRole *string /*Used to illustrate which user is assigned to this payment request. In: query @@ -148,6 +152,11 @@ func (o *GetServicesCounselingQueueParams) BindRequest(r *http.Request, route *m qs := runtime.Values(r.URL.Query()) + qActiveRole, qhkActiveRole, _ := qs.GetOK("activeRole") + if err := o.bindActiveRole(qActiveRole, qhkActiveRole, route.Formats); err != nil { + res = append(res, err) + } + qAssignedTo, qhkAssignedTo, _ := qs.GetOK("assignedTo") if err := o.bindAssignedTo(qAssignedTo, qhkAssignedTo, route.Formats); err != nil { res = append(res, err) @@ -273,6 +282,24 @@ func (o *GetServicesCounselingQueueParams) BindRequest(r *http.Request, route *m return nil } +// bindActiveRole binds and validates parameter ActiveRole from query. +func (o *GetServicesCounselingQueueParams) bindActiveRole(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + o.ActiveRole = &raw + + return nil +} + // bindAssignedTo binds and validates parameter AssignedTo from query. func (o *GetServicesCounselingQueueParams) bindAssignedTo(rawData []string, hasKey bool, formats strfmt.Registry) error { var raw string diff --git a/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_urlbuilder.go b/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_urlbuilder.go index d7ad7668c07..b013d4b0089 100644 --- a/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_urlbuilder.go +++ b/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_urlbuilder.go @@ -16,6 +16,7 @@ import ( // GetServicesCounselingQueueURL generates an URL for the get services counseling queue operation type GetServicesCounselingQueueURL struct { + ActiveRole *string AssignedTo *string Branch *string CloseoutInitiated *strfmt.DateTime @@ -75,6 +76,14 @@ func (o *GetServicesCounselingQueueURL) Build() (*url.URL, error) { qs := make(url.Values) + var activeRoleQ string + if o.ActiveRole != nil { + activeRoleQ = *o.ActiveRole + } + if activeRoleQ != "" { + qs.Set("activeRole", activeRoleQ) + } + var assignedToQ string if o.AssignedTo != nil { assignedToQ = *o.AssignedTo diff --git a/pkg/gen/ghcapi/ghcoperations/shipment/review_shipment_address_update.go b/pkg/gen/ghcapi/ghcoperations/shipment/review_shipment_address_update.go index 61dafe8bc53..d4532a282ce 100644 --- a/pkg/gen/ghcapi/ghcoperations/shipment/review_shipment_address_update.go +++ b/pkg/gen/ghcapi/ghcoperations/shipment/review_shipment_address_update.go @@ -36,7 +36,7 @@ func NewReviewShipmentAddressUpdate(ctx *middleware.Context, handler ReviewShipm } /* - ReviewShipmentAddressUpdate swagger:route PATCH /shipments/{shipmentID}/review-shipment-address-update shipment shipment_address_updates reviewShipmentAddressUpdate + ReviewShipmentAddressUpdate swagger:route PATCH /shipments/{shipmentID}/review-shipment-address-update shipment reviewShipmentAddressUpdate # Allows TOO to review a shipment address update diff --git a/pkg/gen/ghcapi/ghcoperations/transportation_office/show_counseling_offices.go b/pkg/gen/ghcapi/ghcoperations/transportation_office/show_counseling_offices.go new file mode 100644 index 00000000000..b0cb58981df --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/transportation_office/show_counseling_offices.go @@ -0,0 +1,58 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package transportation_office + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" +) + +// ShowCounselingOfficesHandlerFunc turns a function with the right signature into a show counseling offices handler +type ShowCounselingOfficesHandlerFunc func(ShowCounselingOfficesParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn ShowCounselingOfficesHandlerFunc) Handle(params ShowCounselingOfficesParams) middleware.Responder { + return fn(params) +} + +// ShowCounselingOfficesHandler interface for that can handle valid show counseling offices params +type ShowCounselingOfficesHandler interface { + Handle(ShowCounselingOfficesParams) middleware.Responder +} + +// NewShowCounselingOffices creates a new http.Handler for the show counseling offices operation +func NewShowCounselingOffices(ctx *middleware.Context, handler ShowCounselingOfficesHandler) *ShowCounselingOffices { + return &ShowCounselingOffices{Context: ctx, Handler: handler} +} + +/* + ShowCounselingOffices swagger:route GET /transportation_offices/{dutyLocationId}/counseling_offices/{serviceMemberId} transportationOffice showCounselingOffices + +# Returns the counseling locations in the GBLOC matching the duty location + +Returns the counseling locations matching the GBLOC from the selected duty location +*/ +type ShowCounselingOffices struct { + Context *middleware.Context + Handler ShowCounselingOfficesHandler +} + +func (o *ShowCounselingOffices) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewShowCounselingOfficesParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/pkg/gen/ghcapi/ghcoperations/transportation_office/show_counseling_offices_parameters.go b/pkg/gen/ghcapi/ghcoperations/transportation_office/show_counseling_offices_parameters.go new file mode 100644 index 00000000000..91d0a12238c --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/transportation_office/show_counseling_offices_parameters.go @@ -0,0 +1,134 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package transportation_office + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/validate" +) + +// NewShowCounselingOfficesParams creates a new ShowCounselingOfficesParams object +// +// There are no default values defined in the spec. +func NewShowCounselingOfficesParams() ShowCounselingOfficesParams { + + return ShowCounselingOfficesParams{} +} + +// ShowCounselingOfficesParams contains all the bound params for the show counseling offices operation +// typically these are obtained from a http.Request +// +// swagger:parameters showCounselingOffices +type ShowCounselingOfficesParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /*UUID of the duty location + Required: true + In: path + */ + DutyLocationID strfmt.UUID + /*UUID of the service member, some counseling offices are branch specific + Required: true + In: path + */ + ServiceMemberID strfmt.UUID +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewShowCounselingOfficesParams() beforehand. +func (o *ShowCounselingOfficesParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + rDutyLocationID, rhkDutyLocationID, _ := route.Params.GetOK("dutyLocationId") + if err := o.bindDutyLocationID(rDutyLocationID, rhkDutyLocationID, route.Formats); err != nil { + res = append(res, err) + } + + rServiceMemberID, rhkServiceMemberID, _ := route.Params.GetOK("serviceMemberId") + if err := o.bindServiceMemberID(rServiceMemberID, rhkServiceMemberID, route.Formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindDutyLocationID binds and validates parameter DutyLocationID from path. +func (o *ShowCounselingOfficesParams) bindDutyLocationID(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: true + // Parameter is provided by construction from the route + + // Format: uuid + value, err := formats.Parse("uuid", raw) + if err != nil { + return errors.InvalidType("dutyLocationId", "path", "strfmt.UUID", raw) + } + o.DutyLocationID = *(value.(*strfmt.UUID)) + + if err := o.validateDutyLocationID(formats); err != nil { + return err + } + + return nil +} + +// validateDutyLocationID carries on validations for parameter DutyLocationID +func (o *ShowCounselingOfficesParams) validateDutyLocationID(formats strfmt.Registry) error { + + if err := validate.FormatOf("dutyLocationId", "path", "uuid", o.DutyLocationID.String(), formats); err != nil { + return err + } + return nil +} + +// bindServiceMemberID binds and validates parameter ServiceMemberID from path. +func (o *ShowCounselingOfficesParams) bindServiceMemberID(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: true + // Parameter is provided by construction from the route + + // Format: uuid + value, err := formats.Parse("uuid", raw) + if err != nil { + return errors.InvalidType("serviceMemberId", "path", "strfmt.UUID", raw) + } + o.ServiceMemberID = *(value.(*strfmt.UUID)) + + if err := o.validateServiceMemberID(formats); err != nil { + return err + } + + return nil +} + +// validateServiceMemberID carries on validations for parameter ServiceMemberID +func (o *ShowCounselingOfficesParams) validateServiceMemberID(formats strfmt.Registry) error { + + if err := validate.FormatOf("serviceMemberId", "path", "uuid", o.ServiceMemberID.String(), formats); err != nil { + return err + } + return nil +} diff --git a/pkg/gen/ghcapi/ghcoperations/transportation_office/show_counseling_offices_responses.go b/pkg/gen/ghcapi/ghcoperations/transportation_office/show_counseling_offices_responses.go new file mode 100644 index 00000000000..0e38c7ca905 --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/transportation_office/show_counseling_offices_responses.go @@ -0,0 +1,222 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package transportation_office + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "github.com/transcom/mymove/pkg/gen/ghcmessages" +) + +// ShowCounselingOfficesOKCode is the HTTP code returned for type ShowCounselingOfficesOK +const ShowCounselingOfficesOKCode int = 200 + +/* +ShowCounselingOfficesOK Successfully retrieved counseling offices + +swagger:response showCounselingOfficesOK +*/ +type ShowCounselingOfficesOK struct { + + /* + In: Body + */ + Payload ghcmessages.CounselingOffices `json:"body,omitempty"` +} + +// NewShowCounselingOfficesOK creates ShowCounselingOfficesOK with default headers values +func NewShowCounselingOfficesOK() *ShowCounselingOfficesOK { + + return &ShowCounselingOfficesOK{} +} + +// WithPayload adds the payload to the show counseling offices o k response +func (o *ShowCounselingOfficesOK) WithPayload(payload ghcmessages.CounselingOffices) *ShowCounselingOfficesOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the show counseling offices o k response +func (o *ShowCounselingOfficesOK) SetPayload(payload ghcmessages.CounselingOffices) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *ShowCounselingOfficesOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + payload := o.Payload + if payload == nil { + // return empty array + payload = ghcmessages.CounselingOffices{} + } + + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } +} + +// ShowCounselingOfficesBadRequestCode is the HTTP code returned for type ShowCounselingOfficesBadRequest +const ShowCounselingOfficesBadRequestCode int = 400 + +/* +ShowCounselingOfficesBadRequest The request payload is invalid + +swagger:response showCounselingOfficesBadRequest +*/ +type ShowCounselingOfficesBadRequest struct { + + /* + In: Body + */ + Payload *ghcmessages.Error `json:"body,omitempty"` +} + +// NewShowCounselingOfficesBadRequest creates ShowCounselingOfficesBadRequest with default headers values +func NewShowCounselingOfficesBadRequest() *ShowCounselingOfficesBadRequest { + + return &ShowCounselingOfficesBadRequest{} +} + +// WithPayload adds the payload to the show counseling offices bad request response +func (o *ShowCounselingOfficesBadRequest) WithPayload(payload *ghcmessages.Error) *ShowCounselingOfficesBadRequest { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the show counseling offices bad request response +func (o *ShowCounselingOfficesBadRequest) SetPayload(payload *ghcmessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *ShowCounselingOfficesBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(400) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// ShowCounselingOfficesForbiddenCode is the HTTP code returned for type ShowCounselingOfficesForbidden +const ShowCounselingOfficesForbiddenCode int = 403 + +/* +ShowCounselingOfficesForbidden The request was denied + +swagger:response showCounselingOfficesForbidden +*/ +type ShowCounselingOfficesForbidden struct { + + /* + In: Body + */ + Payload *ghcmessages.Error `json:"body,omitempty"` +} + +// NewShowCounselingOfficesForbidden creates ShowCounselingOfficesForbidden with default headers values +func NewShowCounselingOfficesForbidden() *ShowCounselingOfficesForbidden { + + return &ShowCounselingOfficesForbidden{} +} + +// WithPayload adds the payload to the show counseling offices forbidden response +func (o *ShowCounselingOfficesForbidden) WithPayload(payload *ghcmessages.Error) *ShowCounselingOfficesForbidden { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the show counseling offices forbidden response +func (o *ShowCounselingOfficesForbidden) SetPayload(payload *ghcmessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *ShowCounselingOfficesForbidden) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(403) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// ShowCounselingOfficesNotFoundCode is the HTTP code returned for type ShowCounselingOfficesNotFound +const ShowCounselingOfficesNotFoundCode int = 404 + +/* +ShowCounselingOfficesNotFound The requested resource wasn't found + +swagger:response showCounselingOfficesNotFound +*/ +type ShowCounselingOfficesNotFound struct { + + /* + In: Body + */ + Payload *ghcmessages.Error `json:"body,omitempty"` +} + +// NewShowCounselingOfficesNotFound creates ShowCounselingOfficesNotFound with default headers values +func NewShowCounselingOfficesNotFound() *ShowCounselingOfficesNotFound { + + return &ShowCounselingOfficesNotFound{} +} + +// WithPayload adds the payload to the show counseling offices not found response +func (o *ShowCounselingOfficesNotFound) WithPayload(payload *ghcmessages.Error) *ShowCounselingOfficesNotFound { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the show counseling offices not found response +func (o *ShowCounselingOfficesNotFound) SetPayload(payload *ghcmessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *ShowCounselingOfficesNotFound) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(404) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// ShowCounselingOfficesInternalServerErrorCode is the HTTP code returned for type ShowCounselingOfficesInternalServerError +const ShowCounselingOfficesInternalServerErrorCode int = 500 + +/* +ShowCounselingOfficesInternalServerError internal server error + +swagger:response showCounselingOfficesInternalServerError +*/ +type ShowCounselingOfficesInternalServerError struct { +} + +// NewShowCounselingOfficesInternalServerError creates ShowCounselingOfficesInternalServerError with default headers values +func NewShowCounselingOfficesInternalServerError() *ShowCounselingOfficesInternalServerError { + + return &ShowCounselingOfficesInternalServerError{} +} + +// WriteResponse to the client +func (o *ShowCounselingOfficesInternalServerError) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses + + rw.WriteHeader(500) +} diff --git a/pkg/gen/ghcapi/ghcoperations/transportation_office/show_counseling_offices_urlbuilder.go b/pkg/gen/ghcapi/ghcoperations/transportation_office/show_counseling_offices_urlbuilder.go new file mode 100644 index 00000000000..269dfc8874e --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/transportation_office/show_counseling_offices_urlbuilder.go @@ -0,0 +1,109 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package transportation_office + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" + "strings" + + "github.com/go-openapi/strfmt" +) + +// ShowCounselingOfficesURL generates an URL for the show counseling offices operation +type ShowCounselingOfficesURL struct { + DutyLocationID strfmt.UUID + ServiceMemberID strfmt.UUID + + _basePath string + // avoid unkeyed usage + _ struct{} +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *ShowCounselingOfficesURL) WithBasePath(bp string) *ShowCounselingOfficesURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *ShowCounselingOfficesURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *ShowCounselingOfficesURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/transportation_offices/{dutyLocationId}/counseling_offices/{serviceMemberId}" + + dutyLocationID := o.DutyLocationID.String() + if dutyLocationID != "" { + _path = strings.Replace(_path, "{dutyLocationId}", dutyLocationID, -1) + } else { + return nil, errors.New("dutyLocationId is required on ShowCounselingOfficesURL") + } + + serviceMemberID := o.ServiceMemberID.String() + if serviceMemberID != "" { + _path = strings.Replace(_path, "{serviceMemberId}", serviceMemberID, -1) + } else { + return nil, errors.New("serviceMemberId is required on ShowCounselingOfficesURL") + } + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/ghc/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *ShowCounselingOfficesURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *ShowCounselingOfficesURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *ShowCounselingOfficesURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on ShowCounselingOfficesURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on ShowCounselingOfficesURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *ShowCounselingOfficesURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/pkg/gen/ghcmessages/available_office_user.go b/pkg/gen/ghcmessages/available_office_user.go index bb9b84b48dc..80e15c0e96b 100644 --- a/pkg/gen/ghcmessages/available_office_user.go +++ b/pkg/gen/ghcmessages/available_office_user.go @@ -34,7 +34,7 @@ type AvailableOfficeUser struct { OfficeUserID strfmt.UUID `json:"officeUserId,omitempty"` // workload - Workload int64 `json:"workload,omitempty"` + Workload int64 `json:"workload"` } // Validate validates this available office user diff --git a/pkg/gen/ghcmessages/counseling_office.go b/pkg/gen/ghcmessages/counseling_office.go new file mode 100644 index 00000000000..b3bf2fea949 --- /dev/null +++ b/pkg/gen/ghcmessages/counseling_office.go @@ -0,0 +1,95 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package ghcmessages + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// CounselingOffice counseling office +// +// swagger:model CounselingOffice +type CounselingOffice struct { + + // id + // Example: c56a4180-65aa-42ec-a945-5fd21dec0538 + // Required: true + // Format: uuid + ID *strfmt.UUID `json:"id"` + + // name + // Example: Fort Bragg North Station + // Required: true + Name *string `json:"name"` +} + +// Validate validates this counseling office +func (m *CounselingOffice) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateID(formats); err != nil { + res = append(res, err) + } + + if err := m.validateName(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *CounselingOffice) validateID(formats strfmt.Registry) error { + + if err := validate.Required("id", "body", m.ID); err != nil { + return err + } + + if err := validate.FormatOf("id", "body", "uuid", m.ID.String(), formats); err != nil { + return err + } + + return nil +} + +func (m *CounselingOffice) validateName(formats strfmt.Registry) error { + + if err := validate.Required("name", "body", m.Name); err != nil { + return err + } + + return nil +} + +// ContextValidate validates this counseling office based on context it is used +func (m *CounselingOffice) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *CounselingOffice) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *CounselingOffice) UnmarshalBinary(b []byte) error { + var res CounselingOffice + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/pkg/gen/ghcmessages/counseling_offices.go b/pkg/gen/ghcmessages/counseling_offices.go new file mode 100644 index 00000000000..28a6d79e3b3 --- /dev/null +++ b/pkg/gen/ghcmessages/counseling_offices.go @@ -0,0 +1,78 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package ghcmessages + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + "strconv" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// CounselingOffices counseling offices +// +// swagger:model CounselingOffices +type CounselingOffices []*CounselingOffice + +// Validate validates this counseling offices +func (m CounselingOffices) Validate(formats strfmt.Registry) error { + var res []error + + for i := 0; i < len(m); i++ { + if swag.IsZero(m[i]) { // not required + continue + } + + if m[i] != nil { + if err := m[i].Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName(strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName(strconv.Itoa(i)) + } + return err + } + } + + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// ContextValidate validate this counseling offices based on the context it is used +func (m CounselingOffices) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + for i := 0; i < len(m); i++ { + + if m[i] != nil { + + if swag.IsZero(m[i]) { // not required + return nil + } + + if err := m[i].ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName(strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName(strconv.Itoa(i)) + } + return err + } + } + + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/pkg/gen/ghcmessages/counseling_update_allowance_payload.go b/pkg/gen/ghcmessages/counseling_update_allowance_payload.go index d6bed9fac0c..805a206b000 100644 --- a/pkg/gen/ghcmessages/counseling_update_allowance_payload.go +++ b/pkg/gen/ghcmessages/counseling_update_allowance_payload.go @@ -70,6 +70,10 @@ type CounselingUpdateAllowancePayload struct { // ub allowance // Example: 500 UbAllowance *int64 `json:"ubAllowance,omitempty"` + + // Indicates the weight restriction for a move to a particular location. + // Example: 1500 + WeightRestriction *int64 `json:"weightRestriction,omitempty"` } // Validate validates this counseling update allowance payload diff --git a/pkg/gen/ghcmessages/create_orders.go b/pkg/gen/ghcmessages/create_orders.go index 25f77f42c15..a71a85a4d74 100644 --- a/pkg/gen/ghcmessages/create_orders.go +++ b/pkg/gen/ghcmessages/create_orders.go @@ -23,6 +23,11 @@ type CreateOrders struct { // Example: true AccompaniedTour *bool `json:"accompaniedTour,omitempty"` + // counseling office Id + // Example: cf1addea-a4f9-4173-8506-2bb82a064cb7 + // Format: uuid + CounselingOfficeID *strfmt.UUID `json:"counselingOfficeId,omitempty"` + // department indicator DepartmentIndicator *DeptIndicator `json:"departmentIndicator,omitempty"` @@ -100,6 +105,10 @@ type CreateOrders struct { func (m *CreateOrders) Validate(formats strfmt.Registry) error { var res []error + if err := m.validateCounselingOfficeID(formats); err != nil { + res = append(res, err) + } + if err := m.validateDepartmentIndicator(formats); err != nil { res = append(res, err) } @@ -150,6 +159,18 @@ func (m *CreateOrders) Validate(formats strfmt.Registry) error { return nil } +func (m *CreateOrders) validateCounselingOfficeID(formats strfmt.Registry) error { + if swag.IsZero(m.CounselingOfficeID) { // not required + return nil + } + + if err := validate.FormatOf("counselingOfficeId", "body", "uuid", m.CounselingOfficeID.String(), formats); err != nil { + return err + } + + return nil +} + func (m *CreateOrders) validateDepartmentIndicator(formats strfmt.Registry) error { if swag.IsZero(m.DepartmentIndicator) { // not required return nil diff --git a/pkg/gen/ghcmessages/entitlements.go b/pkg/gen/ghcmessages/entitlements.go index 2ee15f3d03a..e856534cc33 100644 --- a/pkg/gen/ghcmessages/entitlements.go +++ b/pkg/gen/ghcmessages/entitlements.go @@ -90,6 +90,10 @@ type Entitlements struct { // The amount of weight in pounds that the move is entitled for shipment types of Unaccompanied Baggage. // Example: 3 UnaccompaniedBaggageAllowance *int64 `json:"unaccompaniedBaggageAllowance,omitempty"` + + // weight restriction + // Example: 1500 + WeightRestriction *int64 `json:"weightRestriction,omitempty"` } // Validate validates this entitlements diff --git a/pkg/gen/ghcmessages/move.go b/pkg/gen/ghcmessages/move.go index 26d652a3f42..604dff87fb7 100644 --- a/pkg/gen/ghcmessages/move.go +++ b/pkg/gen/ghcmessages/move.go @@ -61,6 +61,13 @@ type Move struct { // Format: uuid ContractorID *strfmt.UUID `json:"contractorId,omitempty"` + // counseling office + CounselingOffice *TransportationOffice `json:"counselingOffice,omitempty"` + + // The transportation office that will handle services counseling for this move + // Format: uuid + CounselingOfficeID *strfmt.UUID `json:"counselingOfficeId,omitempty"` + // created at // Format: date-time CreatedAt strfmt.DateTime `json:"createdAt,omitempty"` @@ -201,6 +208,14 @@ func (m *Move) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validateCounselingOffice(formats); err != nil { + res = append(res, err) + } + + if err := m.validateCounselingOfficeID(formats); err != nil { + res = append(res, err) + } + if err := m.validateCreatedAt(formats); err != nil { res = append(res, err) } @@ -457,6 +472,37 @@ func (m *Move) validateContractorID(formats strfmt.Registry) error { return nil } +func (m *Move) validateCounselingOffice(formats strfmt.Registry) error { + if swag.IsZero(m.CounselingOffice) { // not required + return nil + } + + if m.CounselingOffice != nil { + if err := m.CounselingOffice.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("counselingOffice") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("counselingOffice") + } + return err + } + } + + return nil +} + +func (m *Move) validateCounselingOfficeID(formats strfmt.Registry) error { + if swag.IsZero(m.CounselingOfficeID) { // not required + return nil + } + + if err := validate.FormatOf("counselingOfficeId", "body", "uuid", m.CounselingOfficeID.String(), formats); err != nil { + return err + } + + return nil +} + func (m *Move) validateCreatedAt(formats strfmt.Registry) error { if swag.IsZero(m.CreatedAt) { // not required return nil @@ -701,6 +747,10 @@ func (m *Move) ContextValidate(ctx context.Context, formats strfmt.Registry) err res = append(res, err) } + if err := m.contextValidateCounselingOffice(ctx, formats); err != nil { + res = append(res, err) + } + if err := m.contextValidateFinancialReviewFlag(ctx, formats); err != nil { res = append(res, err) } @@ -857,6 +907,27 @@ func (m *Move) contextValidateContractor(ctx context.Context, formats strfmt.Reg return nil } +func (m *Move) contextValidateCounselingOffice(ctx context.Context, formats strfmt.Registry) error { + + if m.CounselingOffice != nil { + + if swag.IsZero(m.CounselingOffice) { // not required + return nil + } + + if err := m.CounselingOffice.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("counselingOffice") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("counselingOffice") + } + return err + } + } + + return nil +} + func (m *Move) contextValidateFinancialReviewFlag(ctx context.Context, formats strfmt.Registry) error { if err := validate.ReadOnly(ctx, "financialReviewFlag", "body", bool(m.FinancialReviewFlag)); err != nil { diff --git a/pkg/gen/ghcmessages/p_p_m_closeout.go b/pkg/gen/ghcmessages/p_p_m_closeout.go index b0f423ba61a..a84e0e4c2e0 100644 --- a/pkg/gen/ghcmessages/p_p_m_closeout.go +++ b/pkg/gen/ghcmessages/p_p_m_closeout.go @@ -69,6 +69,15 @@ type PPMCloseout struct { // Format: uuid ID strfmt.UUID `json:"id"` + // The full price of international shipping and linehaul (ISLH) + IntlLinehaulPrice *int64 `json:"intlLinehaulPrice"` + + // The full price of international packing (IHPK) + IntlPackPrice *int64 `json:"intlPackPrice"` + + // The full price of international unpacking (IHUPK) + IntlUnpackPrice *int64 `json:"intlUnpackPrice"` + // The distance between the old address and the new address in miles. // Example: 54 // Minimum: 0 diff --git a/pkg/gen/ghcmessages/update_allowance_payload.go b/pkg/gen/ghcmessages/update_allowance_payload.go index ee0d677c0e7..c0aa957934a 100644 --- a/pkg/gen/ghcmessages/update_allowance_payload.go +++ b/pkg/gen/ghcmessages/update_allowance_payload.go @@ -70,6 +70,10 @@ type UpdateAllowancePayload struct { // ub allowance // Example: 500 UbAllowance *int64 `json:"ubAllowance,omitempty"` + + // Indicates the weight restriction for the move to a particular location. + // Example: 1500 + WeightRestriction *int64 `json:"weightRestriction,omitempty"` } // Validate validates this update allowance payload diff --git a/pkg/gen/primeapi/embedded_spec.go b/pkg/gen/primeapi/embedded_spec.go index d41170948f8..c4dfc4fb5b2 100644 --- a/pkg/gen/primeapi/embedded_spec.go +++ b/pkg/gen/primeapi/embedded_spec.go @@ -355,7 +355,7 @@ func init() { }, "/mto-service-items/{mtoServiceItemID}": { "patch": { - "description": "Updates MTOServiceItems after creation. Not all service items or fields may be updated, please see details below.\n\nThis endpoint supports different body definitions. In the modelType field below, select the modelType corresponding\n to the service item you wish to update and the documentation will update with the new definition.\n\n* Addresses: To update a destination service item's SIT destination final address, update the shipment delivery address.\nFor approved shipments, please use [updateShipmentDestinationAddress](#mtoShipment/updateShipmentDestinationAddress).\nFor shipments not yet approved, please use [updateMTOShipmentAddress](#mtoShipment/updateMTOShipmentAddress).\n\n* SIT Service Items: Take note that when updating ` + "`" + `sitCustomerContacted` + "`" + `, ` + "`" + `sitDepartureDate` + "`" + `, or ` + "`" + `sitRequestedDelivery` + "`" + `, we want\nthose to be updated on ` + "`" + `DOASIT` + "`" + ` (for origin SIT) and ` + "`" + `DDASIT` + "`" + ` (for destination SIT). If updating those values in other service\nitems, the office users will not have as much attention to those values.\n\nTo create a service item, please use [createMTOServiceItem](#mtoServiceItem/createMTOServiceItem)) endpoint.\n\n* Resubmitting rejected SIT service items: This endpoint will handle the logic of changing the status of rejected SIT service items from\nREJECTED to SUBMITTED. Please provide the ` + "`" + `requestedApprovalsRequestedStatus: true` + "`" + ` when resubmitting as this will give attention to the TOO to\nreview the resubmitted SIT service item. Another note, ` + "`" + `updateReason` + "`" + ` must have a different value than the current ` + "`" + `reason` + "`" + ` value on the service item.\nIf this value is not updated, then an error will be sent back.\n\nThe following SIT service items can be resubmitted following a rejection:\n- DDASIT\n- DDDSIT\n- DDFSIT\n- DOASIT\n- DOPSIT\n- DOFSIT\n- DDSFSC\n- DOSFSC\n\nAt a MINIMUM, the payload for resubmitting a rejected SIT service item must look like this:\n` + "`" + `` + "`" + `` + "`" + `json\n{\n \"reServiceCode\": \"DDFSIT\",\n \"updateReason\": \"A reason that differs from the previous reason\",\n \"modelType\": \"UpdateMTOServiceItemSIT\",\n \"requestApprovalsRequestedStatus\": true\n}\n` + "`" + `` + "`" + `` + "`" + `\n\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\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", "consumes": [ "application/json" ], @@ -1063,7 +1063,7 @@ func init() { }, "/payment-requests": { "post": { - "description": "Creates a new instance of a paymentRequest and is assigned the status ` + "`" + `PENDING` + "`" + `.\nA move task order can have multiple payment requests, and\na final payment request can be marked using boolean ` + "`" + `isFinal` + "`" + `.\n\nIf a ` + "`" + `PENDING` + "`" + ` payment request is recalculated,\na new payment request is created and the original request is\nmarked with the status ` + "`" + `DEPRECATED` + "`" + `.\n\n**NOTE**: In order to create a payment request for most service items, the shipment *must*\nbe updated with the ` + "`" + `PrimeActualWeight` + "`" + ` value via [updateMTOShipment](#operation/updateMTOShipment).\n\nIf ` + "`" + `WeightBilled` + "`" + ` is not provided then the full shipment weight (` + "`" + `PrimeActualWeight` + "`" + `) will be considered in the calculation.\n\n**NOTE**: Diversions have a unique calcuation for payment requests without a ` + "`" + `WeightBilled` + "`" + ` parameter.\n\nIf you created a payment request for a diversion and ` + "`" + `WeightBilled` + "`" + ` is not provided, then the following will be used in the calculation:\n- The lowest shipment weight (` + "`" + `PrimeActualWeight` + "`" + `) found in the diverted shipment chain.\n- The lowest reweigh weight found in the diverted shipment chain.\n\nThe diverted shipment chain is created by referencing the ` + "`" + `diversion` + "`" + ` boolean, ` + "`" + `divertedFromShipmentId` + "`" + ` UUID, and matching destination to pickup addresses.\nIf the chain cannot be established it will fall back to the ` + "`" + `PrimeActualWeight` + "`" + ` of the current shipment. This is utilized because diverted shipments are all one single shipment, but going to different locations.\nThe lowest weight found is the true shipment weight, and thus we search the chain of shipments for the lowest weight found.\n\nA service item can be on several payment requests in the case of partial payment requests and payments.\n\nIn the request, if no params are necessary, then just the ` + "`" + `serviceItem` + "`" + ` ` + "`" + `id` + "`" + ` is required. For example:\n` + "`" + `` + "`" + `` + "`" + `json\n{\n \"isFinal\": false,\n \"moveTaskOrderID\": \"uuid\",\n \"serviceItems\": [\n {\n \"id\": \"uuid\",\n },\n {\n \"id\": \"uuid\",\n \"params\": [\n {\n \"key\": \"Service Item Parameter Name\",\n \"value\": \"Service Item Parameter Value\"\n }\n ]\n }\n ],\n \"pointOfContact\": \"string\"\n}\n` + "`" + `` + "`" + `` + "`" + `\n\nDomestic Basic Service Items \u0026 Accepted Payment Request Parameters:\n---\n\n**DLH - Domestic Linehaul**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DSH - Domestic Shorthaul**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**FSC - Fuel Surcharge**\n**NOTE**: FSC requires ` + "`" + `ActualPickupDate` + "`" + ` to be updated on the shipment.\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DUPK - Domestic Unpacking**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DPK - Domestic Packing**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DNPK - Domestic NTS Packing**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DPK - Domestic Packing**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DOP - Domestic Origin Price**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDP - Domestic Destination Price**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\nDomestic SIT Service Items \u0026 Accepted Payment Request Parameters:\n---\n\n**DOFSIT - Domestic origin 1st day SIT**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DOASIT - Domestic origin add'l SIT** *(SITPaymentRequestStart \u0026 SITPaymentRequestEnd are **REQUIRED**)*\n*To create a paymentRequest for this service item, the ` + "`" + `SITPaymentRequestStart` + "`" + ` and ` + "`" + `SITPaymentRequestEnd` + "`" + ` dates must not overlap previously requested SIT dates.*\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n },\n {\n \"key\": \"SITPaymentRequestStart\",\n \"value\": \"date\"\n },\n {\n \"key\": \"SITPaymentRequestEnd\",\n \"value\": \"date\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DOPSIT - Domestic origin SIT pickup**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DOSHUT - Domestic origin shuttle service**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDFSIT - Domestic destination 1st day SIT**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDASIT - Domestic destination add'l SIT** *(SITPaymentRequestStart \u0026 SITPaymentRequestEnd are **REQUIRED**)*\n*To create a paymentRequest for this service item, the ` + "`" + `SITPaymentRequestStart` + "`" + ` and ` + "`" + `SITPaymentRequestEnd` + "`" + ` dates must not overlap previously requested SIT dates.*\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n },\n {\n \"key\": \"SITPaymentRequestStart\",\n \"value\": \"date\"\n },\n {\n \"key\": \"SITPaymentRequestEnd\",\n \"value\": \"date\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDDSIT - Domestic destination SIT delivery**\n*To create a paymentRequest for this service item, it must first have a final address set via [updateMTOServiceItem](#operation/updateMTOServiceItem).*\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDSHUT - Domestic destination shuttle service**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n---\n\nInternational Basic Service Items \u0026 Accepted Payment Request Parameters:\n---\nJust like domestic shipments \u0026 service items, if ` + "`" + `WeightBilled` + "`" + ` is not provided then the full shipment weight (` + "`" + `PrimeActualWeight` + "`" + `) will be considered in the calculation.\n**NOTE**: ` + "`" + `POEFSC` + "`" + ` \u0026 ` + "`" + `PODFSC` + "`" + ` service items must have a port associated on the service item in order to successfully add it to a payment request. To update the port of a service item, you must use the (#operation/updateMTOServiceItem) endpoint.\n\n**ISLH - International Shipping \u0026 Linehaul**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**IHPK - International HHG Pack**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**IHUPK - International HHG Unpack**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**POEFSC - International Port of Embarkation Fuel Surcharge**\n **NOTE**: POEFSC requires ` + "`" + `ActualPickupDate` + "`" + ` to be updated on the shipment \u0026 ` + "`" + `POELocation` + "`" + ` on the service item.\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**PODFSC - International Port of Debarkation Fuel Surcharge**\n**NOTE**: PODFSC requires ` + "`" + `ActualPickupDate` + "`" + ` to be updated on the shipment \u0026 ` + "`" + `PODLocation` + "`" + ` on the service item.\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n---\n", + "description": "Creates a new instance of a paymentRequest and is assigned the status ` + "`" + `PENDING` + "`" + `.\nA move task order can have multiple payment requests, and\na final payment request can be marked using boolean ` + "`" + `isFinal` + "`" + `.\n\nIf a ` + "`" + `PENDING` + "`" + ` payment request is recalculated,\na new payment request is created and the original request is\nmarked with the status ` + "`" + `DEPRECATED` + "`" + `.\n\n**NOTE**: In order to create a payment request for most service items, the shipment *must*\nbe updated with the ` + "`" + `PrimeActualWeight` + "`" + ` value via [updateMTOShipment](#operation/updateMTOShipment).\n\nIf ` + "`" + `WeightBilled` + "`" + ` is not provided then the full shipment weight (` + "`" + `PrimeActualWeight` + "`" + `) will be considered in the calculation.\n\n**NOTE**: Diversions have a unique calcuation for payment requests without a ` + "`" + `WeightBilled` + "`" + ` parameter.\n\nIf you created a payment request for a diversion and ` + "`" + `WeightBilled` + "`" + ` is not provided, then the following will be used in the calculation:\n- The lowest shipment weight (` + "`" + `PrimeActualWeight` + "`" + `) found in the diverted shipment chain.\n- The lowest reweigh weight found in the diverted shipment chain.\n\nThe diverted shipment chain is created by referencing the ` + "`" + `diversion` + "`" + ` boolean, ` + "`" + `divertedFromShipmentId` + "`" + ` UUID, and matching destination to pickup addresses.\nIf the chain cannot be established it will fall back to the ` + "`" + `PrimeActualWeight` + "`" + ` of the current shipment. This is utilized because diverted shipments are all one single shipment, but going to different locations.\nThe lowest weight found is the true shipment weight, and thus we search the chain of shipments for the lowest weight found.\n\nA service item can be on several payment requests in the case of partial payment requests and payments.\n\nIn the request, if no params are necessary, then just the ` + "`" + `serviceItem` + "`" + ` ` + "`" + `id` + "`" + ` is required. For example:\n` + "`" + `` + "`" + `` + "`" + `json\n{\n \"isFinal\": false,\n \"moveTaskOrderID\": \"uuid\",\n \"serviceItems\": [\n {\n \"id\": \"uuid\",\n },\n {\n \"id\": \"uuid\",\n \"params\": [\n {\n \"key\": \"Service Item Parameter Name\",\n \"value\": \"Service Item Parameter Value\"\n }\n ]\n }\n ],\n \"pointOfContact\": \"string\"\n}\n` + "`" + `` + "`" + `` + "`" + `\n\nDomestic Basic Service Items \u0026 Accepted Payment Request Parameters:\n---\n\n**DLH - Domestic Linehaul**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DSH - Domestic Shorthaul**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**FSC - Fuel Surcharge**\n**NOTE**: FSC requires ` + "`" + `ActualPickupDate` + "`" + ` to be updated on the shipment.\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DUPK - Domestic Unpacking**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DPK - Domestic Packing**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DNPK - Domestic NTS Packing**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DPK - Domestic Packing**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DOP - Domestic Origin Price**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDP - Domestic Destination Price**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\nDomestic SIT Service Items \u0026 Accepted Payment Request Parameters:\n---\n\n**DOFSIT - Domestic origin 1st day SIT**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DOASIT - Domestic origin add'l SIT** *(SITPaymentRequestStart \u0026 SITPaymentRequestEnd are **REQUIRED**)*\n*To create a paymentRequest for this service item, the ` + "`" + `SITPaymentRequestStart` + "`" + ` and ` + "`" + `SITPaymentRequestEnd` + "`" + ` dates must not overlap previously requested SIT dates.*\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n },\n {\n \"key\": \"SITPaymentRequestStart\",\n \"value\": \"date\"\n },\n {\n \"key\": \"SITPaymentRequestEnd\",\n \"value\": \"date\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DOPSIT - Domestic origin SIT pickup**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DOSHUT - Domestic origin shuttle service**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDFSIT - Domestic destination 1st day SIT**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDASIT - Domestic destination add'l SIT** *(SITPaymentRequestStart \u0026 SITPaymentRequestEnd are **REQUIRED**)*\n*To create a paymentRequest for this service item, the ` + "`" + `SITPaymentRequestStart` + "`" + ` and ` + "`" + `SITPaymentRequestEnd` + "`" + ` dates must not overlap previously requested SIT dates.*\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n },\n {\n \"key\": \"SITPaymentRequestStart\",\n \"value\": \"date\"\n },\n {\n \"key\": \"SITPaymentRequestEnd\",\n \"value\": \"date\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDDSIT - Domestic destination SIT delivery**\n*To create a paymentRequest for this service item, it must first have a final address set via [updateMTOServiceItem](#operation/updateMTOServiceItem).*\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDSHUT - Domestic destination shuttle service**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n---\n\nInternational Basic Service Items \u0026 Accepted Payment Request Parameters:\n---\nJust like domestic shipments \u0026 service items, if ` + "`" + `WeightBilled` + "`" + ` is not provided then the full shipment weight (` + "`" + `PrimeActualWeight` + "`" + `) will be considered in the calculation.\n**NOTE**: ` + "`" + `POEFSC` + "`" + ` \u0026 ` + "`" + `PODFSC` + "`" + ` service items must have a port associated on the service item in order to successfully add it to a payment request. To update the port of a service item, you must use the (#operation/updateMTOServiceItem) endpoint.\n\n**ISLH - International Shipping \u0026 Linehaul**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**IHPK - International HHG Pack**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**IHUPK - International HHG Unpack**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**POEFSC - International Port of Embarkation Fuel Surcharge**\n **NOTE**: POEFSC requires ` + "`" + `ActualPickupDate` + "`" + ` to be updated on the shipment \u0026 ` + "`" + `POELocation` + "`" + ` on the service item.\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**PODFSC - International Port of Debarkation Fuel Surcharge**\n**NOTE**: PODFSC requires ` + "`" + `ActualPickupDate` + "`" + ` to be updated on the shipment \u0026 ` + "`" + `PODLocation` + "`" + ` on the service item.\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n---\n\nInternational Basic Service Items \u0026 Accepted Payment Request Parameters:\n---\n**IOSHUT - International origin shuttle service**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**IDSHUT - International destination shuttle service**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n---\n", "consumes": [ "application/json" ], @@ -1828,6 +1828,12 @@ func init() { "type": "integer", "x-nullable": true, "example": 3 + }, + "weightRestriction": { + "type": "integer", + "x-formatting": "weight", + "x-nullable": true, + "example": 1500 } } }, @@ -2421,14 +2427,73 @@ func init() { } ] }, + "MTOServiceItemInternationalShuttle": { + "description": "Describes an international 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 + }, + "market": { + "description": "To identify whether the service was provided within (CONUS) or (OCONUS)", + "type": "string", + "enum": [ + "CONUS", + "OCONUS" + ], + "example": "CONUS" + }, + "reServiceCode": { + "description": "A unique code for the service item. Indicates if shuttling is requested for the international shipment origin (` + "`" + `IOSHUT` + "`" + `) or destination (` + "`" + `IDSHUT` + "`" + `).\n", + "type": "string", + "enum": [ + "IOSHUT", + "IDSHUT" + ] + }, + "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." + }, + "requestApprovalsRequestedStatus": { + "description": "Indicates if \"Approvals Requested\" status is being requested.", + "type": "boolean", + "x-nullable": true + } + } + } + ] + }, "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 * PODFSC, POEFSC - MTOSerivceItemInternationalFuelSurcharge\n\nThe documentation will then update with the supported fields.\n", + "description": "Describes all model sub-types for a MTOServiceItem model.\n\nUsing this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DOFSIT, DOASIT - MTOServiceItemOriginSIT\n * DDFSIT, DDASIT - MTOServiceItemDestSIT\n * DOSHUT, DDSHUT - MTOServiceItemShuttle\n * IOSHUT, IDSHUT - MTOServiceItemInternationalShuttle\n * DCRT, DUCRT - MTOServiceItemDomesticCrating\n * ICRT, IUCRT - MTOServiceItemInternationalCrating\n * PODFSC, POEFSC - MTOSerivceItemInternationalFuelSurcharge\n\nThe documentation will then update with the supported fields.\n", "type": "string", "enum": [ "MTOServiceItemBasic", "MTOServiceItemOriginSIT", "MTOServiceItemDestSIT", "MTOServiceItemShuttle", + "MTOServiceItemInternationalShuttle", "MTOServiceItemDomesticCrating", "MTOServiceItemInternationalCrating", "MTOSerivceItemInternationalFuelSurcharge" @@ -4032,13 +4097,54 @@ func init() { } ] }, + "UpdateMTOServiceItemInternationalShuttle": { + "description": "Subtype used to provide the estimated weight and actual weight for shuttle. This is not creating a new service item but rather updating an existing service item.\n", + "allOf": [ + { + "$ref": "#/definitions/UpdateMTOServiceItem" + }, + { + "type": "object", + "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 + }, + "reServiceCode": { + "description": "Service code allowed for this model type.", + "type": "string", + "enum": [ + "IDSHUT", + "IOSHUT" + ] + }, + "requestApprovalsRequestedStatus": { + "description": "Indicates if \"Approvals Requested\" status is being requested.", + "type": "boolean", + "x-nullable": true + } + } + } + ] + }, "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\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 * 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", "type": "string", "enum": [ "UpdateMTOServiceItemSIT", "UpdateMTOServiceItemShuttle", - "UpdateMTOServiceItemInternationalPortFSC" + "UpdateMTOServiceItemInternationalPortFSC", + "UpdateMTOServiceItemInternationalShuttle" ] }, "UpdateMTOServiceItemSIT": { @@ -5051,7 +5157,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 service items: This endpoint will handle the logic of changing the status of rejected SIT service items from\nREJECTED to SUBMITTED. Please provide the ` + "`" + `requestedApprovalsRequestedStatus: true` + "`" + ` when resubmitting as this will give attention to the TOO to\nreview the resubmitted SIT service item. Another note, ` + "`" + `updateReason` + "`" + ` must have a different value than the current ` + "`" + `reason` + "`" + ` value on the service item.\nIf this value is not updated, then an error will be sent back.\n\nThe following SIT service items can be resubmitted following a rejection:\n- DDASIT\n- DDDSIT\n- DDFSIT\n- DOASIT\n- DOPSIT\n- DOFSIT\n- DDSFSC\n- DOSFSC\n\nAt a MINIMUM, the payload for resubmitting a rejected SIT service item must look like this:\n` + "`" + `` + "`" + `` + "`" + `json\n{\n \"reServiceCode\": \"DDFSIT\",\n \"updateReason\": \"A reason that differs from the previous reason\",\n \"modelType\": \"UpdateMTOServiceItemSIT\",\n \"requestApprovalsRequestedStatus\": true\n}\n` + "`" + `` + "`" + `` + "`" + `\n\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\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", "consumes": [ "application/json" ], @@ -5998,7 +6104,7 @@ func init() { }, "/payment-requests": { "post": { - "description": "Creates a new instance of a paymentRequest and is assigned the status ` + "`" + `PENDING` + "`" + `.\nA move task order can have multiple payment requests, and\na final payment request can be marked using boolean ` + "`" + `isFinal` + "`" + `.\n\nIf a ` + "`" + `PENDING` + "`" + ` payment request is recalculated,\na new payment request is created and the original request is\nmarked with the status ` + "`" + `DEPRECATED` + "`" + `.\n\n**NOTE**: In order to create a payment request for most service items, the shipment *must*\nbe updated with the ` + "`" + `PrimeActualWeight` + "`" + ` value via [updateMTOShipment](#operation/updateMTOShipment).\n\nIf ` + "`" + `WeightBilled` + "`" + ` is not provided then the full shipment weight (` + "`" + `PrimeActualWeight` + "`" + `) will be considered in the calculation.\n\n**NOTE**: Diversions have a unique calcuation for payment requests without a ` + "`" + `WeightBilled` + "`" + ` parameter.\n\nIf you created a payment request for a diversion and ` + "`" + `WeightBilled` + "`" + ` is not provided, then the following will be used in the calculation:\n- The lowest shipment weight (` + "`" + `PrimeActualWeight` + "`" + `) found in the diverted shipment chain.\n- The lowest reweigh weight found in the diverted shipment chain.\n\nThe diverted shipment chain is created by referencing the ` + "`" + `diversion` + "`" + ` boolean, ` + "`" + `divertedFromShipmentId` + "`" + ` UUID, and matching destination to pickup addresses.\nIf the chain cannot be established it will fall back to the ` + "`" + `PrimeActualWeight` + "`" + ` of the current shipment. This is utilized because diverted shipments are all one single shipment, but going to different locations.\nThe lowest weight found is the true shipment weight, and thus we search the chain of shipments for the lowest weight found.\n\nA service item can be on several payment requests in the case of partial payment requests and payments.\n\nIn the request, if no params are necessary, then just the ` + "`" + `serviceItem` + "`" + ` ` + "`" + `id` + "`" + ` is required. For example:\n` + "`" + `` + "`" + `` + "`" + `json\n{\n \"isFinal\": false,\n \"moveTaskOrderID\": \"uuid\",\n \"serviceItems\": [\n {\n \"id\": \"uuid\",\n },\n {\n \"id\": \"uuid\",\n \"params\": [\n {\n \"key\": \"Service Item Parameter Name\",\n \"value\": \"Service Item Parameter Value\"\n }\n ]\n }\n ],\n \"pointOfContact\": \"string\"\n}\n` + "`" + `` + "`" + `` + "`" + `\n\nDomestic Basic Service Items \u0026 Accepted Payment Request Parameters:\n---\n\n**DLH - Domestic Linehaul**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DSH - Domestic Shorthaul**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**FSC - Fuel Surcharge**\n**NOTE**: FSC requires ` + "`" + `ActualPickupDate` + "`" + ` to be updated on the shipment.\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DUPK - Domestic Unpacking**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DPK - Domestic Packing**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DNPK - Domestic NTS Packing**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DPK - Domestic Packing**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DOP - Domestic Origin Price**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDP - Domestic Destination Price**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\nDomestic SIT Service Items \u0026 Accepted Payment Request Parameters:\n---\n\n**DOFSIT - Domestic origin 1st day SIT**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DOASIT - Domestic origin add'l SIT** *(SITPaymentRequestStart \u0026 SITPaymentRequestEnd are **REQUIRED**)*\n*To create a paymentRequest for this service item, the ` + "`" + `SITPaymentRequestStart` + "`" + ` and ` + "`" + `SITPaymentRequestEnd` + "`" + ` dates must not overlap previously requested SIT dates.*\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n },\n {\n \"key\": \"SITPaymentRequestStart\",\n \"value\": \"date\"\n },\n {\n \"key\": \"SITPaymentRequestEnd\",\n \"value\": \"date\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DOPSIT - Domestic origin SIT pickup**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DOSHUT - Domestic origin shuttle service**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDFSIT - Domestic destination 1st day SIT**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDASIT - Domestic destination add'l SIT** *(SITPaymentRequestStart \u0026 SITPaymentRequestEnd are **REQUIRED**)*\n*To create a paymentRequest for this service item, the ` + "`" + `SITPaymentRequestStart` + "`" + ` and ` + "`" + `SITPaymentRequestEnd` + "`" + ` dates must not overlap previously requested SIT dates.*\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n },\n {\n \"key\": \"SITPaymentRequestStart\",\n \"value\": \"date\"\n },\n {\n \"key\": \"SITPaymentRequestEnd\",\n \"value\": \"date\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDDSIT - Domestic destination SIT delivery**\n*To create a paymentRequest for this service item, it must first have a final address set via [updateMTOServiceItem](#operation/updateMTOServiceItem).*\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDSHUT - Domestic destination shuttle service**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n---\n\nInternational Basic Service Items \u0026 Accepted Payment Request Parameters:\n---\nJust like domestic shipments \u0026 service items, if ` + "`" + `WeightBilled` + "`" + ` is not provided then the full shipment weight (` + "`" + `PrimeActualWeight` + "`" + `) will be considered in the calculation.\n**NOTE**: ` + "`" + `POEFSC` + "`" + ` \u0026 ` + "`" + `PODFSC` + "`" + ` service items must have a port associated on the service item in order to successfully add it to a payment request. To update the port of a service item, you must use the (#operation/updateMTOServiceItem) endpoint.\n\n**ISLH - International Shipping \u0026 Linehaul**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**IHPK - International HHG Pack**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**IHUPK - International HHG Unpack**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**POEFSC - International Port of Embarkation Fuel Surcharge**\n **NOTE**: POEFSC requires ` + "`" + `ActualPickupDate` + "`" + ` to be updated on the shipment \u0026 ` + "`" + `POELocation` + "`" + ` on the service item.\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**PODFSC - International Port of Debarkation Fuel Surcharge**\n**NOTE**: PODFSC requires ` + "`" + `ActualPickupDate` + "`" + ` to be updated on the shipment \u0026 ` + "`" + `PODLocation` + "`" + ` on the service item.\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n---\n", + "description": "Creates a new instance of a paymentRequest and is assigned the status ` + "`" + `PENDING` + "`" + `.\nA move task order can have multiple payment requests, and\na final payment request can be marked using boolean ` + "`" + `isFinal` + "`" + `.\n\nIf a ` + "`" + `PENDING` + "`" + ` payment request is recalculated,\na new payment request is created and the original request is\nmarked with the status ` + "`" + `DEPRECATED` + "`" + `.\n\n**NOTE**: In order to create a payment request for most service items, the shipment *must*\nbe updated with the ` + "`" + `PrimeActualWeight` + "`" + ` value via [updateMTOShipment](#operation/updateMTOShipment).\n\nIf ` + "`" + `WeightBilled` + "`" + ` is not provided then the full shipment weight (` + "`" + `PrimeActualWeight` + "`" + `) will be considered in the calculation.\n\n**NOTE**: Diversions have a unique calcuation for payment requests without a ` + "`" + `WeightBilled` + "`" + ` parameter.\n\nIf you created a payment request for a diversion and ` + "`" + `WeightBilled` + "`" + ` is not provided, then the following will be used in the calculation:\n- The lowest shipment weight (` + "`" + `PrimeActualWeight` + "`" + `) found in the diverted shipment chain.\n- The lowest reweigh weight found in the diverted shipment chain.\n\nThe diverted shipment chain is created by referencing the ` + "`" + `diversion` + "`" + ` boolean, ` + "`" + `divertedFromShipmentId` + "`" + ` UUID, and matching destination to pickup addresses.\nIf the chain cannot be established it will fall back to the ` + "`" + `PrimeActualWeight` + "`" + ` of the current shipment. This is utilized because diverted shipments are all one single shipment, but going to different locations.\nThe lowest weight found is the true shipment weight, and thus we search the chain of shipments for the lowest weight found.\n\nA service item can be on several payment requests in the case of partial payment requests and payments.\n\nIn the request, if no params are necessary, then just the ` + "`" + `serviceItem` + "`" + ` ` + "`" + `id` + "`" + ` is required. For example:\n` + "`" + `` + "`" + `` + "`" + `json\n{\n \"isFinal\": false,\n \"moveTaskOrderID\": \"uuid\",\n \"serviceItems\": [\n {\n \"id\": \"uuid\",\n },\n {\n \"id\": \"uuid\",\n \"params\": [\n {\n \"key\": \"Service Item Parameter Name\",\n \"value\": \"Service Item Parameter Value\"\n }\n ]\n }\n ],\n \"pointOfContact\": \"string\"\n}\n` + "`" + `` + "`" + `` + "`" + `\n\nDomestic Basic Service Items \u0026 Accepted Payment Request Parameters:\n---\n\n**DLH - Domestic Linehaul**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DSH - Domestic Shorthaul**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**FSC - Fuel Surcharge**\n**NOTE**: FSC requires ` + "`" + `ActualPickupDate` + "`" + ` to be updated on the shipment.\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DUPK - Domestic Unpacking**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DPK - Domestic Packing**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DNPK - Domestic NTS Packing**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DPK - Domestic Packing**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DOP - Domestic Origin Price**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDP - Domestic Destination Price**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\nDomestic SIT Service Items \u0026 Accepted Payment Request Parameters:\n---\n\n**DOFSIT - Domestic origin 1st day SIT**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DOASIT - Domestic origin add'l SIT** *(SITPaymentRequestStart \u0026 SITPaymentRequestEnd are **REQUIRED**)*\n*To create a paymentRequest for this service item, the ` + "`" + `SITPaymentRequestStart` + "`" + ` and ` + "`" + `SITPaymentRequestEnd` + "`" + ` dates must not overlap previously requested SIT dates.*\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n },\n {\n \"key\": \"SITPaymentRequestStart\",\n \"value\": \"date\"\n },\n {\n \"key\": \"SITPaymentRequestEnd\",\n \"value\": \"date\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DOPSIT - Domestic origin SIT pickup**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DOSHUT - Domestic origin shuttle service**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDFSIT - Domestic destination 1st day SIT**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDASIT - Domestic destination add'l SIT** *(SITPaymentRequestStart \u0026 SITPaymentRequestEnd are **REQUIRED**)*\n*To create a paymentRequest for this service item, the ` + "`" + `SITPaymentRequestStart` + "`" + ` and ` + "`" + `SITPaymentRequestEnd` + "`" + ` dates must not overlap previously requested SIT dates.*\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n },\n {\n \"key\": \"SITPaymentRequestStart\",\n \"value\": \"date\"\n },\n {\n \"key\": \"SITPaymentRequestEnd\",\n \"value\": \"date\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDDSIT - Domestic destination SIT delivery**\n*To create a paymentRequest for this service item, it must first have a final address set via [updateMTOServiceItem](#operation/updateMTOServiceItem).*\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDSHUT - Domestic destination shuttle service**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n---\n\nInternational Basic Service Items \u0026 Accepted Payment Request Parameters:\n---\nJust like domestic shipments \u0026 service items, if ` + "`" + `WeightBilled` + "`" + ` is not provided then the full shipment weight (` + "`" + `PrimeActualWeight` + "`" + `) will be considered in the calculation.\n**NOTE**: ` + "`" + `POEFSC` + "`" + ` \u0026 ` + "`" + `PODFSC` + "`" + ` service items must have a port associated on the service item in order to successfully add it to a payment request. To update the port of a service item, you must use the (#operation/updateMTOServiceItem) endpoint.\n\n**ISLH - International Shipping \u0026 Linehaul**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**IHPK - International HHG Pack**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**IHUPK - International HHG Unpack**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**POEFSC - International Port of Embarkation Fuel Surcharge**\n **NOTE**: POEFSC requires ` + "`" + `ActualPickupDate` + "`" + ` to be updated on the shipment \u0026 ` + "`" + `POELocation` + "`" + ` on the service item.\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**PODFSC - International Port of Debarkation Fuel Surcharge**\n**NOTE**: PODFSC requires ` + "`" + `ActualPickupDate` + "`" + ` to be updated on the shipment \u0026 ` + "`" + `PODLocation` + "`" + ` on the service item.\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n---\n\nInternational Basic Service Items \u0026 Accepted Payment Request Parameters:\n---\n**IOSHUT - International origin shuttle service**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**IDSHUT - International destination shuttle service**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n---\n", "consumes": [ "application/json" ], @@ -6799,6 +6905,12 @@ func init() { "type": "integer", "x-nullable": true, "example": 3 + }, + "weightRestriction": { + "type": "integer", + "x-formatting": "weight", + "x-nullable": true, + "example": 1500 } } }, @@ -7392,14 +7504,73 @@ func init() { } ] }, + "MTOServiceItemInternationalShuttle": { + "description": "Describes an international 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 + }, + "market": { + "description": "To identify whether the service was provided within (CONUS) or (OCONUS)", + "type": "string", + "enum": [ + "CONUS", + "OCONUS" + ], + "example": "CONUS" + }, + "reServiceCode": { + "description": "A unique code for the service item. Indicates if shuttling is requested for the international shipment origin (` + "`" + `IOSHUT` + "`" + `) or destination (` + "`" + `IDSHUT` + "`" + `).\n", + "type": "string", + "enum": [ + "IOSHUT", + "IDSHUT" + ] + }, + "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." + }, + "requestApprovalsRequestedStatus": { + "description": "Indicates if \"Approvals Requested\" status is being requested.", + "type": "boolean", + "x-nullable": true + } + } + } + ] + }, "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 * PODFSC, POEFSC - MTOSerivceItemInternationalFuelSurcharge\n\nThe documentation will then update with the supported fields.\n", + "description": "Describes all model sub-types for a MTOServiceItem model.\n\nUsing this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DOFSIT, DOASIT - MTOServiceItemOriginSIT\n * DDFSIT, DDASIT - MTOServiceItemDestSIT\n * DOSHUT, DDSHUT - MTOServiceItemShuttle\n * IOSHUT, IDSHUT - MTOServiceItemInternationalShuttle\n * DCRT, DUCRT - MTOServiceItemDomesticCrating\n * ICRT, IUCRT - MTOServiceItemInternationalCrating\n * PODFSC, POEFSC - MTOSerivceItemInternationalFuelSurcharge\n\nThe documentation will then update with the supported fields.\n", "type": "string", "enum": [ "MTOServiceItemBasic", "MTOServiceItemOriginSIT", "MTOServiceItemDestSIT", "MTOServiceItemShuttle", + "MTOServiceItemInternationalShuttle", "MTOServiceItemDomesticCrating", "MTOServiceItemInternationalCrating", "MTOSerivceItemInternationalFuelSurcharge" @@ -9008,13 +9179,54 @@ func init() { } ] }, + "UpdateMTOServiceItemInternationalShuttle": { + "description": "Subtype used to provide the estimated weight and actual weight for shuttle. This is not creating a new service item but rather updating an existing service item.\n", + "allOf": [ + { + "$ref": "#/definitions/UpdateMTOServiceItem" + }, + { + "type": "object", + "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 + }, + "reServiceCode": { + "description": "Service code allowed for this model type.", + "type": "string", + "enum": [ + "IDSHUT", + "IOSHUT" + ] + }, + "requestApprovalsRequestedStatus": { + "description": "Indicates if \"Approvals Requested\" status is being requested.", + "type": "boolean", + "x-nullable": true + } + } + } + ] + }, "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\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 * 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", "type": "string", "enum": [ "UpdateMTOServiceItemSIT", "UpdateMTOServiceItemShuttle", - "UpdateMTOServiceItemInternationalPortFSC" + "UpdateMTOServiceItemInternationalPortFSC", + "UpdateMTOServiceItemInternationalShuttle" ] }, "UpdateMTOServiceItemSIT": { 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 d5ba2aa78de..8bfdb75c0f7 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 @@ -50,9 +50,9 @@ items, the office users will not have as much attention to those values. To create a service item, please use [createMTOServiceItem](#mtoServiceItem/createMTOServiceItem)) endpoint. -* Resubmitting rejected SIT service items: This endpoint will handle the logic of changing the status of rejected SIT service items from +* Resubmitting rejected SIT/Accessorial service items: This endpoint will handle the logic of changing the status of rejected SIT/Accessorial service items from REJECTED to SUBMITTED. Please provide the `requestedApprovalsRequestedStatus: true` when resubmitting as this will give attention to the TOO to -review the resubmitted SIT service item. Another note, `updateReason` must have a different value than the current `reason` value on the service item. +review the resubmitted SIT/Accessorial service item. Another note, `updateReason` must have a different value than the current `reason` value on the service item. If this value is not updated, then an error will be sent back. The following SIT service items can be resubmitted following a rejection: @@ -65,7 +65,11 @@ The following SIT service items can be resubmitted following a rejection: - DDSFSC - DOSFSC -At a MINIMUM, the payload for resubmitting a rejected SIT service item must look like this: +The following Accessorial service items can be resubmitted following a rejection: +- IOSHUT +- IDSHUT + +At a MINIMUM, the payload for resubmitting a rejected SIT/Accessorial service item must look like this: ```json { diff --git a/pkg/gen/primeapi/primeoperations/payment_request/create_payment_request.go b/pkg/gen/primeapi/primeoperations/payment_request/create_payment_request.go index d1ca6a38ab8..137229d55d1 100644 --- a/pkg/gen/primeapi/primeoperations/payment_request/create_payment_request.go +++ b/pkg/gen/primeapi/primeoperations/payment_request/create_payment_request.go @@ -382,6 +382,33 @@ Just like domestic shipments & service items, if `WeightBilled` is not provided } ] +``` +--- + +International Basic Service Items & Accepted Payment Request Parameters: +--- +**IOSHUT - International origin shuttle service** +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + +``` + +**IDSHUT - International destination shuttle service** +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` --- */ 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 1f1299ebd67..9fe2fa1212d 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 @@ -233,9 +233,9 @@ items, the office users will not have as much attention to those values. To create a service item, please use [createMTOServiceItem](#mtoServiceItem/createMTOServiceItem)) endpoint. -* Resubmitting rejected SIT service items: This endpoint will handle the logic of changing the status of rejected SIT service items from +* Resubmitting rejected SIT/Accessorial service items: This endpoint will handle the logic of changing the status of rejected SIT/Accessorial service items from REJECTED to SUBMITTED. Please provide the `requestedApprovalsRequestedStatus: true` when resubmitting as this will give attention to the TOO to -review the resubmitted SIT service item. Another note, `updateReason` must have a different value than the current `reason` value on the service item. +review the resubmitted SIT/Accessorial service item. Another note, `updateReason` must have a different value than the current `reason` value on the service item. If this value is not updated, then an error will be sent back. The following SIT service items can be resubmitted following a rejection: @@ -248,7 +248,11 @@ The following SIT service items can be resubmitted following a rejection: - DDSFSC - DOSFSC -At a MINIMUM, the payload for resubmitting a rejected SIT service item must look like this: +The following Accessorial service items can be resubmitted following a rejection: +- IOSHUT +- IDSHUT + +At a MINIMUM, the payload for resubmitting a rejected SIT/Accessorial service item must look like this: ```json { diff --git a/pkg/gen/primeclient/payment_request/payment_request_client.go b/pkg/gen/primeclient/payment_request/payment_request_client.go index b10b46d87cf..a2fba214717 100644 --- a/pkg/gen/primeclient/payment_request/payment_request_client.go +++ b/pkg/gen/primeclient/payment_request/payment_request_client.go @@ -389,6 +389,33 @@ Just like domestic shipments & service items, if `WeightBilled` is not provided } ] +``` +--- + +International Basic Service Items & Accepted Payment Request Parameters: +--- +**IOSHUT - International origin shuttle service** +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + +``` + +**IDSHUT - International destination shuttle service** +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` --- */ diff --git a/pkg/gen/primemessages/entitlements.go b/pkg/gen/primemessages/entitlements.go index c51ada24273..65870bfa8e6 100644 --- a/pkg/gen/primemessages/entitlements.go +++ b/pkg/gen/primemessages/entitlements.go @@ -79,6 +79,10 @@ type Entitlements struct { // The amount of weight in pounds that the move is entitled for shipment types of Unaccompanied Baggage. // Example: 3 UnaccompaniedBaggageAllowance *int64 `json:"unaccompaniedBaggageAllowance,omitempty"` + + // weight restriction + // Example: 1500 + WeightRestriction *int64 `json:"weightRestriction,omitempty"` } // Validate validates this entitlements diff --git a/pkg/gen/primemessages/m_t_o_service_item.go b/pkg/gen/primemessages/m_t_o_service_item.go index 1db2b84d144..5ed0f248ae0 100644 --- a/pkg/gen/primemessages/m_t_o_service_item.go +++ b/pkg/gen/primemessages/m_t_o_service_item.go @@ -285,6 +285,12 @@ func unmarshalMTOServiceItem(data []byte, consumer runtime.Consumer) (MTOService 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/primemessages/m_t_o_service_item_international_shuttle.go b/pkg/gen/primemessages/m_t_o_service_item_international_shuttle.go new file mode 100644 index 00000000000..7b7dbdf29de --- /dev/null +++ b/pkg/gen/primemessages/m_t_o_service_item_international_shuttle.go @@ -0,0 +1,701 @@ +// 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" +) + +// MTOServiceItemInternationalShuttle Describes an international shuttle service item. +// +// swagger:model MTOServiceItemInternationalShuttle +type MTOServiceItemInternationalShuttle 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"` + + // To identify whether the service was provided within (CONUS) or (OCONUS) + // Example: CONUS + // Enum: [CONUS OCONUS] + Market string `json:"market,omitempty"` + + // A unique code for the service item. Indicates if shuttling is requested for the international shipment origin (`IOSHUT`) or destination (`IDSHUT`). + // + // Required: true + // Enum: [IOSHUT IDSHUT] + 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"` + + // Indicates if "Approvals Requested" status is being requested. + RequestApprovalsRequestedStatus *bool `json:"requestApprovalsRequestedStatus,omitempty"` +} + +// 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 +} + +// LockedPriceCents gets the locked price cents of this subtype +func (m *MTOServiceItemInternationalShuttle) LockedPriceCents() *int64 { + return m.lockedPriceCentsField +} + +// SetLockedPriceCents sets the locked price cents of this subtype +func (m *MTOServiceItemInternationalShuttle) SetLockedPriceCents(val *int64) { + m.lockedPriceCentsField = 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 +} + +// ServiceRequestDocuments gets the service request documents of this subtype +func (m *MTOServiceItemInternationalShuttle) ServiceRequestDocuments() ServiceRequestDocuments { + return m.serviceRequestDocumentsField +} + +// SetServiceRequestDocuments sets the service request documents of this subtype +func (m *MTOServiceItemInternationalShuttle) SetServiceRequestDocuments(val ServiceRequestDocuments) { + m.serviceRequestDocumentsField = 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 { + + // 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"` + + // To identify whether the service was provided within (CONUS) or (OCONUS) + // Example: CONUS + // Enum: [CONUS OCONUS] + Market string `json:"market,omitempty"` + + // A unique code for the service item. Indicates if shuttling is requested for the international shipment origin (`IOSHUT`) or destination (`IDSHUT`). + // + // Required: true + // Enum: [IOSHUT IDSHUT] + 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"` + + // Indicates if "Approvals Requested" status is being requested. + RequestApprovalsRequestedStatus *bool `json:"requestApprovalsRequestedStatus,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 MTOServiceItemInternationalShuttle + + 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.Market = data.Market + result.ReServiceCode = data.ReServiceCode + result.Reason = data.Reason + result.RequestApprovalsRequestedStatus = data.RequestApprovalsRequestedStatus + + *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 { + + // 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"` + + // To identify whether the service was provided within (CONUS) or (OCONUS) + // Example: CONUS + // Enum: [CONUS OCONUS] + Market string `json:"market,omitempty"` + + // A unique code for the service item. Indicates if shuttling is requested for the international shipment origin (`IOSHUT`) or destination (`IDSHUT`). + // + // Required: true + // Enum: [IOSHUT IDSHUT] + 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"` + + // Indicates if "Approvals Requested" status is being requested. + RequestApprovalsRequestedStatus *bool `json:"requestApprovalsRequestedStatus,omitempty"` + }{ + + ActualWeight: m.ActualWeight, + + EstimatedWeight: m.EstimatedWeight, + + Market: m.Market, + + ReServiceCode: m.ReServiceCode, + + Reason: m.Reason, + + RequestApprovalsRequestedStatus: m.RequestApprovalsRequestedStatus, + }) + 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 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.validateServiceRequestDocuments(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) 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 *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.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 *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) 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 *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) 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 *MTOServiceItemInternationalShuttle) 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 *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/primemessages/m_t_o_service_item_model_type.go b/pkg/gen/primemessages/m_t_o_service_item_model_type.go index ffdea3d8c15..9326c1377a1 100644 --- a/pkg/gen/primemessages/m_t_o_service_item_model_type.go +++ b/pkg/gen/primemessages/m_t_o_service_item_model_type.go @@ -20,6 +20,7 @@ import ( // - DOFSIT, DOASIT - MTOServiceItemOriginSIT // - DDFSIT, DDASIT - MTOServiceItemDestSIT // - DOSHUT, DDSHUT - MTOServiceItemShuttle +// - IOSHUT, IDSHUT - MTOServiceItemInternationalShuttle // - DCRT, DUCRT - MTOServiceItemDomesticCrating // - ICRT, IUCRT - MTOServiceItemInternationalCrating // - PODFSC, POEFSC - MTOSerivceItemInternationalFuelSurcharge @@ -52,6 +53,9 @@ const ( // MTOServiceItemModelTypeMTOServiceItemShuttle captures enum value "MTOServiceItemShuttle" MTOServiceItemModelTypeMTOServiceItemShuttle MTOServiceItemModelType = "MTOServiceItemShuttle" + // MTOServiceItemModelTypeMTOServiceItemInternationalShuttle captures enum value "MTOServiceItemInternationalShuttle" + MTOServiceItemModelTypeMTOServiceItemInternationalShuttle MTOServiceItemModelType = "MTOServiceItemInternationalShuttle" + // MTOServiceItemModelTypeMTOServiceItemDomesticCrating captures enum value "MTOServiceItemDomesticCrating" MTOServiceItemModelTypeMTOServiceItemDomesticCrating MTOServiceItemModelType = "MTOServiceItemDomesticCrating" @@ -67,7 +71,7 @@ var mTOServiceItemModelTypeEnum []interface{} func init() { var res []MTOServiceItemModelType - if err := json.Unmarshal([]byte(`["MTOServiceItemBasic","MTOServiceItemOriginSIT","MTOServiceItemDestSIT","MTOServiceItemShuttle","MTOServiceItemDomesticCrating","MTOServiceItemInternationalCrating","MTOSerivceItemInternationalFuelSurcharge"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["MTOServiceItemBasic","MTOServiceItemOriginSIT","MTOServiceItemDestSIT","MTOServiceItemShuttle","MTOServiceItemInternationalShuttle","MTOServiceItemDomesticCrating","MTOServiceItemInternationalCrating","MTOSerivceItemInternationalFuelSurcharge"]`), &res); err != nil { panic(err) } for _, v := range res { 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 3afb12a256e..36fc059bd0a 100644 --- a/pkg/gen/primemessages/update_m_t_o_service_item.go +++ b/pkg/gen/primemessages/update_m_t_o_service_item.go @@ -123,6 +123,12 @@ func unmarshalUpdateMTOServiceItem(data []byte, consumer runtime.Consumer) (Upda return nil, err } return &result, nil + case "UpdateMTOServiceItemInternationalShuttle": + var result UpdateMTOServiceItemInternationalShuttle + if err := consumer.Consume(buf2, &result); err != nil { + return nil, err + } + return &result, nil case "UpdateMTOServiceItemSIT": var result UpdateMTOServiceItemSIT if err := consumer.Consume(buf2, &result); err != nil { diff --git a/pkg/gen/primemessages/update_m_t_o_service_item_international_shuttle.go b/pkg/gen/primemessages/update_m_t_o_service_item_international_shuttle.go new file mode 100644 index 00000000000..1d4a7b6a0bf --- /dev/null +++ b/pkg/gen/primemessages/update_m_t_o_service_item_international_shuttle.go @@ -0,0 +1,276 @@ +// 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" +) + +// UpdateMTOServiceItemInternationalShuttle Subtype used to provide the estimated weight and actual weight for shuttle. This is not creating a new service item but rather updating an existing service item. +// +// swagger:model UpdateMTOServiceItemInternationalShuttle +type UpdateMTOServiceItemInternationalShuttle struct { + idField strfmt.UUID + + // 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"` + + // Service code allowed for this model type. + // Enum: [IDSHUT IOSHUT] + ReServiceCode string `json:"reServiceCode,omitempty"` + + // Indicates if "Approvals Requested" status is being requested. + RequestApprovalsRequestedStatus *bool `json:"requestApprovalsRequestedStatus,omitempty"` +} + +// ID gets the id of this subtype +func (m *UpdateMTOServiceItemInternationalShuttle) ID() strfmt.UUID { + return m.idField +} + +// SetID sets the id of this subtype +func (m *UpdateMTOServiceItemInternationalShuttle) SetID(val strfmt.UUID) { + m.idField = val +} + +// ModelType gets the model type of this subtype +func (m *UpdateMTOServiceItemInternationalShuttle) ModelType() UpdateMTOServiceItemModelType { + return "UpdateMTOServiceItemInternationalShuttle" +} + +// SetModelType sets the model type of this subtype +func (m *UpdateMTOServiceItemInternationalShuttle) SetModelType(val UpdateMTOServiceItemModelType) { +} + +// UnmarshalJSON unmarshals this object with a polymorphic type from a JSON structure +func (m *UpdateMTOServiceItemInternationalShuttle) 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"` + + // Service code allowed for this model type. + // Enum: [IDSHUT IOSHUT] + ReServiceCode string `json:"reServiceCode,omitempty"` + + // Indicates if "Approvals Requested" status is being requested. + RequestApprovalsRequestedStatus *bool `json:"requestApprovalsRequestedStatus,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 UpdateMTOServiceItemInternationalShuttle + + 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.ActualWeight = data.ActualWeight + result.EstimatedWeight = data.EstimatedWeight + result.ReServiceCode = data.ReServiceCode + result.RequestApprovalsRequestedStatus = data.RequestApprovalsRequestedStatus + + *m = result + + return nil +} + +// MarshalJSON marshals this object with a polymorphic type to a JSON structure +func (m UpdateMTOServiceItemInternationalShuttle) 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"` + + // Service code allowed for this model type. + // Enum: [IDSHUT IOSHUT] + ReServiceCode string `json:"reServiceCode,omitempty"` + + // Indicates if "Approvals Requested" status is being requested. + RequestApprovalsRequestedStatus *bool `json:"requestApprovalsRequestedStatus,omitempty"` + }{ + + ActualWeight: m.ActualWeight, + + EstimatedWeight: m.EstimatedWeight, + + ReServiceCode: m.ReServiceCode, + + RequestApprovalsRequestedStatus: m.RequestApprovalsRequestedStatus, + }) + 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 international shuttle +func (m *UpdateMTOServiceItemInternationalShuttle) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateID(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 *UpdateMTOServiceItemInternationalShuttle) 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 +} + +var updateMTOServiceItemInternationalShuttleTypeReServiceCodePropEnum []interface{} + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["IDSHUT","IOSHUT"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + updateMTOServiceItemInternationalShuttleTypeReServiceCodePropEnum = append(updateMTOServiceItemInternationalShuttleTypeReServiceCodePropEnum, v) + } +} + +// property enum +func (m *UpdateMTOServiceItemInternationalShuttle) validateReServiceCodeEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, updateMTOServiceItemInternationalShuttleTypeReServiceCodePropEnum, true); err != nil { + return err + } + return nil +} + +func (m *UpdateMTOServiceItemInternationalShuttle) 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 international shuttle based on the context it is used +func (m *UpdateMTOServiceItemInternationalShuttle) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *UpdateMTOServiceItemInternationalShuttle) 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 +} + +// MarshalBinary interface implementation +func (m *UpdateMTOServiceItemInternationalShuttle) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *UpdateMTOServiceItemInternationalShuttle) UnmarshalBinary(b []byte) error { + var res UpdateMTOServiceItemInternationalShuttle + 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 40e86f82729..cc59a427e36 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 @@ -27,6 +27,8 @@ import ( // - DOSHUT - UpdateMTOServiceItemShuttle // - PODFSC - UpdateMTOServiceItemInternationalPortFSC // - POEFSC - UpdateMTOServiceItemInternationalPortFSC +// - IDSHUT - UpdateMTOServiceItemInternationalShuttle +// - IOSHUT - UpdateMTOServiceItemInternationalShuttle // // The documentation will then update with the supported fields. // @@ -52,6 +54,9 @@ const ( // UpdateMTOServiceItemModelTypeUpdateMTOServiceItemInternationalPortFSC captures enum value "UpdateMTOServiceItemInternationalPortFSC" UpdateMTOServiceItemModelTypeUpdateMTOServiceItemInternationalPortFSC UpdateMTOServiceItemModelType = "UpdateMTOServiceItemInternationalPortFSC" + + // UpdateMTOServiceItemModelTypeUpdateMTOServiceItemInternationalShuttle captures enum value "UpdateMTOServiceItemInternationalShuttle" + UpdateMTOServiceItemModelTypeUpdateMTOServiceItemInternationalShuttle UpdateMTOServiceItemModelType = "UpdateMTOServiceItemInternationalShuttle" ) // for schema @@ -59,7 +64,7 @@ var updateMTOServiceItemModelTypeEnum []interface{} func init() { var res []UpdateMTOServiceItemModelType - if err := json.Unmarshal([]byte(`["UpdateMTOServiceItemSIT","UpdateMTOServiceItemShuttle","UpdateMTOServiceItemInternationalPortFSC"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["UpdateMTOServiceItemSIT","UpdateMTOServiceItemShuttle","UpdateMTOServiceItemInternationalPortFSC","UpdateMTOServiceItemInternationalShuttle"]`), &res); err != nil { panic(err) } for _, v := range res { diff --git a/pkg/gen/primev2api/embedded_spec.go b/pkg/gen/primev2api/embedded_spec.go index a564eeb49ce..9e0f66eb6c8 100644 --- a/pkg/gen/primev2api/embedded_spec.go +++ b/pkg/gen/primev2api/embedded_spec.go @@ -1064,6 +1064,12 @@ func init() { "type": "integer", "x-nullable": true, "example": 3 + }, + "weightRestriction": { + "type": "integer", + "x-formatting": "weight", + "x-nullable": true, + "example": 1500 } } }, @@ -1501,14 +1507,73 @@ func init() { } ] }, + "MTOServiceItemInternationalShuttle": { + "description": "Describes an international 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 + }, + "market": { + "description": "To identify whether the service was provided within (CONUS) or (OCONUS)", + "type": "string", + "enum": [ + "CONUS", + "OCONUS" + ], + "example": "CONUS" + }, + "reServiceCode": { + "description": "A unique code for the service item. Indicates if shuttling is requested for the international shipment origin (` + "`" + `IOSHUT` + "`" + `) or destination (` + "`" + `IDSHUT` + "`" + `).\n", + "type": "string", + "enum": [ + "IOSHUT", + "IDSHUT" + ] + }, + "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." + }, + "requestApprovalsRequestedStatus": { + "description": "Indicates if \"Approvals Requested\" status is being requested.", + "type": "boolean", + "x-nullable": true + } + } + } + ] + }, "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 * PODFSC, POEFSC - MTOSerivceItemInternationalFuelSurcharge\n\nThe documentation will then update with the supported fields.\n", + "description": "Describes all model sub-types for a MTOServiceItem model.\n\nUsing this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DOFSIT, DOASIT - MTOServiceItemOriginSIT\n * DDFSIT, DDASIT - MTOServiceItemDestSIT\n * DOSHUT, DDSHUT - MTOServiceItemShuttle\n * IOSHUT, IDSHUT - MTOServiceItemInternationalShuttle\n * DCRT, DUCRT - MTOServiceItemDomesticCrating\n * ICRT, IUCRT - MTOServiceItemInternationalCrating\n * PODFSC, POEFSC - MTOSerivceItemInternationalFuelSurcharge\n\nThe documentation will then update with the supported fields.\n", "type": "string", "enum": [ "MTOServiceItemBasic", "MTOServiceItemOriginSIT", "MTOServiceItemDestSIT", "MTOServiceItemShuttle", + "MTOServiceItemInternationalShuttle", "MTOServiceItemDomesticCrating", "MTOServiceItemInternationalCrating", "MTOSerivceItemInternationalFuelSurcharge" @@ -3073,12 +3138,53 @@ func init() { }, "discriminator": "modelType" }, + "UpdateMTOServiceItemInternationalShuttle": { + "description": "Subtype used to provide the estimated weight and actual weight for shuttle. This is not creating a new service item but rather updating an existing service item.\n", + "allOf": [ + { + "$ref": "#/definitions/UpdateMTOServiceItem" + }, + { + "type": "object", + "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 + }, + "reServiceCode": { + "description": "Service code allowed for this model type.", + "type": "string", + "enum": [ + "IDSHUT", + "IOSHUT" + ] + }, + "requestApprovalsRequestedStatus": { + "description": "Indicates if \"Approvals Requested\" status is being requested.", + "type": "boolean", + "x-nullable": true + } + } + } + ] + }, "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\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 * 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", - "UpdateMTOServiceItemShuttle" + "UpdateMTOServiceItemShuttle", + "UpdateMTOServiceItemInternationalShuttle" ] }, "UpdateMTOServiceItemSIT": { @@ -4722,6 +4828,12 @@ func init() { "type": "integer", "x-nullable": true, "example": 3 + }, + "weightRestriction": { + "type": "integer", + "x-formatting": "weight", + "x-nullable": true, + "example": 1500 } } }, @@ -5159,14 +5271,73 @@ func init() { } ] }, + "MTOServiceItemInternationalShuttle": { + "description": "Describes an international 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 + }, + "market": { + "description": "To identify whether the service was provided within (CONUS) or (OCONUS)", + "type": "string", + "enum": [ + "CONUS", + "OCONUS" + ], + "example": "CONUS" + }, + "reServiceCode": { + "description": "A unique code for the service item. Indicates if shuttling is requested for the international shipment origin (` + "`" + `IOSHUT` + "`" + `) or destination (` + "`" + `IDSHUT` + "`" + `).\n", + "type": "string", + "enum": [ + "IOSHUT", + "IDSHUT" + ] + }, + "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." + }, + "requestApprovalsRequestedStatus": { + "description": "Indicates if \"Approvals Requested\" status is being requested.", + "type": "boolean", + "x-nullable": true + } + } + } + ] + }, "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 * PODFSC, POEFSC - MTOSerivceItemInternationalFuelSurcharge\n\nThe documentation will then update with the supported fields.\n", + "description": "Describes all model sub-types for a MTOServiceItem model.\n\nUsing this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DOFSIT, DOASIT - MTOServiceItemOriginSIT\n * DDFSIT, DDASIT - MTOServiceItemDestSIT\n * DOSHUT, DDSHUT - MTOServiceItemShuttle\n * IOSHUT, IDSHUT - MTOServiceItemInternationalShuttle\n * DCRT, DUCRT - MTOServiceItemDomesticCrating\n * ICRT, IUCRT - MTOServiceItemInternationalCrating\n * PODFSC, POEFSC - MTOSerivceItemInternationalFuelSurcharge\n\nThe documentation will then update with the supported fields.\n", "type": "string", "enum": [ "MTOServiceItemBasic", "MTOServiceItemOriginSIT", "MTOServiceItemDestSIT", "MTOServiceItemShuttle", + "MTOServiceItemInternationalShuttle", "MTOServiceItemDomesticCrating", "MTOServiceItemInternationalCrating", "MTOSerivceItemInternationalFuelSurcharge" @@ -6733,12 +6904,53 @@ func init() { }, "discriminator": "modelType" }, + "UpdateMTOServiceItemInternationalShuttle": { + "description": "Subtype used to provide the estimated weight and actual weight for shuttle. This is not creating a new service item but rather updating an existing service item.\n", + "allOf": [ + { + "$ref": "#/definitions/UpdateMTOServiceItem" + }, + { + "type": "object", + "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 + }, + "reServiceCode": { + "description": "Service code allowed for this model type.", + "type": "string", + "enum": [ + "IDSHUT", + "IOSHUT" + ] + }, + "requestApprovalsRequestedStatus": { + "description": "Indicates if \"Approvals Requested\" status is being requested.", + "type": "boolean", + "x-nullable": true + } + } + } + ] + }, "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\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 * 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", - "UpdateMTOServiceItemShuttle" + "UpdateMTOServiceItemShuttle", + "UpdateMTOServiceItemInternationalShuttle" ] }, "UpdateMTOServiceItemSIT": { diff --git a/pkg/gen/primev2messages/entitlements.go b/pkg/gen/primev2messages/entitlements.go index e29d3f733e3..58280696ab1 100644 --- a/pkg/gen/primev2messages/entitlements.go +++ b/pkg/gen/primev2messages/entitlements.go @@ -79,6 +79,10 @@ type Entitlements struct { // The amount of weight in pounds that the move is entitled for shipment types of Unaccompanied Baggage. // Example: 3 UnaccompaniedBaggageAllowance *int64 `json:"unaccompaniedBaggageAllowance,omitempty"` + + // weight restriction + // Example: 1500 + WeightRestriction *int64 `json:"weightRestriction,omitempty"` } // Validate validates this entitlements diff --git a/pkg/gen/primev2messages/m_t_o_service_item.go b/pkg/gen/primev2messages/m_t_o_service_item.go index 58ae8130bb3..7dfadf4c428 100644 --- a/pkg/gen/primev2messages/m_t_o_service_item.go +++ b/pkg/gen/primev2messages/m_t_o_service_item.go @@ -279,6 +279,12 @@ func unmarshalMTOServiceItem(data []byte, consumer runtime.Consumer) (MTOService 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/primev2messages/m_t_o_service_item_international_shuttle.go b/pkg/gen/primev2messages/m_t_o_service_item_international_shuttle.go new file mode 100644 index 00000000000..3383f8839f2 --- /dev/null +++ b/pkg/gen/primev2messages/m_t_o_service_item_international_shuttle.go @@ -0,0 +1,701 @@ +// 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" +) + +// MTOServiceItemInternationalShuttle Describes an international shuttle service item. +// +// swagger:model MTOServiceItemInternationalShuttle +type MTOServiceItemInternationalShuttle 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"` + + // To identify whether the service was provided within (CONUS) or (OCONUS) + // Example: CONUS + // Enum: [CONUS OCONUS] + Market string `json:"market,omitempty"` + + // A unique code for the service item. Indicates if shuttling is requested for the international shipment origin (`IOSHUT`) or destination (`IDSHUT`). + // + // Required: true + // Enum: [IOSHUT IDSHUT] + 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"` + + // Indicates if "Approvals Requested" status is being requested. + RequestApprovalsRequestedStatus *bool `json:"requestApprovalsRequestedStatus,omitempty"` +} + +// 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 +} + +// LockedPriceCents gets the locked price cents of this subtype +func (m *MTOServiceItemInternationalShuttle) LockedPriceCents() *int64 { + return m.lockedPriceCentsField +} + +// SetLockedPriceCents sets the locked price cents of this subtype +func (m *MTOServiceItemInternationalShuttle) SetLockedPriceCents(val *int64) { + m.lockedPriceCentsField = 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 +} + +// ServiceRequestDocuments gets the service request documents of this subtype +func (m *MTOServiceItemInternationalShuttle) ServiceRequestDocuments() ServiceRequestDocuments { + return m.serviceRequestDocumentsField +} + +// SetServiceRequestDocuments sets the service request documents of this subtype +func (m *MTOServiceItemInternationalShuttle) SetServiceRequestDocuments(val ServiceRequestDocuments) { + m.serviceRequestDocumentsField = 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 { + + // 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"` + + // To identify whether the service was provided within (CONUS) or (OCONUS) + // Example: CONUS + // Enum: [CONUS OCONUS] + Market string `json:"market,omitempty"` + + // A unique code for the service item. Indicates if shuttling is requested for the international shipment origin (`IOSHUT`) or destination (`IDSHUT`). + // + // Required: true + // Enum: [IOSHUT IDSHUT] + 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"` + + // Indicates if "Approvals Requested" status is being requested. + RequestApprovalsRequestedStatus *bool `json:"requestApprovalsRequestedStatus,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 MTOServiceItemInternationalShuttle + + 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.Market = data.Market + result.ReServiceCode = data.ReServiceCode + result.Reason = data.Reason + result.RequestApprovalsRequestedStatus = data.RequestApprovalsRequestedStatus + + *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 { + + // 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"` + + // To identify whether the service was provided within (CONUS) or (OCONUS) + // Example: CONUS + // Enum: [CONUS OCONUS] + Market string `json:"market,omitempty"` + + // A unique code for the service item. Indicates if shuttling is requested for the international shipment origin (`IOSHUT`) or destination (`IDSHUT`). + // + // Required: true + // Enum: [IOSHUT IDSHUT] + 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"` + + // Indicates if "Approvals Requested" status is being requested. + RequestApprovalsRequestedStatus *bool `json:"requestApprovalsRequestedStatus,omitempty"` + }{ + + ActualWeight: m.ActualWeight, + + EstimatedWeight: m.EstimatedWeight, + + Market: m.Market, + + ReServiceCode: m.ReServiceCode, + + Reason: m.Reason, + + RequestApprovalsRequestedStatus: m.RequestApprovalsRequestedStatus, + }) + 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 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.validateServiceRequestDocuments(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) 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 *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.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 *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) 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 *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) 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 *MTOServiceItemInternationalShuttle) 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 *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/primev2messages/m_t_o_service_item_model_type.go b/pkg/gen/primev2messages/m_t_o_service_item_model_type.go index 58755658344..97d0c5272dc 100644 --- a/pkg/gen/primev2messages/m_t_o_service_item_model_type.go +++ b/pkg/gen/primev2messages/m_t_o_service_item_model_type.go @@ -20,6 +20,7 @@ import ( // - DOFSIT, DOASIT - MTOServiceItemOriginSIT // - DDFSIT, DDASIT - MTOServiceItemDestSIT // - DOSHUT, DDSHUT - MTOServiceItemShuttle +// - IOSHUT, IDSHUT - MTOServiceItemInternationalShuttle // - DCRT, DUCRT - MTOServiceItemDomesticCrating // - ICRT, IUCRT - MTOServiceItemInternationalCrating // - PODFSC, POEFSC - MTOSerivceItemInternationalFuelSurcharge @@ -52,6 +53,9 @@ const ( // MTOServiceItemModelTypeMTOServiceItemShuttle captures enum value "MTOServiceItemShuttle" MTOServiceItemModelTypeMTOServiceItemShuttle MTOServiceItemModelType = "MTOServiceItemShuttle" + // MTOServiceItemModelTypeMTOServiceItemInternationalShuttle captures enum value "MTOServiceItemInternationalShuttle" + MTOServiceItemModelTypeMTOServiceItemInternationalShuttle MTOServiceItemModelType = "MTOServiceItemInternationalShuttle" + // MTOServiceItemModelTypeMTOServiceItemDomesticCrating captures enum value "MTOServiceItemDomesticCrating" MTOServiceItemModelTypeMTOServiceItemDomesticCrating MTOServiceItemModelType = "MTOServiceItemDomesticCrating" @@ -67,7 +71,7 @@ var mTOServiceItemModelTypeEnum []interface{} func init() { var res []MTOServiceItemModelType - if err := json.Unmarshal([]byte(`["MTOServiceItemBasic","MTOServiceItemOriginSIT","MTOServiceItemDestSIT","MTOServiceItemShuttle","MTOServiceItemDomesticCrating","MTOServiceItemInternationalCrating","MTOSerivceItemInternationalFuelSurcharge"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["MTOServiceItemBasic","MTOServiceItemOriginSIT","MTOServiceItemDestSIT","MTOServiceItemShuttle","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.go b/pkg/gen/primev2messages/update_m_t_o_service_item.go index 659a4a1904a..43c689afd62 100644 --- a/pkg/gen/primev2messages/update_m_t_o_service_item.go +++ b/pkg/gen/primev2messages/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 "UpdateMTOServiceItemInternationalShuttle": + var result UpdateMTOServiceItemInternationalShuttle + if err := consumer.Consume(buf2, &result); err != nil { + return nil, err + } + return &result, nil case "UpdateMTOServiceItemSIT": var result UpdateMTOServiceItemSIT if err := consumer.Consume(buf2, &result); err != nil { diff --git a/pkg/gen/primev2messages/update_m_t_o_service_item_international_shuttle.go b/pkg/gen/primev2messages/update_m_t_o_service_item_international_shuttle.go new file mode 100644 index 00000000000..081185c8d70 --- /dev/null +++ b/pkg/gen/primev2messages/update_m_t_o_service_item_international_shuttle.go @@ -0,0 +1,276 @@ +// 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" +) + +// UpdateMTOServiceItemInternationalShuttle Subtype used to provide the estimated weight and actual weight for shuttle. This is not creating a new service item but rather updating an existing service item. +// +// swagger:model UpdateMTOServiceItemInternationalShuttle +type UpdateMTOServiceItemInternationalShuttle struct { + idField strfmt.UUID + + // 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"` + + // Service code allowed for this model type. + // Enum: [IDSHUT IOSHUT] + ReServiceCode string `json:"reServiceCode,omitempty"` + + // Indicates if "Approvals Requested" status is being requested. + RequestApprovalsRequestedStatus *bool `json:"requestApprovalsRequestedStatus,omitempty"` +} + +// ID gets the id of this subtype +func (m *UpdateMTOServiceItemInternationalShuttle) ID() strfmt.UUID { + return m.idField +} + +// SetID sets the id of this subtype +func (m *UpdateMTOServiceItemInternationalShuttle) SetID(val strfmt.UUID) { + m.idField = val +} + +// ModelType gets the model type of this subtype +func (m *UpdateMTOServiceItemInternationalShuttle) ModelType() UpdateMTOServiceItemModelType { + return "UpdateMTOServiceItemInternationalShuttle" +} + +// SetModelType sets the model type of this subtype +func (m *UpdateMTOServiceItemInternationalShuttle) SetModelType(val UpdateMTOServiceItemModelType) { +} + +// UnmarshalJSON unmarshals this object with a polymorphic type from a JSON structure +func (m *UpdateMTOServiceItemInternationalShuttle) 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"` + + // Service code allowed for this model type. + // Enum: [IDSHUT IOSHUT] + ReServiceCode string `json:"reServiceCode,omitempty"` + + // Indicates if "Approvals Requested" status is being requested. + RequestApprovalsRequestedStatus *bool `json:"requestApprovalsRequestedStatus,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 UpdateMTOServiceItemInternationalShuttle + + 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.ActualWeight = data.ActualWeight + result.EstimatedWeight = data.EstimatedWeight + result.ReServiceCode = data.ReServiceCode + result.RequestApprovalsRequestedStatus = data.RequestApprovalsRequestedStatus + + *m = result + + return nil +} + +// MarshalJSON marshals this object with a polymorphic type to a JSON structure +func (m UpdateMTOServiceItemInternationalShuttle) 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"` + + // Service code allowed for this model type. + // Enum: [IDSHUT IOSHUT] + ReServiceCode string `json:"reServiceCode,omitempty"` + + // Indicates if "Approvals Requested" status is being requested. + RequestApprovalsRequestedStatus *bool `json:"requestApprovalsRequestedStatus,omitempty"` + }{ + + ActualWeight: m.ActualWeight, + + EstimatedWeight: m.EstimatedWeight, + + ReServiceCode: m.ReServiceCode, + + RequestApprovalsRequestedStatus: m.RequestApprovalsRequestedStatus, + }) + 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 international shuttle +func (m *UpdateMTOServiceItemInternationalShuttle) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateID(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 *UpdateMTOServiceItemInternationalShuttle) 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 +} + +var updateMTOServiceItemInternationalShuttleTypeReServiceCodePropEnum []interface{} + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["IDSHUT","IOSHUT"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + updateMTOServiceItemInternationalShuttleTypeReServiceCodePropEnum = append(updateMTOServiceItemInternationalShuttleTypeReServiceCodePropEnum, v) + } +} + +// property enum +func (m *UpdateMTOServiceItemInternationalShuttle) validateReServiceCodeEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, updateMTOServiceItemInternationalShuttleTypeReServiceCodePropEnum, true); err != nil { + return err + } + return nil +} + +func (m *UpdateMTOServiceItemInternationalShuttle) 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 international shuttle based on the context it is used +func (m *UpdateMTOServiceItemInternationalShuttle) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *UpdateMTOServiceItemInternationalShuttle) 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 +} + +// MarshalBinary interface implementation +func (m *UpdateMTOServiceItemInternationalShuttle) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *UpdateMTOServiceItemInternationalShuttle) UnmarshalBinary(b []byte) error { + var res UpdateMTOServiceItemInternationalShuttle + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} 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 6d9ea3d53ee..8b865cfec1f 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 @@ -21,6 +21,8 @@ import ( // - DOFSIT - UpdateMTOServiceItemSIT // - DDSHUT - UpdateMTOServiceItemShuttle // - DOSHUT - UpdateMTOServiceItemShuttle +// - IDSHUT - UpdateMTOServiceItemInternationalShuttle +// - IOSHUT - UpdateMTOServiceItemInternationalShuttle // // The documentation will then update with the supported fields. // @@ -43,6 +45,9 @@ const ( // UpdateMTOServiceItemModelTypeUpdateMTOServiceItemShuttle captures enum value "UpdateMTOServiceItemShuttle" UpdateMTOServiceItemModelTypeUpdateMTOServiceItemShuttle UpdateMTOServiceItemModelType = "UpdateMTOServiceItemShuttle" + + // UpdateMTOServiceItemModelTypeUpdateMTOServiceItemInternationalShuttle captures enum value "UpdateMTOServiceItemInternationalShuttle" + UpdateMTOServiceItemModelTypeUpdateMTOServiceItemInternationalShuttle UpdateMTOServiceItemModelType = "UpdateMTOServiceItemInternationalShuttle" ) // for schema @@ -50,7 +55,7 @@ var updateMTOServiceItemModelTypeEnum []interface{} func init() { var res []UpdateMTOServiceItemModelType - if err := json.Unmarshal([]byte(`["UpdateMTOServiceItemSIT","UpdateMTOServiceItemShuttle"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["UpdateMTOServiceItemSIT","UpdateMTOServiceItemShuttle","UpdateMTOServiceItemInternationalShuttle"]`), &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 7f05d250550..e5bde117279 100644 --- a/pkg/gen/primev3api/embedded_spec.go +++ b/pkg/gen/primev3api/embedded_spec.go @@ -1226,6 +1226,12 @@ func init() { "type": "integer", "x-nullable": true, "example": 3 + }, + "weightRestriction": { + "type": "integer", + "x-formatting": "weight", + "x-nullable": true, + "example": 1500 } } }, @@ -1688,14 +1694,73 @@ func init() { } ] }, + "MTOServiceItemInternationalShuttle": { + "description": "Describes an international 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 + }, + "market": { + "description": "To identify whether the service was provided within (CONUS) or (OCONUS)", + "type": "string", + "enum": [ + "CONUS", + "OCONUS" + ], + "example": "CONUS" + }, + "reServiceCode": { + "description": "A unique code for the service item. Indicates if shuttling is requested for the international shipment origin (` + "`" + `IOSHUT` + "`" + `) or destination (` + "`" + `IDSHUT` + "`" + `).\n", + "type": "string", + "enum": [ + "IOSHUT", + "IDSHUT" + ] + }, + "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." + }, + "requestApprovalsRequestedStatus": { + "description": "Indicates if \"Approvals Requested\" status is being requested.", + "type": "boolean", + "x-nullable": true + } + } + } + ] + }, "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 * PODFSC, POEFSC - MTOSerivceItemInternationalFuelSurcharge\n\nThe documentation will then update with the supported fields.\n", + "description": "Describes all model sub-types for a MTOServiceItem model.\n\nUsing this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DOFSIT, DOASIT - MTOServiceItemOriginSIT\n * DDFSIT, DDASIT - MTOServiceItemDestSIT\n * DOSHUT, DDSHUT - MTOServiceItemShuttle\n * IOSHUT, IDSHUT - MTOServiceItemInternationalShuttle\n * DCRT, DUCRT - MTOServiceItemDomesticCrating\n * ICRT, IUCRT - MTOServiceItemInternationalCrating\n * PODFSC, POEFSC - MTOSerivceItemInternationalFuelSurcharge\n\nThe documentation will then update with the supported fields.\n", "type": "string", "enum": [ "MTOServiceItemBasic", "MTOServiceItemOriginSIT", "MTOServiceItemDestSIT", "MTOServiceItemShuttle", + "MTOServiceItemInternationalShuttle", "MTOServiceItemDomesticCrating", "MTOServiceItemInternationalCrating", "MTOSerivceItemInternationalFuelSurcharge" @@ -3691,12 +3756,53 @@ func init() { }, "discriminator": "modelType" }, + "UpdateMTOServiceItemInternationalShuttle": { + "description": "Subtype used to provide the estimated weight and actual weight for shuttle. This is not creating a new service item but rather updating an existing service item.\n", + "allOf": [ + { + "$ref": "#/definitions/UpdateMTOServiceItem" + }, + { + "type": "object", + "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 + }, + "reServiceCode": { + "description": "Service code allowed for this model type.", + "type": "string", + "enum": [ + "IDSHUT", + "IOSHUT" + ] + }, + "requestApprovalsRequestedStatus": { + "description": "Indicates if \"Approvals Requested\" status is being requested.", + "type": "boolean", + "x-nullable": true + } + } + } + ] + }, "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\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 * 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", - "UpdateMTOServiceItemShuttle" + "UpdateMTOServiceItemShuttle", + "UpdateMTOServiceItemInternationalShuttle" ] }, "UpdateMTOServiceItemSIT": { @@ -5594,6 +5700,12 @@ func init() { "type": "integer", "x-nullable": true, "example": 3 + }, + "weightRestriction": { + "type": "integer", + "x-formatting": "weight", + "x-nullable": true, + "example": 1500 } } }, @@ -6056,14 +6168,73 @@ func init() { } ] }, + "MTOServiceItemInternationalShuttle": { + "description": "Describes an international 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 + }, + "market": { + "description": "To identify whether the service was provided within (CONUS) or (OCONUS)", + "type": "string", + "enum": [ + "CONUS", + "OCONUS" + ], + "example": "CONUS" + }, + "reServiceCode": { + "description": "A unique code for the service item. Indicates if shuttling is requested for the international shipment origin (` + "`" + `IOSHUT` + "`" + `) or destination (` + "`" + `IDSHUT` + "`" + `).\n", + "type": "string", + "enum": [ + "IOSHUT", + "IDSHUT" + ] + }, + "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." + }, + "requestApprovalsRequestedStatus": { + "description": "Indicates if \"Approvals Requested\" status is being requested.", + "type": "boolean", + "x-nullable": true + } + } + } + ] + }, "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 * PODFSC, POEFSC - MTOSerivceItemInternationalFuelSurcharge\n\nThe documentation will then update with the supported fields.\n", + "description": "Describes all model sub-types for a MTOServiceItem model.\n\nUsing this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DOFSIT, DOASIT - MTOServiceItemOriginSIT\n * DDFSIT, DDASIT - MTOServiceItemDestSIT\n * DOSHUT, DDSHUT - MTOServiceItemShuttle\n * IOSHUT, IDSHUT - MTOServiceItemInternationalShuttle\n * DCRT, DUCRT - MTOServiceItemDomesticCrating\n * ICRT, IUCRT - MTOServiceItemInternationalCrating\n * PODFSC, POEFSC - MTOSerivceItemInternationalFuelSurcharge\n\nThe documentation will then update with the supported fields.\n", "type": "string", "enum": [ "MTOServiceItemBasic", "MTOServiceItemOriginSIT", "MTOServiceItemDestSIT", "MTOServiceItemShuttle", + "MTOServiceItemInternationalShuttle", "MTOServiceItemDomesticCrating", "MTOServiceItemInternationalCrating", "MTOSerivceItemInternationalFuelSurcharge" @@ -8061,12 +8232,53 @@ func init() { }, "discriminator": "modelType" }, + "UpdateMTOServiceItemInternationalShuttle": { + "description": "Subtype used to provide the estimated weight and actual weight for shuttle. This is not creating a new service item but rather updating an existing service item.\n", + "allOf": [ + { + "$ref": "#/definitions/UpdateMTOServiceItem" + }, + { + "type": "object", + "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 + }, + "reServiceCode": { + "description": "Service code allowed for this model type.", + "type": "string", + "enum": [ + "IDSHUT", + "IOSHUT" + ] + }, + "requestApprovalsRequestedStatus": { + "description": "Indicates if \"Approvals Requested\" status is being requested.", + "type": "boolean", + "x-nullable": true + } + } + } + ] + }, "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\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 * 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", - "UpdateMTOServiceItemShuttle" + "UpdateMTOServiceItemShuttle", + "UpdateMTOServiceItemInternationalShuttle" ] }, "UpdateMTOServiceItemSIT": { diff --git a/pkg/gen/primev3messages/entitlements.go b/pkg/gen/primev3messages/entitlements.go index 1e228c6350f..2ef73ccfbf5 100644 --- a/pkg/gen/primev3messages/entitlements.go +++ b/pkg/gen/primev3messages/entitlements.go @@ -79,6 +79,10 @@ type Entitlements struct { // The amount of weight in pounds that the move is entitled for shipment types of Unaccompanied Baggage. // Example: 3 UnaccompaniedBaggageAllowance *int64 `json:"unaccompaniedBaggageAllowance,omitempty"` + + // weight restriction + // Example: 1500 + WeightRestriction *int64 `json:"weightRestriction,omitempty"` } // Validate validates this entitlements diff --git a/pkg/gen/primev3messages/m_t_o_service_item.go b/pkg/gen/primev3messages/m_t_o_service_item.go index d6861255a80..75d33c217f1 100644 --- a/pkg/gen/primev3messages/m_t_o_service_item.go +++ b/pkg/gen/primev3messages/m_t_o_service_item.go @@ -285,6 +285,12 @@ func unmarshalMTOServiceItem(data []byte, consumer runtime.Consumer) (MTOService 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/primev3messages/m_t_o_service_item_international_shuttle.go b/pkg/gen/primev3messages/m_t_o_service_item_international_shuttle.go new file mode 100644 index 00000000000..6b03f772b77 --- /dev/null +++ b/pkg/gen/primev3messages/m_t_o_service_item_international_shuttle.go @@ -0,0 +1,701 @@ +// 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" +) + +// MTOServiceItemInternationalShuttle Describes an international shuttle service item. +// +// swagger:model MTOServiceItemInternationalShuttle +type MTOServiceItemInternationalShuttle 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"` + + // To identify whether the service was provided within (CONUS) or (OCONUS) + // Example: CONUS + // Enum: [CONUS OCONUS] + Market string `json:"market,omitempty"` + + // A unique code for the service item. Indicates if shuttling is requested for the international shipment origin (`IOSHUT`) or destination (`IDSHUT`). + // + // Required: true + // Enum: [IOSHUT IDSHUT] + 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"` + + // Indicates if "Approvals Requested" status is being requested. + RequestApprovalsRequestedStatus *bool `json:"requestApprovalsRequestedStatus,omitempty"` +} + +// 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 +} + +// LockedPriceCents gets the locked price cents of this subtype +func (m *MTOServiceItemInternationalShuttle) LockedPriceCents() *int64 { + return m.lockedPriceCentsField +} + +// SetLockedPriceCents sets the locked price cents of this subtype +func (m *MTOServiceItemInternationalShuttle) SetLockedPriceCents(val *int64) { + m.lockedPriceCentsField = 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 +} + +// ServiceRequestDocuments gets the service request documents of this subtype +func (m *MTOServiceItemInternationalShuttle) ServiceRequestDocuments() ServiceRequestDocuments { + return m.serviceRequestDocumentsField +} + +// SetServiceRequestDocuments sets the service request documents of this subtype +func (m *MTOServiceItemInternationalShuttle) SetServiceRequestDocuments(val ServiceRequestDocuments) { + m.serviceRequestDocumentsField = 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 { + + // 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"` + + // To identify whether the service was provided within (CONUS) or (OCONUS) + // Example: CONUS + // Enum: [CONUS OCONUS] + Market string `json:"market,omitempty"` + + // A unique code for the service item. Indicates if shuttling is requested for the international shipment origin (`IOSHUT`) or destination (`IDSHUT`). + // + // Required: true + // Enum: [IOSHUT IDSHUT] + 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"` + + // Indicates if "Approvals Requested" status is being requested. + RequestApprovalsRequestedStatus *bool `json:"requestApprovalsRequestedStatus,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 MTOServiceItemInternationalShuttle + + 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.Market = data.Market + result.ReServiceCode = data.ReServiceCode + result.Reason = data.Reason + result.RequestApprovalsRequestedStatus = data.RequestApprovalsRequestedStatus + + *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 { + + // 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"` + + // To identify whether the service was provided within (CONUS) or (OCONUS) + // Example: CONUS + // Enum: [CONUS OCONUS] + Market string `json:"market,omitempty"` + + // A unique code for the service item. Indicates if shuttling is requested for the international shipment origin (`IOSHUT`) or destination (`IDSHUT`). + // + // Required: true + // Enum: [IOSHUT IDSHUT] + 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"` + + // Indicates if "Approvals Requested" status is being requested. + RequestApprovalsRequestedStatus *bool `json:"requestApprovalsRequestedStatus,omitempty"` + }{ + + ActualWeight: m.ActualWeight, + + EstimatedWeight: m.EstimatedWeight, + + Market: m.Market, + + ReServiceCode: m.ReServiceCode, + + Reason: m.Reason, + + RequestApprovalsRequestedStatus: m.RequestApprovalsRequestedStatus, + }) + 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 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.validateServiceRequestDocuments(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) 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 *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.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 *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) 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 *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) 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 *MTOServiceItemInternationalShuttle) 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 *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/primev3messages/m_t_o_service_item_model_type.go b/pkg/gen/primev3messages/m_t_o_service_item_model_type.go index 50a9d22e418..53d2a0450f6 100644 --- a/pkg/gen/primev3messages/m_t_o_service_item_model_type.go +++ b/pkg/gen/primev3messages/m_t_o_service_item_model_type.go @@ -20,6 +20,7 @@ import ( // - DOFSIT, DOASIT - MTOServiceItemOriginSIT // - DDFSIT, DDASIT - MTOServiceItemDestSIT // - DOSHUT, DDSHUT - MTOServiceItemShuttle +// - IOSHUT, IDSHUT - MTOServiceItemInternationalShuttle // - DCRT, DUCRT - MTOServiceItemDomesticCrating // - ICRT, IUCRT - MTOServiceItemInternationalCrating // - PODFSC, POEFSC - MTOSerivceItemInternationalFuelSurcharge @@ -52,6 +53,9 @@ const ( // MTOServiceItemModelTypeMTOServiceItemShuttle captures enum value "MTOServiceItemShuttle" MTOServiceItemModelTypeMTOServiceItemShuttle MTOServiceItemModelType = "MTOServiceItemShuttle" + // MTOServiceItemModelTypeMTOServiceItemInternationalShuttle captures enum value "MTOServiceItemInternationalShuttle" + MTOServiceItemModelTypeMTOServiceItemInternationalShuttle MTOServiceItemModelType = "MTOServiceItemInternationalShuttle" + // MTOServiceItemModelTypeMTOServiceItemDomesticCrating captures enum value "MTOServiceItemDomesticCrating" MTOServiceItemModelTypeMTOServiceItemDomesticCrating MTOServiceItemModelType = "MTOServiceItemDomesticCrating" @@ -67,7 +71,7 @@ var mTOServiceItemModelTypeEnum []interface{} func init() { var res []MTOServiceItemModelType - if err := json.Unmarshal([]byte(`["MTOServiceItemBasic","MTOServiceItemOriginSIT","MTOServiceItemDestSIT","MTOServiceItemShuttle","MTOServiceItemDomesticCrating","MTOServiceItemInternationalCrating","MTOSerivceItemInternationalFuelSurcharge"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["MTOServiceItemBasic","MTOServiceItemOriginSIT","MTOServiceItemDestSIT","MTOServiceItemShuttle","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.go b/pkg/gen/primev3messages/update_m_t_o_service_item.go index 458956f1f1d..6f16095105d 100644 --- a/pkg/gen/primev3messages/update_m_t_o_service_item.go +++ b/pkg/gen/primev3messages/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 "UpdateMTOServiceItemInternationalShuttle": + var result UpdateMTOServiceItemInternationalShuttle + if err := consumer.Consume(buf2, &result); err != nil { + return nil, err + } + return &result, nil case "UpdateMTOServiceItemSIT": var result UpdateMTOServiceItemSIT if err := consumer.Consume(buf2, &result); err != nil { diff --git a/pkg/gen/primev3messages/update_m_t_o_service_item_international_shuttle.go b/pkg/gen/primev3messages/update_m_t_o_service_item_international_shuttle.go new file mode 100644 index 00000000000..4686d0e8474 --- /dev/null +++ b/pkg/gen/primev3messages/update_m_t_o_service_item_international_shuttle.go @@ -0,0 +1,276 @@ +// 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" +) + +// UpdateMTOServiceItemInternationalShuttle Subtype used to provide the estimated weight and actual weight for shuttle. This is not creating a new service item but rather updating an existing service item. +// +// swagger:model UpdateMTOServiceItemInternationalShuttle +type UpdateMTOServiceItemInternationalShuttle struct { + idField strfmt.UUID + + // 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"` + + // Service code allowed for this model type. + // Enum: [IDSHUT IOSHUT] + ReServiceCode string `json:"reServiceCode,omitempty"` + + // Indicates if "Approvals Requested" status is being requested. + RequestApprovalsRequestedStatus *bool `json:"requestApprovalsRequestedStatus,omitempty"` +} + +// ID gets the id of this subtype +func (m *UpdateMTOServiceItemInternationalShuttle) ID() strfmt.UUID { + return m.idField +} + +// SetID sets the id of this subtype +func (m *UpdateMTOServiceItemInternationalShuttle) SetID(val strfmt.UUID) { + m.idField = val +} + +// ModelType gets the model type of this subtype +func (m *UpdateMTOServiceItemInternationalShuttle) ModelType() UpdateMTOServiceItemModelType { + return "UpdateMTOServiceItemInternationalShuttle" +} + +// SetModelType sets the model type of this subtype +func (m *UpdateMTOServiceItemInternationalShuttle) SetModelType(val UpdateMTOServiceItemModelType) { +} + +// UnmarshalJSON unmarshals this object with a polymorphic type from a JSON structure +func (m *UpdateMTOServiceItemInternationalShuttle) 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"` + + // Service code allowed for this model type. + // Enum: [IDSHUT IOSHUT] + ReServiceCode string `json:"reServiceCode,omitempty"` + + // Indicates if "Approvals Requested" status is being requested. + RequestApprovalsRequestedStatus *bool `json:"requestApprovalsRequestedStatus,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 UpdateMTOServiceItemInternationalShuttle + + 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.ActualWeight = data.ActualWeight + result.EstimatedWeight = data.EstimatedWeight + result.ReServiceCode = data.ReServiceCode + result.RequestApprovalsRequestedStatus = data.RequestApprovalsRequestedStatus + + *m = result + + return nil +} + +// MarshalJSON marshals this object with a polymorphic type to a JSON structure +func (m UpdateMTOServiceItemInternationalShuttle) 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"` + + // Service code allowed for this model type. + // Enum: [IDSHUT IOSHUT] + ReServiceCode string `json:"reServiceCode,omitempty"` + + // Indicates if "Approvals Requested" status is being requested. + RequestApprovalsRequestedStatus *bool `json:"requestApprovalsRequestedStatus,omitempty"` + }{ + + ActualWeight: m.ActualWeight, + + EstimatedWeight: m.EstimatedWeight, + + ReServiceCode: m.ReServiceCode, + + RequestApprovalsRequestedStatus: m.RequestApprovalsRequestedStatus, + }) + 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 international shuttle +func (m *UpdateMTOServiceItemInternationalShuttle) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateID(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 *UpdateMTOServiceItemInternationalShuttle) 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 +} + +var updateMTOServiceItemInternationalShuttleTypeReServiceCodePropEnum []interface{} + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["IDSHUT","IOSHUT"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + updateMTOServiceItemInternationalShuttleTypeReServiceCodePropEnum = append(updateMTOServiceItemInternationalShuttleTypeReServiceCodePropEnum, v) + } +} + +// property enum +func (m *UpdateMTOServiceItemInternationalShuttle) validateReServiceCodeEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, updateMTOServiceItemInternationalShuttleTypeReServiceCodePropEnum, true); err != nil { + return err + } + return nil +} + +func (m *UpdateMTOServiceItemInternationalShuttle) 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 international shuttle based on the context it is used +func (m *UpdateMTOServiceItemInternationalShuttle) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *UpdateMTOServiceItemInternationalShuttle) 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 +} + +// MarshalBinary interface implementation +func (m *UpdateMTOServiceItemInternationalShuttle) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *UpdateMTOServiceItemInternationalShuttle) UnmarshalBinary(b []byte) error { + var res UpdateMTOServiceItemInternationalShuttle + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} 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 a1d95c43bb3..a1bc8152ec6 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 @@ -21,6 +21,8 @@ import ( // - DOFSIT - UpdateMTOServiceItemSIT // - DDSHUT - UpdateMTOServiceItemShuttle // - DOSHUT - UpdateMTOServiceItemShuttle +// - IDSHUT - UpdateMTOServiceItemInternationalShuttle +// - IOSHUT - UpdateMTOServiceItemInternationalShuttle // // The documentation will then update with the supported fields. // @@ -43,6 +45,9 @@ const ( // UpdateMTOServiceItemModelTypeUpdateMTOServiceItemShuttle captures enum value "UpdateMTOServiceItemShuttle" UpdateMTOServiceItemModelTypeUpdateMTOServiceItemShuttle UpdateMTOServiceItemModelType = "UpdateMTOServiceItemShuttle" + + // UpdateMTOServiceItemModelTypeUpdateMTOServiceItemInternationalShuttle captures enum value "UpdateMTOServiceItemInternationalShuttle" + UpdateMTOServiceItemModelTypeUpdateMTOServiceItemInternationalShuttle UpdateMTOServiceItemModelType = "UpdateMTOServiceItemInternationalShuttle" ) // for schema @@ -50,7 +55,7 @@ var updateMTOServiceItemModelTypeEnum []interface{} func init() { var res []UpdateMTOServiceItemModelType - if err := json.Unmarshal([]byte(`["UpdateMTOServiceItemSIT","UpdateMTOServiceItemShuttle"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["UpdateMTOServiceItemSIT","UpdateMTOServiceItemShuttle","UpdateMTOServiceItemInternationalShuttle"]`), &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 63314742252..b794a05a20e 100644 --- a/pkg/gen/supportapi/embedded_spec.go +++ b/pkg/gen/supportapi/embedded_spec.go @@ -1183,6 +1183,11 @@ func init() { "type": "integer", "x-nullable": true, "example": 3 + }, + "weightRestriction": { + "type": "integer", + "x-nullable": true, + "example": 1500 } } }, @@ -1539,6 +1544,7 @@ func init() { "MTOServiceItemOriginSIT", "MTOServiceItemDestSIT", "MTOServiceItemShuttle", + "MTOServiceItemInternationalShuttle", "MTOServiceItemDomesticCrating", "MTOServiceItemInternationalCrating" ] @@ -4050,6 +4056,11 @@ func init() { "type": "integer", "x-nullable": true, "example": 3 + }, + "weightRestriction": { + "type": "integer", + "x-nullable": true, + "example": 1500 } } }, @@ -4406,6 +4417,7 @@ func init() { "MTOServiceItemOriginSIT", "MTOServiceItemDestSIT", "MTOServiceItemShuttle", + "MTOServiceItemInternationalShuttle", "MTOServiceItemDomesticCrating", "MTOServiceItemInternationalCrating" ] diff --git a/pkg/gen/supportmessages/entitlement.go b/pkg/gen/supportmessages/entitlement.go index 434ad8aeed3..f1fd0f5f8c9 100644 --- a/pkg/gen/supportmessages/entitlement.go +++ b/pkg/gen/supportmessages/entitlement.go @@ -81,6 +81,10 @@ type Entitlement struct { // The amount of weight in pounds that the move is entitled for shipment types of Unaccompanied Baggage. // Example: 3 UnaccompaniedBaggageAllowance *int64 `json:"unaccompaniedBaggageAllowance,omitempty"` + + // weight restriction + // Example: 1500 + WeightRestriction *int64 `json:"weightRestriction,omitempty"` } // Validate validates this entitlement diff --git a/pkg/gen/supportmessages/m_t_o_service_item_model_type.go b/pkg/gen/supportmessages/m_t_o_service_item_model_type.go index 9a949e919da..3f957023e8a 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 @@ -51,6 +51,9 @@ const ( // MTOServiceItemModelTypeMTOServiceItemShuttle captures enum value "MTOServiceItemShuttle" MTOServiceItemModelTypeMTOServiceItemShuttle MTOServiceItemModelType = "MTOServiceItemShuttle" + // MTOServiceItemModelTypeMTOServiceItemInternationalShuttle captures enum value "MTOServiceItemInternationalShuttle" + MTOServiceItemModelTypeMTOServiceItemInternationalShuttle MTOServiceItemModelType = "MTOServiceItemInternationalShuttle" + // MTOServiceItemModelTypeMTOServiceItemDomesticCrating captures enum value "MTOServiceItemDomesticCrating" MTOServiceItemModelTypeMTOServiceItemDomesticCrating MTOServiceItemModelType = "MTOServiceItemDomesticCrating" @@ -63,7 +66,7 @@ var mTOServiceItemModelTypeEnum []interface{} func init() { var res []MTOServiceItemModelType - if err := json.Unmarshal([]byte(`["MTOServiceItemBasic","MTOServiceItemOriginSIT","MTOServiceItemDestSIT","MTOServiceItemShuttle","MTOServiceItemDomesticCrating","MTOServiceItemInternationalCrating"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["MTOServiceItemBasic","MTOServiceItemOriginSIT","MTOServiceItemDestSIT","MTOServiceItemShuttle","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 34d6729a0f2..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), @@ -112,6 +115,11 @@ func NewAdminAPI(handlerConfig handlers.HandlerConfig) *adminops.MymoveAPI { transportaionOfficeAssignmentUpdater, } + adminAPI.OfficeUsersDeleteOfficeUserHandler = DeleteOfficeUserHandler{ + handlerConfig, + officeuser.NewOfficeUserDeleter(queryBuilder), + } + adminAPI.TransportationOfficesIndexOfficesHandler = IndexOfficesHandler{ handlerConfig, office.NewOfficeListFetcher(queryBuilder), @@ -119,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/moves_test.go b/pkg/handlers/adminapi/moves_test.go index bfc3291df46..dfeabc2ee36 100644 --- a/pkg/handlers/adminapi/moves_test.go +++ b/pkg/handlers/adminapi/moves_test.go @@ -118,7 +118,6 @@ func (suite *HandlerSuite) TestUpdateMoveHandler() { mock.Anything, mock.Anything, false, - false, ).Return(400, nil) return UpdateMoveHandler{ suite.HandlerConfig(), diff --git a/pkg/handlers/adminapi/office_users.go b/pkg/handlers/adminapi/office_users.go index 5983e514804..bb2bc65199c 100644 --- a/pkg/handlers/adminapi/office_users.go +++ b/pkg/handlers/adminapi/office_users.go @@ -586,3 +586,37 @@ func getPrimaryTransportationOfficeIDFromPayload(payload []*adminmessages.Office return transportationOfficeID, apperror.NewBadDataError("Could not identify primary transportaion office from list of assignments") } + +// DeleteOfficeUserHandler deletes an office user via DELETE /office_user/{officeUserId} +type DeleteOfficeUserHandler struct { + handlers.HandlerConfig + services.OfficeUserDeleter +} + +func (h DeleteOfficeUserHandler) Handle(params officeuserop.DeleteOfficeUserParams) middleware.Responder { + return h.AuditableAppContextFromRequestWithErrors(params.HTTPRequest, + func(appCtx appcontext.AppContext) (middleware.Responder, error) { + + // we only allow this to be called from the admin app + if !appCtx.Session().IsAdminApp() { + return officeuserop.NewDeleteOfficeUserUnauthorized(), nil + } + + officeUserID, err := uuid.FromString(params.OfficeUserID.String()) + if err != nil { + return handlers.ResponseForError(appCtx.Logger(), err), err + } + + err = h.OfficeUserDeleter.DeleteOfficeUser(appCtx, officeUserID) + if err != nil { + switch err.(type) { + case apperror.NotFoundError: + return officeuserop.NewDeleteOfficeUserNotFound(), err + default: + return officeuserop.NewDeleteOfficeUserInternalServerError(), err + } + } + + return officeuserop.NewDeleteOfficeUserNoContent(), nil + }) +} diff --git a/pkg/handlers/adminapi/office_users_test.go b/pkg/handlers/adminapi/office_users_test.go index 33b57c791a2..b2386b96a2e 100644 --- a/pkg/handlers/adminapi/office_users_test.go +++ b/pkg/handlers/adminapi/office_users_test.go @@ -4,12 +4,14 @@ import ( "database/sql" "fmt" "net/http" + "net/http/httptest" "github.com/go-openapi/strfmt" "github.com/gobuffalo/validate/v3" "github.com/gofrs/uuid" "github.com/stretchr/testify/mock" + "github.com/transcom/mymove/pkg/auth" "github.com/transcom/mymove/pkg/factory" officeuserop "github.com/transcom/mymove/pkg/gen/adminapi/adminoperations/office_users" "github.com/transcom/mymove/pkg/gen/adminmessages" @@ -751,3 +753,107 @@ func (suite *HandlerSuite) TestUpdateOfficeUserHandler() { mockRevoker.AssertNumberOfCalls(suite.T(), "RevokeUserSession", 1) }) } + +func (suite *HandlerSuite) TestDeleteOfficeUsersHandler() { + suite.Run("deleted requested users results in no content (successful) response", func() { + user := factory.BuildDefaultUser(suite.DB()) + status := models.OfficeUserStatusREQUESTED + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + { + Model: models.OfficeUser{ + Active: true, + UserID: &user.ID, + Email: user.OktaEmail, + Status: &status, + }, + }, + { + Model: user, + LinkOnly: true, + }, + }, []roles.RoleType{roles.RoleTypeTOO}) + officeUserID := officeUser.ID + + params := officeuserop.DeleteOfficeUserParams{ + HTTPRequest: suite.setupAuthenticatedRequest("DELETE", fmt.Sprintf("/office_users/%s", officeUserID)), + OfficeUserID: *handlers.FmtUUID(officeUserID), + } + + queryBuilder := query.NewQueryBuilder() + handler := DeleteOfficeUserHandler{ + HandlerConfig: suite.HandlerConfig(), + OfficeUserDeleter: officeuser.NewOfficeUserDeleter(queryBuilder), + } + + response := handler.Handle(params) + + suite.IsType(&officeuserop.DeleteOfficeUserNoContent{}, response) + + var dbUser models.User + err := suite.DB().Where("id = ?", user.ID).First(&dbUser) + suite.Error(err) + suite.Equal(sql.ErrNoRows, err, "sql: no rows in result set") + + var dbOfficeUser models.OfficeUser + err = suite.DB().Where("user_id = ?", user.ID).First(&dbOfficeUser) + suite.Error(err) + suite.Equal(sql.ErrNoRows, err, "sql: no rows in result set") + + // .All does not return a sql no rows error, so we will verify that the struct is empty + var userRoles []models.UsersRoles + err = suite.DB().Where("user_id = ?", user.ID).All(&userRoles) + suite.NoError(err) + suite.Empty(userRoles, "Expected no roles to remain for the user") + + var userPrivileges []models.UsersPrivileges + err = suite.DB().Where("user_id = ?", user.ID).All(&userPrivileges) + suite.NoError(err) + suite.Empty(userPrivileges, "Expected no privileges to remain for the user") + }) + + suite.Run("get an error when the office user does not exist", func() { + officeUserID := uuid.Must(uuid.NewV4()) + + params := officeuserop.DeleteOfficeUserParams{ + HTTPRequest: suite.setupAuthenticatedRequest("DELETE", fmt.Sprintf("/office_users/%s", officeUserID)), + OfficeUserID: *handlers.FmtUUID(officeUserID), + } + + queryBuilder := query.NewQueryBuilder() + handler := DeleteOfficeUserHandler{ + HandlerConfig: suite.HandlerConfig(), + OfficeUserDeleter: officeuser.NewOfficeUserDeleter(queryBuilder), + } + + response := handler.Handle(params) + + suite.IsType(&officeuserop.DeleteOfficeUserNotFound{}, response) + }) + + suite.Run("error response when a user is not in the admin application", func() { + officeUser := factory.BuildOfficeUser(suite.DB(), nil, nil) + officeUserID := officeUser.ID + req := httptest.NewRequest("DELETE", fmt.Sprintf("/office_users/%s", officeUserID), nil) + + session := &auth.Session{ + ApplicationName: auth.OfficeApp, + OfficeUserID: officeUserID, + } + ctx := auth.SetSessionInRequestContext(req, session) + + params := officeuserop.DeleteOfficeUserParams{ + HTTPRequest: req.WithContext(ctx), + OfficeUserID: *handlers.FmtUUID(officeUserID), + } + + queryBuilder := query.NewQueryBuilder() + handler := DeleteOfficeUserHandler{ + HandlerConfig: suite.HandlerConfig(), + OfficeUserDeleter: officeuser.NewOfficeUserDeleter(queryBuilder), + } + + response := handler.Handle(params) + + suite.IsType(&officeuserop.DeleteOfficeUserUnauthorized{}, response) + }) +} diff --git a/pkg/handlers/adminapi/requested_office_users.go b/pkg/handlers/adminapi/requested_office_users.go index ece06237578..ceee52b22c3 100644 --- a/pkg/handlers/adminapi/requested_office_users.go +++ b/pkg/handlers/adminapi/requested_office_users.go @@ -21,6 +21,7 @@ import ( "github.com/transcom/mymove/pkg/handlers" "github.com/transcom/mymove/pkg/handlers/authentication/okta" "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/models/roles" "github.com/transcom/mymove/pkg/notifications" "github.com/transcom/mymove/pkg/services" "github.com/transcom/mymove/pkg/services/query" @@ -148,12 +149,55 @@ func CreateOfficeOktaAccount(appCtx appcontext.AppContext, params requested_offi return res, nil } +// Function that filters Requested Office Users based on filtered Transportation Offices +func filterByTransportationOffice(officeUsers models.OfficeUsers, filteredTransportationOffices models.TransportationOffices) models.OfficeUsers { + var filteredOfficeUsers models.OfficeUsers + var currentOfficeUser models.OfficeUser + var currentTransportationOffice models.TransportationOffice + + for i := range officeUsers { + currentOfficeUser = officeUsers[i] + for j := range filteredTransportationOffices { + currentTransportationOffice = filteredTransportationOffices[j] + + if currentOfficeUser.TransportationOfficeID == currentTransportationOffice.ID { + filteredOfficeUsers = append(filteredOfficeUsers, currentOfficeUser) + } + } + } + + return filteredOfficeUsers +} + +// Function that filters Requested Office Users based on filtered Roles +func filterByRoles(officeUsers models.OfficeUsers, roles roles.Roles) models.OfficeUsers { + var filteredOfficeUsers models.OfficeUsers + + roleIDSet := make(map[uuid.UUID]struct{}) + for _, role := range roles { + roleIDSet[role.ID] = struct{}{} + } + + for _, officeUser := range officeUsers { + for _, userRole := range officeUser.User.Roles { + if _, exists := roleIDSet[userRole.ID]; exists { + filteredOfficeUsers = append(filteredOfficeUsers, officeUser) + break + } + } + } + + return filteredOfficeUsers +} + // IndexRequestedOfficeUsersHandler returns a list of requested office users via GET /requested_office_users type IndexRequestedOfficeUsersHandler struct { handlers.HandlerConfig services.RequestedOfficeUserListFetcher services.NewQueryFilter services.NewPagination + services.TransportationOfficesFetcher + services.RoleAssociater } var requestedOfficeUserFilterConverters = map[string]func(string) []services.QueryFilter{ @@ -167,6 +211,9 @@ var requestedOfficeUserFilterConverters = map[string]func(string) []services.Que }, } +var TransportationOfficeSearch = "transportationOfficeSearch" +var RoleSearch = "rolesSearch" + // Handle retrieves a list of requested office users func (h IndexRequestedOfficeUsersHandler) Handle(params requested_office_users.IndexRequestedOfficeUsersParams) middleware.Responder { return h.AuditableAppContextFromRequestWithErrors(params.HTTPRequest, @@ -192,6 +239,50 @@ func (h IndexRequestedOfficeUsersHandler) Handle(params requested_office_users.I return handlers.ResponseForError(appCtx.Logger(), err), err } + // Requested office user filters that is being used + requestedOfficeUserFilters := map[string]string{} + + if params.Filter != nil { + if err := json.Unmarshal([]byte(*params.Filter), &requestedOfficeUserFilters); err != nil { + return handlers.ResponseForError(appCtx.Logger(), err), err + } + } + + var filteredTransportationOffices models.TransportationOffices + // If there was a Transportation Office filter applied then get the filtered Transportation Offices + if requestedOfficeUserFilters[TransportationOfficeSearch] != "" { + searchString := requestedOfficeUserFilters[TransportationOfficeSearch] + transportationOfficesFilterResults, err := h.TransportationOfficesFetcher.GetTransportationOffices(appCtx, searchString, false, true) + if err != nil { + appCtx.Logger().Error("Error searching for Transportation Offices using filter: ", zap.Error(err)) + return handlers.ResponseForError(appCtx.Logger(), err), err + } + + filteredTransportationOffices = *transportationOfficesFilterResults + } + + // If there was a Roles filter applied then get the filtered Roles + var filteredRoles roles.Roles + if requestedOfficeUserFilters[RoleSearch] != "" { + rolesFilterResult, err := roles.FindRoles(appCtx.DB(), requestedOfficeUserFilters[RoleSearch]) + if err != nil { + appCtx.Logger().Error("Error searching for Roles using filter: ", zap.Error(err)) + return handlers.ResponseForError(appCtx.Logger(), err), err + } + + filteredRoles = rolesFilterResult + } + + // Filter users by filteredTransportationOffices if the filter is used + if len(filteredTransportationOffices) > 0 && len(officeUsers) > 0 { + officeUsers = filterByTransportationOffice(officeUsers, filteredTransportationOffices) + } + + // Filter users by roles if the filter is used + if len(filteredRoles) > 0 && len(officeUsers) > 0 { + officeUsers = filterByRoles(officeUsers, filteredRoles) + } + totalOfficeUsersCount, err := h.RequestedOfficeUserListFetcher.FetchRequestedOfficeUsersCount(appCtx, queryFilters) if err != nil { return handlers.ResponseForError(appCtx.Logger(), err), err @@ -234,7 +325,7 @@ func (h GetRequestedOfficeUserHandler) Handle(params requested_office_users.GetR roles, err := h.RoleAssociater.FetchRolesForUser(appCtx, *requestedOfficeUser.UserID) if err != nil { appCtx.Logger().Error("Error fetching user roles", zap.Error(err)) - return requested_office_users.NewUpdateRequestedOfficeUserInternalServerError(), err + return requested_office_users.NewGetRequestedOfficeUserInternalServerError(), err } requestedOfficeUser.User.Roles = roles @@ -245,7 +336,7 @@ func (h GetRequestedOfficeUserHandler) Handle(params requested_office_users.GetR }) } -// GetRequestedOfficeUserHandler returns a list of office users via GET /requested_office_users/{officeUserId} +// UpdateRequestedOfficeUserHandler updates a requested office user via PATCH /requested_office_users/{officeUserId} type UpdateRequestedOfficeUserHandler struct { handlers.HandlerConfig services.RequestedOfficeUserUpdater diff --git a/pkg/handlers/adminapi/requested_office_users_test.go b/pkg/handlers/adminapi/requested_office_users_test.go index d84c87ad69c..117741cf35d 100644 --- a/pkg/handlers/adminapi/requested_office_users_test.go +++ b/pkg/handlers/adminapi/requested_office_users_test.go @@ -1,6 +1,7 @@ package adminapi import ( + "encoding/json" "fmt" "net/http" "time" @@ -22,6 +23,7 @@ import ( "github.com/transcom/mymove/pkg/services/pagination" "github.com/transcom/mymove/pkg/services/query" requestedofficeusers "github.com/transcom/mymove/pkg/services/requested_office_users" + transportationofficeservice "github.com/transcom/mymove/pkg/services/transportation_office" ) func (suite *HandlerSuite) TestIndexRequestedOfficeUsersHandler() { @@ -486,6 +488,134 @@ func (suite *HandlerSuite) TestUpdateRequestedOfficeUserHandlerWithOktaAccountCr }) } +func (suite *HandlerSuite) TestFilterByTransportationOffice() { + + transportationOffice1 := factory.BuildTransportationOffice(suite.DB(), []factory.Customization{ + { + Model: models.TransportationOffice{ + Name: "PPPO Camp Houston", + ProvidesCloseout: false, + }, + }, + }, nil) + transportationOffice2 := factory.BuildTransportationOffice(suite.DB(), []factory.Customization{ + { + Model: models.TransportationOffice{ + Name: "PPPO Camp David", + ProvidesCloseout: false, + }, + }, + }, nil) + transportationOffice3 := factory.BuildTransportationOffice(suite.DB(), []factory.Customization{ + { + Model: models.TransportationOffice{ + Name: "Fort Bliss", + ProvidesCloseout: false, + }, + }, + }, nil) + + mockRoleAssociator := &mocks.RoleAssociater{} + tioRole := factory.FetchOrBuildRoleByRoleType(suite.DB(), roles.RoleTypeTIO) + tooRole := factory.FetchOrBuildRoleByRoleType(suite.DB(), roles.RoleTypeTOO) + scRole := factory.FetchOrBuildRoleByRoleType(suite.DB(), roles.RoleTypeServicesCounselor) + primeRole := factory.FetchOrBuildRoleByRoleType(suite.DB(), roles.RoleTypePrimeSimulator) + mockRoles := roles.Roles{tioRole, tooRole, scRole, primeRole} + mockRoleAssociator.On( + "FetchRolesForUser", + mock.AnythingOfType("*appcontext.appContext"), + mock.Anything, + ).Return(mockRoles, nil) + + requestedStatus := models.OfficeUserStatusREQUESTED + + requestedOfficeUsers := models.OfficeUsers{ + factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + { + Model: transportationOffice1, + LinkOnly: true, + }, + { + Model: models.OfficeUser{ + Status: &requestedStatus, + }, + }, + }, []roles.RoleType{roles.RoleTypeTOO}), + factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + { + Model: transportationOffice2, + LinkOnly: true, + }, + { + Model: models.OfficeUser{ + Status: &requestedStatus, + }, + }, + }, []roles.RoleType{roles.RoleTypeTIO}), + factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + { + Model: transportationOffice2, + LinkOnly: true, + }, + { + Model: models.OfficeUser{ + Status: &requestedStatus, + }, + }, + }, []roles.RoleType{roles.RoleTypeServicesCounselor}), + factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + { + Model: transportationOffice3, + LinkOnly: true, + }, + { + Model: models.OfficeUser{ + Status: &requestedStatus, + }, + }, + }, []roles.RoleType{roles.RoleTypeServicesCounselor}), + } + + type paramFilter struct { + TransportationOfficeSearch string `json:"transportationOfficeSearch"` + RolesSearch string `json:"rolesSearch"` + } + + var testParamFilter paramFilter + + testParamFilter.RolesSearch = "Task" + testParamFilter.TransportationOfficeSearch = "PPPO" + + testParamFilterJsonStr, err := json.Marshal(testParamFilter) + suite.NoError(err) + + rolesSearchFilterString := string(testParamFilterJsonStr) + params := requestedofficeuserop.IndexRequestedOfficeUsersParams{ + HTTPRequest: suite.setupAuthenticatedRequest("GET", "/requested_office_users"), + Filter: &rolesSearchFilterString, + } + + queryBuilder := query.NewQueryBuilder() + transportationOfficeFetcher := transportationofficeservice.NewTransportationOfficesFetcher() + + handler := IndexRequestedOfficeUsersHandler{ + HandlerConfig: suite.HandlerConfig(), + RequestedOfficeUserListFetcher: requestedofficeusers.NewRequestedOfficeUsersListFetcher(queryBuilder), + NewQueryFilter: query.NewQueryFilter, + NewPagination: pagination.NewPagination, + TransportationOfficesFetcher: transportationOfficeFetcher, + RoleAssociater: mockRoleAssociator, + } + + response := handler.Handle(params) + + 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()) + suite.Equal(requestedOfficeUsers[1].ID.String(), okResponse.Payload[1].ID.String()) +} + // Generate and activate Okta endpoints that will be using during the handler func mockAndActivateOktaEndpoints(provider *okta.Provider, responseCode int) { activate := "true" diff --git a/pkg/handlers/authentication/devlocal_test.go b/pkg/handlers/authentication/devlocal_test.go index 82b2b44bc93..34181343fd0 100644 --- a/pkg/handlers/authentication/devlocal_test.go +++ b/pkg/handlers/authentication/devlocal_test.go @@ -12,7 +12,6 @@ import ( "github.com/pkg/errors" "github.com/transcom/mymove/pkg/auth" - "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" @@ -66,31 +65,6 @@ func (suite *AuthSuite) TestCreateUserHandlerMilMove() { } func (suite *AuthSuite) TestCreateUserHandlerOffice() { - // These roles are created during migrations but our test suite truncates all tables - factory.BuildRole(suite.DB(), nil, []factory.Trait{ - factory.GetTraitTOORole, - }) - factory.BuildRole(suite.DB(), nil, []factory.Trait{ - factory.GetTraitTIORole, - }) - factory.BuildRole(suite.DB(), nil, []factory.Trait{ - factory.GetTraitServicesCounselorRole, - }) - factory.BuildRole(suite.DB(), nil, []factory.Trait{ - factory.GetTraitQaeRole, - }) - factory.BuildRole(suite.DB(), nil, []factory.Trait{ - factory.GetTraitCustomerServiceRepresentativeRole, - }) - factory.BuildRole(suite.DB(), nil, []factory.Trait{ - factory.GetTraitHQRole, - }) - factory.BuildRole(suite.DB(), nil, []factory.Trait{ - factory.GetTraitGSRRole, - }) - factory.BuildRole(suite.DB(), nil, []factory.Trait{ - factory.GetTraitPrimeSimulatorRole, - }) handlerConfig := suite.HandlerConfig() appnames := handlerConfig.AppNames() @@ -291,9 +265,6 @@ func (suite *AuthSuite) TestCreateAndLoginUserHandlerFromMilMoveToMilMove() { func (suite *AuthSuite) TestCreateAndLoginUserHandlerFromMilMoveToOffice() { t := suite.T() - factory.BuildRole(suite.DB(), nil, []factory.Trait{ - factory.GetTraitTOORole, - }) handlerConfig := suite.HandlerConfig() appnames := handlerConfig.AppNames() @@ -459,9 +430,6 @@ func (suite *AuthSuite) TestCreateAndLoginUserHandlerFromAdminToMilMove() { func (suite *AuthSuite) TestCreateAndLoginUserHandlerFromAdminToOffice() { t := suite.T() - factory.BuildRole(suite.DB(), nil, []factory.Trait{ - factory.GetTraitTOORole, - }) handlerConfig := suite.HandlerConfig() appnames := handlerConfig.AppNames() diff --git a/pkg/handlers/ghcapi/api.go b/pkg/handlers/ghcapi/api.go index 4a6bed9c52d..ffdd66f11d7 100644 --- a/pkg/handlers/ghcapi/api.go +++ b/pkg/handlers/ghcapi/api.go @@ -83,9 +83,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 +247,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 +259,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(), @@ -414,7 +427,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), ), @@ -482,7 +495,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, @@ -523,7 +536,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, } @@ -658,6 +671,11 @@ func NewGhcAPIHandler(handlerConfig handlers.HandlerConfig) *ghcops.MymoveAPI { transportationOfficeFetcher, } + ghcAPI.TransportationOfficeShowCounselingOfficesHandler = ShowCounselingOfficesHandler{ + handlerConfig, + transportationOfficeFetcher, + } + ghcAPI.MoveUpdateCloseoutOfficeHandler = UpdateMoveCloseoutOfficeHandler{ handlerConfig, closeoutOfficeUpdater, diff --git a/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go b/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go index 87d56d72748..535f7539bc5 100644 --- a/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go +++ b/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go @@ -122,6 +122,8 @@ func Move(move *models.Move, storer storage.FileStorer) (*ghcmessages.Move, erro SCAssignedUser: AssignedOfficeUser(move.SCAssignedUser), TOOAssignedUser: AssignedOfficeUser(move.TOOAssignedUser), TIOAssignedUser: AssignedOfficeUser(move.TIOAssignedUser), + CounselingOfficeID: handlers.FmtUUIDPtr(move.CounselingOfficeID), + CounselingOffice: TransportationOffice(move.CounselingOffice), } return payload, nil @@ -437,6 +439,18 @@ func GBLOCs(gblocs []string) ghcmessages.GBLOCs { return payload } +func CounselingOffices(counselingOffices models.TransportationOffices) ghcmessages.CounselingOffices { + payload := make(ghcmessages.CounselingOffices, len(counselingOffices)) + + for i, counselingOffice := range counselingOffices { + payload[i] = &ghcmessages.CounselingOffice{ + ID: handlers.FmtUUID(counselingOffice.ID), + Name: models.StringPointer(counselingOffice.Name), + } + } + return payload +} + // MoveHistory payload func MoveHistory(logger *zap.Logger, moveHistory *models.MoveHistory) *ghcmessages.MoveHistory { payload := &ghcmessages.MoveHistory{ @@ -733,6 +747,11 @@ func Entitlement(entitlement *models.Entitlement) *ghcmessages.Entitlements { if entitlement.UBAllowance != nil { ubAllowance = models.Int64Pointer(int64(*entitlement.UBAllowance)) } + var weightRestriction *int64 + if entitlement.WeightRestriction != nil { + weightRestriction = models.Int64Pointer(int64(*entitlement.WeightRestriction)) + } + return &ghcmessages.Entitlements{ ID: strfmt.UUID(entitlement.ID.String()), AuthorizedWeight: authorizedWeight, @@ -750,8 +769,9 @@ func Entitlement(entitlement *models.Entitlement) *ghcmessages.Entitlements { AccompaniedTour: accompaniedTour, UnaccompaniedBaggageAllowance: ubAllowance, OrganizationalClothingAndIndividualEquipment: entitlement.OrganizationalClothingAndIndividualEquipment, - GunSafe: gunSafe, - ETag: etag.GenerateEtag(entitlement.UpdatedAt), + GunSafe: gunSafe, + WeightRestriction: weightRestriction, + ETag: etag.GenerateEtag(entitlement.UpdatedAt), } } @@ -1300,13 +1320,16 @@ func PPMCloseout(ppmCloseout *models.PPMCloseout) *ghcmessages.PPMCloseout { Gcc: handlers.FmtCost(ppmCloseout.GCC), Aoa: handlers.FmtCost(ppmCloseout.AOA), RemainingIncentive: handlers.FmtCost(ppmCloseout.RemainingIncentive), - HaulType: (*string)(&ppmCloseout.HaulType), + HaulType: (*string)(ppmCloseout.HaulType), HaulPrice: handlers.FmtCost(ppmCloseout.HaulPrice), HaulFSC: handlers.FmtCost(ppmCloseout.HaulFSC), Dop: handlers.FmtCost(ppmCloseout.DOP), Ddp: handlers.FmtCost(ppmCloseout.DDP), PackPrice: handlers.FmtCost(ppmCloseout.PackPrice), UnpackPrice: handlers.FmtCost(ppmCloseout.UnpackPrice), + IntlPackPrice: handlers.FmtCost((ppmCloseout.IntlPackPrice)), + IntlUnpackPrice: handlers.FmtCost((ppmCloseout.IntlUnpackPrice)), + IntlLinehaulPrice: handlers.FmtCost((ppmCloseout.IntlLinehaulPrice)), SITReimbursement: handlers.FmtCost(ppmCloseout.SITReimbursement), } @@ -1917,6 +1940,22 @@ func MTOServiceItemModel(s *models.MTOServiceItem, storer storage.FileStorer) *g } } + if s.ReService.Code == models.ReServiceCodeIOSHUT && s.MTOShipment.PickupAddress != nil { + if *s.MTOShipment.PickupAddress.IsOconus { + payload.Market = handlers.FmtString(models.MarketOconus.FullString()) + } else { + payload.Market = handlers.FmtString(models.MarketConus.FullString()) + } + } + + if s.ReService.Code == models.ReServiceCodeIDSHUT && s.MTOShipment.DestinationAddress != nil { + if *s.MTOShipment.DestinationAddress.IsOconus { + payload.Market = handlers.FmtString(models.MarketOconus.FullString()) + } else { + payload.Market = handlers.FmtString(models.MarketConus.FullString()) + } + } + if s.ReService.Code == models.ReServiceCodeIUCRT && s.MTOShipment.DestinationAddress != nil { if *s.MTOShipment.DestinationAddress.IsOconus { payload.Market = handlers.FmtString(models.MarketOconus.FullString()) @@ -2191,12 +2230,12 @@ func BulkAssignmentData(appCtx appcontext.AppContext, moves []models.MoveWithEar return *bulkAssignmentData } -func queueMoveIsAssignable(move models.Move, assignedToUser *ghcmessages.AssignedOfficeUser, isCloseoutQueue bool, officeUser models.OfficeUser, ppmCloseoutGblocs bool) bool { +func queueMoveIsAssignable(move models.Move, assignedToUser *ghcmessages.AssignedOfficeUser, isCloseoutQueue bool, officeUser models.OfficeUser, ppmCloseoutGblocs bool, activeRole string) bool { // default to false isAssignable := false // HQ role is read only - if officeUser.User.Roles.HasRole(roles.RoleTypeHQ) { + if activeRole == string(roles.RoleTypeHQ) { isAssignable = false return isAssignable } @@ -2208,13 +2247,13 @@ func queueMoveIsAssignable(move models.Move, assignedToUser *ghcmessages.Assigne isSupervisor := officeUser.User.Privileges.HasPrivilege(models.PrivilegeTypeSupervisor) // in TOO queues, all moves are assignable for supervisor users - if officeUser.User.Roles.HasRole(roles.RoleTypeTOO) && isSupervisor { + if activeRole == string(roles.RoleTypeTOO) && isSupervisor { isAssignable = true } // if it is assigned in the SCs queue // it is only assignable if the user is a supervisor... - if officeUser.User.Roles.HasRole(roles.RoleTypeServicesCounselor) && isSupervisor { + if activeRole == string(roles.RoleTypeServicesCounselor) && isSupervisor { // AND we are in the counseling queue AND the move's counseling office is the supervisor's transportation office if !isCloseoutQueue && move.CounselingOfficeID != nil && *move.CounselingOfficeID == officeUser.TransportationOfficeID { isAssignable = true @@ -2234,35 +2273,36 @@ func queueMoveIsAssignable(move models.Move, assignedToUser *ghcmessages.Assigne } func servicesCounselorAvailableOfficeUsers(move models.Move, officeUsers []models.OfficeUser, officeUser models.OfficeUser, ppmCloseoutGblocs bool, isCloseoutQueue bool) []models.OfficeUser { - if officeUser.User.Roles.HasRole(roles.RoleTypeServicesCounselor) { - // if the office user currently assigned to the move works outside of the logged in users counseling office - // add them to the set - if move.SCAssignedUser != nil && move.SCAssignedUser.TransportationOfficeID != officeUser.TransportationOfficeID { - officeUsers = append(officeUsers, *move.SCAssignedUser) - } + // if the office user currently assigned to the move works outside of the logged in users counseling office + // add them to the set + if move.SCAssignedUser != nil && move.SCAssignedUser.TransportationOfficeID != officeUser.TransportationOfficeID { + officeUsers = append(officeUsers, *move.SCAssignedUser) + } - // if there is no counseling office - // OR if our current user doesn't work at the move's counseling office - // only available user should be themself - if !isCloseoutQueue && (move.CounselingOfficeID == nil) || (move.CounselingOfficeID != nil && *move.CounselingOfficeID != officeUser.TransportationOfficeID) { - officeUsers = models.OfficeUsers{officeUser} - } + var onlySelfAssign bool - // if its the closeout queue and its not a Navy, Marine, or Coast Guard user - // and the move doesn't have a closeout office - // OR the move's closeout office is not the office users office - // only available user should be themself - if isCloseoutQueue && !ppmCloseoutGblocs && move.CloseoutOfficeID == nil || (move.CloseoutOfficeID != nil && *move.CloseoutOfficeID != officeUser.TransportationOfficeID) { - officeUsers = models.OfficeUsers{officeUser} + // if there is no counseling office + // OR if our current user doesn't work at the move's counseling office + // only available user should be themself + onlySelfAssign = (move.CounselingOfficeID == nil) || (move.CounselingOfficeID != nil && *move.CounselingOfficeID != officeUser.TransportationOfficeID) + if !isCloseoutQueue && onlySelfAssign { + officeUsers = models.OfficeUsers{officeUser} + } - } + // if its the closeout queue and its not a Navy, Marine, or Coast Guard user + // and the move doesn't have a closeout office + // OR the move's closeout office is not the office users office + // only available user should be themself + onlySelfAssign = (move.CloseoutOfficeID == nil) || (move.CloseoutOfficeID != nil && *move.CloseoutOfficeID != officeUser.TransportationOfficeID) + if isCloseoutQueue && !ppmCloseoutGblocs && onlySelfAssign { + officeUsers = models.OfficeUsers{officeUser} } return officeUsers } // QueueMoves payload -func QueueMoves(moves []models.Move, officeUsers []models.OfficeUser, requestedPpmStatus *models.PPMShipmentStatus, officeUser models.OfficeUser, officeUsersSafety []models.OfficeUser) *ghcmessages.QueueMoves { +func QueueMoves(moves []models.Move, officeUsers []models.OfficeUser, requestedPpmStatus *models.PPMShipmentStatus, officeUser models.OfficeUser, officeUsersSafety []models.OfficeUser, activeRole string) *ghcmessages.QueueMoves { queueMoves := make(ghcmessages.QueueMoves, len(moves)) for i, move := range moves { customer := move.Orders.ServiceMember @@ -2334,10 +2374,10 @@ func QueueMoves(moves []models.Move, officeUsers []models.OfficeUser, requestedP // determine if there is an assigned user var assignedToUser *ghcmessages.AssignedOfficeUser - if officeUser.User.Roles.HasRole(roles.RoleTypeServicesCounselor) && move.SCAssignedUser != nil { + if (activeRole == string(roles.RoleTypeServicesCounselor) || activeRole == string(roles.RoleTypeHQ)) && move.SCAssignedUser != nil { assignedToUser = AssignedOfficeUser(move.SCAssignedUser) } - if officeUser.User.Roles.HasRole(roles.RoleTypeTOO) && move.TOOAssignedUser != nil { + if (activeRole == string(roles.RoleTypeTOO) || activeRole == string(roles.RoleTypeHQ)) && move.TOOAssignedUser != nil { assignedToUser = AssignedOfficeUser(move.TOOAssignedUser) } @@ -2346,7 +2386,7 @@ func QueueMoves(moves []models.Move, officeUsers []models.OfficeUser, requestedP // requestedPpmStatus also represents if we are viewing the closeout queue isCloseoutQueue := requestedPpmStatus != nil && *requestedPpmStatus == models.PPMShipmentStatusNeedsCloseout // determine if the move is assignable - assignable := queueMoveIsAssignable(move, assignedToUser, isCloseoutQueue, officeUser, ppmCloseoutGblocs) + assignable := queueMoveIsAssignable(move, assignedToUser, isCloseoutQueue, officeUser, ppmCloseoutGblocs, activeRole) isSupervisor := officeUser.User.Privileges.HasPrivilege(models.PrivilegeTypeSupervisor) // only need to attach available office users if move is assignable @@ -2358,7 +2398,16 @@ func QueueMoves(moves []models.Move, officeUsers []models.OfficeUser, requestedP if isSupervisor && move.Orders.OrdersType == "SAFETY" { availableOfficeUsers = officeUsersSafety } - if officeUser.User.Roles.HasRole(roles.RoleTypeServicesCounselor) { + + // if the assigned user does not work at the logged in users transportation office + // append them to the office users + if activeRole == string(roles.RoleTypeTOO) { + if move.TOOAssignedUser != nil && move.TOOAssignedUser.TransportationOfficeID != officeUser.TransportationOfficeID { + availableOfficeUsers = append(availableOfficeUsers, *move.TOOAssignedUser) + } + } + + if activeRole == string(roles.RoleTypeServicesCounselor) { availableOfficeUsers = servicesCounselorAvailableOfficeUsers(move, availableOfficeUsers, officeUser, ppmCloseoutGblocs, isCloseoutQueue) } @@ -2459,7 +2508,7 @@ func queuePaymentRequestStatus(paymentRequest models.PaymentRequest) string { } // QueuePaymentRequests payload -func QueuePaymentRequests(paymentRequests *models.PaymentRequests, officeUsers []models.OfficeUser, officeUser models.OfficeUser, officeUsersSafety []models.OfficeUser) *ghcmessages.QueuePaymentRequests { +func QueuePaymentRequests(paymentRequests *models.PaymentRequests, officeUsers []models.OfficeUser, officeUser models.OfficeUser, officeUsersSafety []models.OfficeUser, activeRole string) *ghcmessages.QueuePaymentRequests { queuePaymentRequests := make(ghcmessages.QueuePaymentRequests, len(*paymentRequests)) @@ -2504,7 +2553,7 @@ func QueuePaymentRequests(paymentRequests *models.PaymentRequests, officeUsers [ isAssignable = true } - if officeUser.User.Roles.HasRole(roles.RoleTypeHQ) { + if activeRole == string(roles.RoleTypeHQ) { isAssignable = false } @@ -2516,6 +2565,13 @@ func QueuePaymentRequests(paymentRequests *models.PaymentRequests, officeUsers [ if isSupervisor && orders.OrdersType == "SAFETY" { availableOfficeUsers = officeUsersSafety } + + // if the assigned TIO doesn't work at the user's counseling office, append them + if queuePaymentRequests[i].AssignedTo != nil && paymentRequest.MoveTaskOrder.TIOAssignedUser.TransportationOfficeID != officeUser.TransportationOfficeID { + availableOfficeUsers = append(availableOfficeUsers, *paymentRequest.MoveTaskOrder.TIOAssignedUser) + } + + // if they're not a supervisor and it is assignable, the only option should be themself if !isSupervisor { availableOfficeUsers = models.OfficeUsers{officeUser} } @@ -2607,9 +2663,11 @@ func SearchMoves(appCtx appcontext.AppContext, moves models.Moves) *ghcmessages. // populates the destination postal code of the move var destinationPostalCode string - destinationPostalCode, err = move.GetDestinationPostalCode(appCtx.DB()) + destinationAddress, err := move.GetDestinationAddress(appCtx.DB()) if err != nil { destinationPostalCode = "" + } else { + destinationPostalCode = destinationAddress.PostalCode } searchMoves[i] = &ghcmessages.SearchMove{ @@ -2689,6 +2747,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 { @@ -2717,30 +2799,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 d9c22df3ae9..fb0dab6e33e 100644 --- a/pkg/handlers/ghcapi/internal/payloads/model_to_payload_test.go +++ b/pkg/handlers/ghcapi/internal/payloads/model_to_payload_test.go @@ -7,6 +7,7 @@ import ( "github.com/go-openapi/strfmt" "github.com/gofrs/uuid" + "github.com/transcom/mymove/pkg/etag" "github.com/transcom/mymove/pkg/factory" "github.com/transcom/mymove/pkg/gen/ghcmessages" "github.com/transcom/mymove/pkg/gen/internalmessages" @@ -39,16 +40,29 @@ func (suite *PayloadsSuite) TestOrderWithMove() { } func (suite *PayloadsSuite) TestBoatShipment() { - boat := factory.BuildBoatShipment(suite.DB(), nil, nil) - boatShipment := BoatShipment(nil, &boat) - suite.NotNil(boatShipment) + suite.Run("Test Boat Shipment", func() { + boat := factory.BuildBoatShipment(suite.DB(), nil, nil) + boatShipment := BoatShipment(nil, &boat) + suite.NotNil(boatShipment) + }) + suite.Run("Test Boat Shipment", func() { + boatShipment := BoatShipment(nil, nil) + suite.Nil(boatShipment) + }) } func (suite *PayloadsSuite) TestMobileHomeShipment() { - mobileHome := factory.BuildMobileHomeShipment(suite.DB(), nil, nil) - mobileHomeShipment := MobileHomeShipment(nil, &mobileHome) - suite.NotNil(mobileHomeShipment) + suite.Run("Test Mobile Home Shipment", func() { + mobileHome := factory.BuildMobileHomeShipment(suite.DB(), nil, nil) + mobileHomeShipment := MobileHomeShipment(nil, &mobileHome) + suite.NotNil(mobileHomeShipment) + }) + + suite.Run("Test Mobile Home Shipment With Nil", func() { + mobileHomeShipment := MobileHomeShipment(nil, nil) + suite.Nil(mobileHomeShipment) + }) } func (suite *PayloadsSuite) TestMovingExpense() { @@ -106,6 +120,18 @@ func (suite *PayloadsSuite) TestMovingExpenses() { suite.NotNil(movingExpensesValue) } +func (suite *PayloadsSuite) TestMTOServiceItemDimension() { + dimension := models.MTOServiceItemDimension{ + Type: models.DimensionTypeItem, + Length: 1000, + Height: 1000, + Width: 1000, + } + + ghcDimension := MTOServiceItemDimension(&dimension) + suite.NotNil(ghcDimension) +} + // TestMove makes sure zero values/optional fields are handled func TestMove(t *testing.T) { _, err := Move(&models.Move{}, &test.FakeS3Storage{}) @@ -195,7 +221,8 @@ func (suite *PayloadsSuite) TestPaymentRequestQueue() { var officeUsers models.OfficeUsers var officeUsersSafety models.OfficeUsers officeUsers = append(officeUsers, officeUser) - var paymentRequestsQueue = QueuePaymentRequests(&paymentRequests, officeUsers, officeUser, officeUsersSafety) + activeRole := string(roles.RoleTypeTIO) + var paymentRequestsQueue = QueuePaymentRequests(&paymentRequests, officeUsers, officeUser, officeUsersSafety, activeRole) suite.Run("Test Payment request is assignable due to not being assigend", func() { paymentRequestCopy := *paymentRequestsQueue @@ -214,7 +241,7 @@ func (suite *PayloadsSuite) TestPaymentRequestQueue() { paymentRequests[0].MoveTaskOrder.TIOAssignedUser = &officeUserTIO paymentRequests[0].MoveTaskOrder.CounselingOffice = &transportationOffice - paymentRequestsQueue = QueuePaymentRequests(&paymentRequests, officeUsers, officeUser, officeUsersSafety) + paymentRequestsQueue = QueuePaymentRequests(&paymentRequests, officeUsers, officeUser, officeUsersSafety, activeRole) suite.Run("Test PaymentRequest has both Counseling Office and TIO AssignedUser ", func() { PaymentRequestsCopy := *paymentRequestsQueue @@ -228,14 +255,14 @@ func (suite *PayloadsSuite) TestPaymentRequestQueue() { }) suite.Run("Test PaymentRequest is assignable due to user Supervisor role", func() { - paymentRequests := QueuePaymentRequests(&paymentRequests, officeUsers, officeUser, officeUsersSafety) + paymentRequests := QueuePaymentRequests(&paymentRequests, officeUsers, officeUser, officeUsersSafety, activeRole) paymentRequestCopy := *paymentRequests suite.Equal(paymentRequestCopy[0].Assignable, true) }) - officeUserHQ := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeHQ}) + activeRole = string(roles.RoleTypeHQ) suite.Run("Test PaymentRequest is not assignable due to user HQ role", func() { - paymentRequests := QueuePaymentRequests(&paymentRequests, officeUsers, officeUserHQ, officeUsersSafety) + paymentRequests := QueuePaymentRequests(&paymentRequests, officeUsers, officeUser, officeUsersSafety, activeRole) paymentRequestCopy := *paymentRequests suite.Equal(paymentRequestCopy[0].Assignable, false) }) @@ -641,6 +668,7 @@ func (suite *PayloadsSuite) TestEntitlement() { dependentsTwelveAndOver := 1 authorizedWeight := 8000 ubAllowance := 300 + weightRestriction := 1000 entitlement := &models.Entitlement{ ID: entitlementID, @@ -658,6 +686,7 @@ func (suite *PayloadsSuite) TestEntitlement() { DependentsTwelveAndOver: &dependentsTwelveAndOver, UpdatedAt: time.Now(), UBAllowance: &ubAllowance, + WeightRestriction: &weightRestriction, } returnedEntitlement := Entitlement(entitlement) @@ -679,6 +708,7 @@ func (suite *PayloadsSuite) TestEntitlement() { suite.Equal(models.BoolPointer(accompaniedTour), returnedEntitlement.AccompaniedTour) suite.Equal(dependentsUnderTwelve, int(*returnedEntitlement.DependentsUnderTwelve)) suite.Equal(dependentsTwelveAndOver, int(*returnedEntitlement.DependentsTwelveAndOver)) + suite.Equal(weightRestriction, int(*returnedEntitlement.WeightRestriction)) } func (suite *PayloadsSuite) TestCreateCustomer() { @@ -832,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) @@ -1265,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) @@ -1328,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()) }) } @@ -1569,88 +1791,158 @@ 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) +func (suite *PayloadsSuite) TestPaymentServiceItemPayload() { + mtoServiceItemID := uuid.Must(uuid.NewV4()) + mtoShipmentID := uuid.Must(uuid.NewV4()) + psID := uuid.Must(uuid.NewV4()) + reServiceCode := models.ReServiceCodeDLH + reServiceName := "Domestic Linehaul" + shipmentType := models.MTOShipmentTypeHHG + priceCents := unit.Cents(12345) + rejectionReason := models.StringPointer("Some reason") + status := models.PaymentServiceItemStatusDenied + referenceID := "REF123" + createdAt := time.Now() + updatedAt := time.Now() + + paymentServiceItemParams := []models.PaymentServiceItemParam{ + { + ID: uuid.Must(uuid.NewV4()), + PaymentServiceItemID: psID, + Value: "1000", + }, + } - poefscServiceItem := factory.BuildMTOServiceItem(nil, []factory.Customization{ - { - Model: models.ReService{ - Code: models.ReServiceCodePOEFSC, - Priority: 1, - }, + paymentServiceItem := models.PaymentServiceItem{ + ID: psID, + MTOServiceItemID: mtoServiceItemID, + MTOServiceItem: models.MTOServiceItem{ + ID: mtoServiceItemID, + MTOShipment: models.MTOShipment{ + ID: mtoShipmentID, + ShipmentType: shipmentType, }, - { - Model: poePortLocation, - LinkOnly: true, - Type: &factory.PortLocations.PortOfEmbarkation, + ReService: models.ReService{ + Code: reServiceCode, + Name: reServiceName, }, - }, nil) - - mtoShipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ - { - Model: models.MTOShipment{ - MTOServiceItems: models.MTOServiceItems{poefscServiceItem}, - }, - }, - }, nil) - - payload := MTOShipment(nil, &mtoShipment, nil) + }, + PriceCents: &priceCents, + RejectionReason: rejectionReason, + Status: status, + ReferenceID: referenceID, + PaymentServiceItemParams: paymentServiceItemParams, + CreatedAt: createdAt, + UpdatedAt: updatedAt, + } - // 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("Success - Returns a ghcmessages PaymentServiceItem payload", func() { + returnedPaymentServiceItem := PaymentServiceItem(&paymentServiceItem) + + suite.NotNil(returnedPaymentServiceItem) + suite.IsType(&ghcmessages.PaymentServiceItem{}, returnedPaymentServiceItem) + suite.Equal(handlers.FmtUUID(paymentServiceItem.ID), &returnedPaymentServiceItem.ID) + suite.Equal(handlers.FmtUUID(paymentServiceItem.MTOServiceItemID), &returnedPaymentServiceItem.MtoServiceItemID) + suite.Equal(string(paymentServiceItem.MTOServiceItem.ReService.Code), returnedPaymentServiceItem.MtoServiceItemCode) + suite.Equal(paymentServiceItem.MTOServiceItem.ReService.Name, returnedPaymentServiceItem.MtoServiceItemName) + suite.Equal(ghcmessages.MTOShipmentType(paymentServiceItem.MTOServiceItem.MTOShipment.ShipmentType), returnedPaymentServiceItem.MtoShipmentType) + suite.Equal(handlers.FmtUUIDPtr(paymentServiceItem.MTOServiceItem.MTOShipmentID), returnedPaymentServiceItem.MtoShipmentID) + suite.Equal(handlers.FmtCost(paymentServiceItem.PriceCents), returnedPaymentServiceItem.PriceCents) + suite.Equal(paymentServiceItem.RejectionReason, returnedPaymentServiceItem.RejectionReason) + suite.Equal(ghcmessages.PaymentServiceItemStatus(paymentServiceItem.Status), returnedPaymentServiceItem.Status) + suite.Equal(paymentServiceItem.ReferenceID, returnedPaymentServiceItem.ReferenceID) + suite.Equal(etag.GenerateEtag(paymentServiceItem.UpdatedAt), returnedPaymentServiceItem.ETag) + + suite.Equal(len(paymentServiceItem.PaymentServiceItemParams), len(returnedPaymentServiceItem.PaymentServiceItemParams)) + for i, param := range paymentServiceItem.PaymentServiceItemParams { + suite.Equal(param.Value, returnedPaymentServiceItem.PaymentServiceItemParams[i].Value) + } }) +} - 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", +func (suite *PayloadsSuite) TestPaymentServiceItemsPayload() { + mtoServiceItemID1 := uuid.Must(uuid.NewV4()) + mtoServiceItemID2 := uuid.Must(uuid.NewV4()) + psID1 := uuid.Must(uuid.NewV4()) + psID2 := uuid.Must(uuid.NewV4()) + priceCents1 := unit.Cents(12345) + priceCents2 := unit.Cents(54321) + reServiceCode1 := models.ReServiceCodeDLH + reServiceCode2 := models.ReServiceCodeDOP + reServiceName1 := "Domestic Linehaul" + reServiceName2 := "Domestic Origin Pack" + shipmentType := models.MTOShipmentTypeHHG + createdAt := time.Now() + updatedAt := time.Now() + + paymentServiceItems := models.PaymentServiceItems{ + { + ID: psID1, + MTOServiceItemID: mtoServiceItemID1, + MTOServiceItem: models.MTOServiceItem{ + ID: mtoServiceItemID1, + MTOShipment: models.MTOShipment{ + ID: uuid.Must(uuid.NewV4()), + ShipmentType: shipmentType, }, - }, - }, nil) - - podfscServiceItem := factory.BuildMTOServiceItem(nil, []factory.Customization{ - { - Model: models.ReService{ - Code: models.ReServiceCodePODFSC, - Priority: 1, + ReService: models.ReService{ + Code: reServiceCode1, + Name: reServiceName1, }, }, - { - Model: podPortLocation, - LinkOnly: true, - Type: &factory.PortLocations.PortOfDebarkation, - }, - }, nil) - - mtoShipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ - { - Model: models.MTOShipment{ - MTOServiceItems: models.MTOServiceItems{podfscServiceItem}, + PriceCents: &priceCents1, + CreatedAt: createdAt, + UpdatedAt: updatedAt, + }, + { + ID: psID2, + MTOServiceItemID: mtoServiceItemID2, + MTOServiceItem: models.MTOServiceItem{ + ID: mtoServiceItemID2, + MTOShipment: models.MTOShipment{ + ID: uuid.Must(uuid.NewV4()), + ShipmentType: shipmentType, + }, + ReService: models.ReService{ + Code: reServiceCode2, + Name: reServiceName2, }, }, - }, nil) + PriceCents: &priceCents2, + CreatedAt: createdAt, + UpdatedAt: updatedAt, + }, + } - payload := MTOShipment(nil, &mtoShipment, nil) + // TPPSPaidInvoiceReportData + lineNetCharge1 := int64(200000) + tppsPaidReportData := models.TPPSPaidInvoiceReportEntrys{ + { + ProductDescription: string(reServiceCode1), + LineNetCharge: unit.Millicents(lineNetCharge1), + }, + } - // 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") + suite.Run("Success - Returns ghcmessages.PaymentServiceItems payload", func() { + returnedPaymentServiceItems := PaymentServiceItems(&paymentServiceItems, &tppsPaidReportData) + + suite.NotNil(returnedPaymentServiceItems) + suite.Len(*returnedPaymentServiceItems, 2) + + psItem1 := (*returnedPaymentServiceItems)[0] + suite.Equal(handlers.FmtUUID(psID1), &psItem1.ID) + suite.Equal(handlers.FmtCost(&priceCents1), psItem1.PriceCents) + suite.Equal(string(reServiceCode1), psItem1.MtoServiceItemCode) + suite.Equal(reServiceName1, psItem1.MtoServiceItemName) + suite.Equal(ghcmessages.MTOShipmentType(shipmentType), psItem1.MtoShipmentType) + suite.NotNil(psItem1.TppsInvoiceAmountPaidPerServiceItemMillicents) + + psItem2 := (*returnedPaymentServiceItems)[1] + suite.Equal(handlers.FmtUUID(psID2), &psItem2.ID) + suite.Equal(handlers.FmtCost(&priceCents2), psItem2.PriceCents) + suite.Equal(string(reServiceCode2), psItem2.MtoServiceItemCode) + suite.Equal(reServiceName2, psItem2.MtoServiceItemName) + suite.Equal(ghcmessages.MTOShipmentType(shipmentType), psItem2.MtoShipmentType) + suite.Nil(psItem2.TppsInvoiceAmountPaidPerServiceItemMillicents) }) } diff --git a/pkg/handlers/ghcapi/move_task_order_test.go b/pkg/handlers/ghcapi/move_task_order_test.go index a156eb19251..316eae9b0c8 100644 --- a/pkg/handlers/ghcapi/move_task_order_test.go +++ b/pkg/handlers/ghcapi/move_task_order_test.go @@ -191,7 +191,6 @@ func (suite *HandlerSuite) TestUpdateMoveTaskOrderHandlerIntegrationSuccess() { mock.Anything, mock.Anything, false, - false, ).Return(400, nil) siCreator := mtoserviceitem.NewMTOServiceItemCreator(planner, queryBuilder, moveRouter, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticPackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticShorthaulPricer(), ghcrateengine.NewDomesticOriginPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) @@ -317,7 +316,6 @@ func (suite *HandlerSuite) TestUpdateMoveTaskOrderHandlerIntegrationWithIncomple mock.Anything, mock.Anything, false, - false, ).Return(400, nil) setUpSignedCertificationCreatorMock := func(returnValue ...interface{}) services.SignedCertificationCreator { @@ -407,7 +405,6 @@ func (suite *HandlerSuite) TestUpdateMTOStatusServiceCounselingCompletedHandler( mock.Anything, mock.Anything, false, - false, ).Return(400, nil) siCreator := mtoserviceitem.NewMTOServiceItemCreator(planner, queryBuilder, moveRouter, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticPackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticShorthaulPricer(), ghcrateengine.NewDomesticOriginPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) handler := UpdateMTOStatusServiceCounselingCompletedHandlerFunc{ @@ -628,7 +625,6 @@ func (suite *HandlerSuite) TestUpdateMoveTIORemarksHandler() { mock.Anything, mock.Anything, false, - false, ).Return(400, nil) setUpSignedCertificationCreatorMock := func(returnValue ...interface{}) services.SignedCertificationCreator { diff --git a/pkg/handlers/ghcapi/move_test.go b/pkg/handlers/ghcapi/move_test.go index 3b9fda47401..68b33148943 100644 --- a/pkg/handlers/ghcapi/move_test.go +++ b/pkg/handlers/ghcapi/move_test.go @@ -597,17 +597,17 @@ func (suite *HandlerSuite) TestSearchMovesHandler() { // Validate outgoing payload without shipment suite.NoError(payload.Validate(strfmt.Default)) - var moveDestinationPostalCode string + var moveDestinationAddress *models.Address var moveDestinationGBLOC string var err error // Get destination postal code and GBLOC based on business logic - moveDestinationPostalCode, err = move.GetDestinationPostalCode(suite.DB()) + moveDestinationAddress, err = move.GetDestinationAddress(suite.DB()) suite.NoError(err) moveDestinationGBLOC, err = move.GetDestinationGBLOC(suite.DB()) suite.NoError(err) - suite.Equal(moveDestinationPostalCode, "62225") + suite.Equal(moveDestinationAddress.PostalCode, "62225") suite.Equal(ghcmessages.GBLOC(moveDestinationGBLOC), ghcmessages.GBLOC("AGFM")) // Set Mock Search settings for move with MTO Shipment @@ -639,12 +639,12 @@ func (suite *HandlerSuite) TestSearchMovesHandler() { suite.NoError(payload.Validate(strfmt.Default)) // Get destination postal code and GBLOC based on business logic - moveDestinationPostalCode, err = moveWithShipment.GetDestinationPostalCode(suite.DB()) + moveDestinationAddress, err = moveWithShipment.GetDestinationAddress(suite.DB()) suite.NoError(err) moveDestinationGBLOC, err = moveWithShipment.GetDestinationGBLOC(suite.DB()) suite.NoError(err) - suite.Equal(moveDestinationPostalCode, "90210") + suite.Equal(moveDestinationAddress.PostalCode, "90210") suite.Equal(ghcmessages.GBLOC(moveDestinationGBLOC), ghcmessages.GBLOC("KKFA")) // Set Mock Search settings for move with PPM Shipment @@ -676,12 +676,12 @@ func (suite *HandlerSuite) TestSearchMovesHandler() { suite.NoError(payload.Validate(strfmt.Default)) // Get destination postal code and GBLOC based on business logic - moveDestinationPostalCode, err = moveWithShipmentPPM.GetDestinationPostalCode(suite.DB()) + moveDestinationAddress, err = moveWithShipmentPPM.GetDestinationAddress(suite.DB()) suite.NoError(err) moveDestinationGBLOC, err = moveWithShipmentPPM.GetDestinationGBLOC(suite.DB()) suite.NoError(err) - suite.Equal(moveDestinationPostalCode, payload.SearchMoves[0].DestinationPostalCode) + suite.Equal(moveDestinationAddress.PostalCode, payload.SearchMoves[0].DestinationPostalCode) suite.Equal(ghcmessages.GBLOC(moveDestinationGBLOC), payload.SearchMoves[0].DestinationGBLOC) }) } diff --git a/pkg/handlers/ghcapi/mto_service_items_test.go b/pkg/handlers/ghcapi/mto_service_items_test.go index 57ecf797ed1..8044580f0b0 100644 --- a/pkg/handlers/ghcapi/mto_service_items_test.go +++ b/pkg/handlers/ghcapi/mto_service_items_test.go @@ -407,7 +407,6 @@ func (suite *HandlerSuite) TestUpdateMTOServiceItemStatusHandler() { mock.Anything, mock.Anything, false, - false, ).Return(400, nil) moveWeights := moveservices.NewMoveWeights(mtoshipment.NewShipmentReweighRequester(), waf) @@ -426,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,9 +660,8 @@ func (suite *HandlerSuite) TestUpdateMTOServiceItemStatusHandler() { mock.Anything, mock.Anything, false, - 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(), @@ -724,9 +722,8 @@ func (suite *HandlerSuite) TestUpdateMTOServiceItemStatusHandler() { mock.Anything, mock.Anything, false, - 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(), @@ -862,7 +859,6 @@ func (suite *HandlerSuite) TestUpdateServiceItemSitEntryDateHandler() { mock.Anything, mock.Anything, false, - false, ).Return(400, nil) moveWeights := moveservices.NewMoveWeights(mtoshipment.NewShipmentReweighRequester(), waf) @@ -881,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 8b101ecb4ae..8104b305f09 100644 --- a/pkg/handlers/ghcapi/mto_shipment_test.go +++ b/pkg/handlers/ghcapi/mto_shipment_test.go @@ -607,7 +607,6 @@ func (suite *HandlerSuite) TestApproveShipmentHandler() { mock.Anything, mock.Anything, false, - false, ).Return(400, nil) approver := mtoshipment.NewShipmentApprover( mtoshipment.NewShipmentRouter(), @@ -2130,7 +2129,6 @@ func (suite *HandlerSuite) TestRequestShipmentReweighHandler() { mock.Anything, mock.Anything, false, - false, ).Return(400, nil) moveRouter := moveservices.NewMoveRouter() moveWeights := moveservices.NewMoveWeights(mtoshipment.NewShipmentReweighRequester(), waf) @@ -2190,7 +2188,6 @@ func (suite *HandlerSuite) TestRequestShipmentReweighHandler() { mock.Anything, mock.Anything, false, - false, ).Return(400, nil) moveRouter := moveservices.NewMoveRouter() moveWeights := moveservices.NewMoveWeights(mtoshipment.NewShipmentReweighRequester(), waf) @@ -2247,7 +2244,6 @@ func (suite *HandlerSuite) TestRequestShipmentReweighHandler() { mock.Anything, mock.Anything, false, - false, ).Return(400, nil) moveRouter := moveservices.NewMoveRouter() moveWeights := moveservices.NewMoveWeights(mtoshipment.NewShipmentReweighRequester(), waf) @@ -2305,7 +2301,6 @@ func (suite *HandlerSuite) TestRequestShipmentReweighHandler() { mock.Anything, mock.Anything, false, - false, ).Return(400, nil) moveRouter := moveservices.NewMoveRouter() moveWeights := moveservices.NewMoveWeights(mtoshipment.NewShipmentReweighRequester(), waf) @@ -2364,7 +2359,6 @@ func (suite *HandlerSuite) TestRequestShipmentReweighHandler() { mock.Anything, mock.Anything, false, - false, ).Return(400, nil) moveRouter := moveservices.NewMoveRouter() moveWeights := moveservices.NewMoveWeights(mtoshipment.NewShipmentReweighRequester(), waf) @@ -2422,7 +2416,6 @@ func (suite *HandlerSuite) TestRequestShipmentReweighHandler() { mock.Anything, mock.Anything, false, - false, ).Return(400, nil) moveRouter := moveservices.NewMoveRouter() moveWeights := moveservices.NewMoveWeights(mtoshipment.NewShipmentReweighRequester(), waf) @@ -2764,7 +2757,6 @@ func (suite *HandlerSuite) TestApproveSITExtensionHandler() { mock.Anything, mock.Anything, false, - false, ).Return(400, nil) moveWeights := moveservices.NewMoveWeights(mtoshipment.NewShipmentReweighRequester(), waf) @@ -2784,7 +2776,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, @@ -2908,7 +2900,6 @@ func (suite *HandlerSuite) CreateApprovedSITDurationUpdate() { mock.Anything, mock.Anything, false, - false, ).Return(400, nil) moveWeights := moveservices.NewMoveWeights(mtoshipment.NewShipmentReweighRequester(), waf) @@ -2929,7 +2920,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, @@ -2993,7 +2984,6 @@ func (suite *HandlerSuite) CreateApprovedSITDurationUpdate() { mock.Anything, mock.Anything, false, - false, ).Return(400, nil) moveWeights := moveservices.NewMoveWeights(mtoshipment.NewShipmentReweighRequester(), waf) @@ -3016,7 +3006,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, @@ -3168,7 +3158,6 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { mock.Anything, mock.Anything, false, - false, ).Return(400, nil) moveTaskOrderUpdater := movetaskorder.NewMoveTaskOrderUpdater( builder, @@ -3254,7 +3243,6 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { mock.Anything, mock.Anything, false, - false, ).Return(400, nil) moveTaskOrderUpdater := movetaskorder.NewMoveTaskOrderUpdater( builder, @@ -3312,7 +3300,6 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { mock.Anything, mock.Anything, false, - false, ).Return(400, nil) moveTaskOrderUpdater := movetaskorder.NewMoveTaskOrderUpdater( builder, @@ -3366,7 +3353,6 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { mock.Anything, mock.Anything, false, - false, ).Return(400, nil) moveTaskOrderUpdater := movetaskorder.NewMoveTaskOrderUpdater( builder, @@ -3415,7 +3401,6 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { mock.Anything, mock.Anything, false, - false, ).Return(400, nil) moveTaskOrderUpdater := movetaskorder.NewMoveTaskOrderUpdater( builder, @@ -3501,7 +3486,6 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandlerUsingPPM() { mock.Anything, mock.Anything, false, - false, ).Return(400, nil) moveTaskOrderUpdater := movetaskorder.NewMoveTaskOrderUpdater( @@ -3714,7 +3698,6 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandlerUsingPPM() { mock.Anything, mock.Anything, false, - false, ).Return(400, nil) moveTaskOrderUpdater := movetaskorder.NewMoveTaskOrderUpdater( builder, @@ -3866,7 +3849,6 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandlerUsingPPM() { mock.Anything, mock.Anything, false, - false, ).Return(400, nil) moveTaskOrderUpdater := movetaskorder.NewMoveTaskOrderUpdater( builder, @@ -4074,7 +4056,6 @@ func (suite *HandlerSuite) TestUpdateShipmentHandler() { mock.Anything, mock.Anything, false, - false, ).Return(400, nil) moveRouter := moveservices.NewMoveRouter() moveWeights := moveservices.NewMoveWeights(mtoshipment.NewShipmentReweighRequester(), waf) @@ -4095,7 +4076,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, @@ -4163,7 +4144,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, @@ -4332,9 +4313,7 @@ func (suite *HandlerSuite) TestUpdateShipmentHandler() { suite.Equal(handlers.FmtPoundPtr(&sitEstimatedWeight), updatedShipment.PpmShipment.SitEstimatedWeight) suite.Equal(handlers.FmtDate(sitEstimatedEntryDate), updatedShipment.PpmShipment.SitEstimatedEntryDate) suite.Equal(handlers.FmtDate(sitEstimatedDepartureDate), updatedShipment.PpmShipment.SitEstimatedDepartureDate) - suite.Equal(int64(sitEstimatedCost), *updatedShipment.PpmShipment.SitEstimatedCost) suite.Equal(handlers.FmtPoundPtr(&estimatedWeight), updatedShipment.PpmShipment.EstimatedWeight) - suite.Equal(int64(estimatedIncentive), *updatedShipment.PpmShipment.EstimatedIncentive) suite.Equal(handlers.FmtBool(hasProGear), updatedShipment.PpmShipment.HasProGear) suite.Equal(handlers.FmtPoundPtr(&proGearWeight), updatedShipment.PpmShipment.ProGearWeight) suite.Equal(handlers.FmtPoundPtr(&spouseProGearWeight), updatedShipment.PpmShipment.SpouseProGearWeight) @@ -4351,7 +4330,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, @@ -4431,7 +4410,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, @@ -4511,7 +4490,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, @@ -4549,7 +4528,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, @@ -4589,7 +4568,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, @@ -4630,7 +4609,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, @@ -4746,9 +4725,8 @@ func (suite *HandlerSuite) TestUpdateSITServiceItemCustomerExpenseHandler() { mock.Anything, mock.Anything, false, - 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() @@ -4824,9 +4802,8 @@ func (suite *HandlerSuite) TestUpdateSITServiceItemCustomerExpenseHandler() { mock.Anything, mock.Anything, false, - 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 f6b559513a2..9a0e2b784d2 100644 --- a/pkg/handlers/ghcapi/orders.go +++ b/pkg/handlers/ghcapi/orders.go @@ -219,21 +219,46 @@ func (h CreateOrderHandler) Handle(params orderop.CreateOrderParams) middleware. return orderop.NewCreateOrderUnprocessableEntity(), err } - destinationGBLOC, err := models.FetchGBLOCForPostalCode(appCtx.DB(), newDutyLocation.Address.PostalCode) - if err != nil { - err = apperror.NewBadDataError("New duty location GBLOC cannot be verified") - appCtx.Logger().Error(err.Error()) - return orderop.NewCreateOrderUnprocessableEntity(), err + var newDutyLocationGBLOC *string + if *newDutyLocation.Address.IsOconus { + newDutyLocationGBLOCOconus, err := models.FetchAddressGbloc(appCtx.DB(), newDutyLocation.Address, serviceMember) + if err != nil { + return nil, apperror.NewNotFoundError(newDutyLocation.ID, "while looking for New Duty Location Oconus GBLOC") + } + newDutyLocationGBLOC = newDutyLocationGBLOCOconus + } else { + newDutyLocationGBLOCConus, err := models.FetchGBLOCForPostalCode(appCtx.DB(), newDutyLocation.Address.PostalCode) + if err != nil { + switch err { + case sql.ErrNoRows: + return nil, apperror.NewNotFoundError(newDutyLocation.ID, "while looking for New Duty Location PostalCodeToGBLOC") + default: + err = apperror.NewBadDataError("New duty location GBLOC cannot be verified") + appCtx.Logger().Error(err.Error()) + return orderop.NewCreateOrderUnprocessableEntity(), err + } + } + newDutyLocationGBLOC = &newDutyLocationGBLOCConus.GBLOC } - originDutyLocationGBLOC, err := models.FetchGBLOCForPostalCode(appCtx.DB(), originDutyLocation.Address.PostalCode) - if err != nil { - switch err { - case sql.ErrNoRows: - return nil, apperror.NewNotFoundError(originDutyLocation.ID, "while looking for Duty Location PostalCodeToGBLOC") - default: - return nil, apperror.NewQueryError("PostalCodeToGBLOC", err, "") + var originDutyLocationGBLOC *string + if *originDutyLocation.Address.IsOconus { + originDutyLocationGBLOCOconus, err := models.FetchAddressGbloc(appCtx.DB(), originDutyLocation.Address, serviceMember) + if err != nil { + return nil, apperror.NewNotFoundError(originDutyLocation.ID, "while looking for Origin Duty Location Oconus GBLOC") } + originDutyLocationGBLOC = originDutyLocationGBLOCOconus + } else { + originDutyLocationGBLOCConus, err := models.FetchGBLOCForPostalCode(appCtx.DB(), originDutyLocation.Address.PostalCode) + if err != nil { + switch err { + case sql.ErrNoRows: + return nil, apperror.NewNotFoundError(originDutyLocation.ID, "while looking for Origin Duty Location PostalCodeToGBLOC") + default: + return nil, apperror.NewQueryError("PostalCodeToGBLOC", err, "") + } + } + originDutyLocationGBLOC = &originDutyLocationGBLOCConus.GBLOC } grade := (internalmessages.OrderPayGrade)(*payload.Grade) @@ -265,6 +290,7 @@ func (h CreateOrderHandler) Handle(params orderop.CreateOrderParams) middleware. weightAllotment.UnaccompaniedBaggageAllowance = unaccompaniedBaggageAllowance } + var weightRestriction *int entitlement := models.Entitlement{ DependentsAuthorized: payload.HasDependents, DBAuthorizedWeight: models.IntPointer(weight), @@ -275,6 +301,7 @@ func (h CreateOrderHandler) Handle(params orderop.CreateOrderParams) middleware. DependentsUnderTwelve: dependentsUnderTwelve, DependentsTwelveAndOver: dependentsTwelveAndOver, UBAllowance: &weightAllotment.UnaccompaniedBaggageAllowance, + WeightRestriction: weightRestriction, } if saveEntitlementErr := appCtx.DB().Save(&entitlement); saveEntitlementErr != nil { @@ -317,9 +344,9 @@ func (h CreateOrderHandler) Handle(params orderop.CreateOrderParams) middleware. &originDutyLocation, &grade, &entitlement, - &originDutyLocationGBLOC.GBLOC, + originDutyLocationGBLOC, packingAndShippingInstructions, - &destinationGBLOC.GBLOC, + newDutyLocationGBLOC, ) if err != nil || verrs.HasAny() { return handlers.ResponseForVErrors(appCtx.Logger(), verrs, err), err @@ -337,15 +364,13 @@ func (h CreateOrderHandler) Handle(params orderop.CreateOrderParams) middleware. Show: models.BoolPointer(true), Status: &status, } - if !appCtx.Session().OfficeUserID.IsNil() { - officeUser, err := models.FetchOfficeUserByID(appCtx.DB(), appCtx.Session().OfficeUserID) + + if payload.CounselingOfficeID != nil { + counselingOffice, err := uuid.FromString(payload.CounselingOfficeID.String()) if err != nil { - err = apperror.NewBadDataError("Unable to fetch office user.") - appCtx.Logger().Error(err.Error()) - return orderop.NewCreateOrderUnprocessableEntity(), err - } else { - moveOptions.CounselingOfficeID = &officeUser.TransportationOfficeID + return handlers.ResponseForError(appCtx.Logger(), err), err } + moveOptions.CounselingOfficeID = &counselingOffice } if newOrder.OrdersType == "SAFETY" { diff --git a/pkg/handlers/ghcapi/orders_test.go b/pkg/handlers/ghcapi/orders_test.go index b58d128f204..37ff1a66c3a 100644 --- a/pkg/handlers/ghcapi/orders_test.go +++ b/pkg/handlers/ghcapi/orders_test.go @@ -32,6 +32,7 @@ import ( "github.com/transcom/mymove/pkg/services/query" storageTest "github.com/transcom/mymove/pkg/storage/test" "github.com/transcom/mymove/pkg/swagger/nullable" + "github.com/transcom/mymove/pkg/testdatagen" "github.com/transcom/mymove/pkg/trace" "github.com/transcom/mymove/pkg/uploader" ) @@ -75,6 +76,7 @@ func (suite *HandlerSuite) TestCreateOrder() { Sac: handlers.FmtString("SacNumber"), DepartmentIndicator: ghcmessages.NewDeptIndicator(deptIndicator), Grade: ghcmessages.GradeE1.Pointer(), + CounselingOfficeID: handlers.FmtUUID(*dutyLocation.TransportationOfficeID), } params := orderop.CreateOrderParams{ @@ -110,20 +112,70 @@ func (suite *HandlerSuite) TestCreateOrder() { func (suite *HandlerSuite) TestCreateOrderWithOCONUSValues() { waf := entitlements.NewWeightAllotmentFetcher() - sm := factory.BuildExtendedServiceMember(suite.AppContextForTest().DB(), nil, nil) + customAffiliation := models.AffiliationARMY + sm := factory.BuildExtendedServiceMember(suite.DB(), []factory.Customization{ + { + Model: models.ServiceMember{ + Affiliation: &customAffiliation, + }, + }, + }, nil) officeUser := factory.BuildOfficeUserWithRoles(suite.AppContextForTest().DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) - originDutyLocation := factory.BuildDutyLocation(suite.AppContextForTest().DB(), []factory.Customization{ + usprc, err := models.FindByZipCode(suite.AppContextForTest().DB(), "99801") + suite.NotNil(usprc) + suite.FatalNoError(err) + + address := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + IsOconus: models.BoolPointer(true), + UsPostRegionCityID: &usprc.ID, + }, + }, + }, nil) + + originDutyLocation := factory.BuildDutyLocation(suite.DB(), []factory.Customization{ { Model: models.DutyLocation{ - Name: "Not Yuma AFB", + Name: factory.MakeRandomString(8), + AddressID: address.ID, }, }, }, nil) + dutyLocation := factory.FetchOrBuildCurrentDutyLocation(suite.AppContextForTest().DB()) - factory.FetchOrBuildPostalCodeToGBLOC(suite.AppContextForTest().DB(), dutyLocation.Address.PostalCode, "KKFA") - factory.FetchOrBuildDefaultContractor(suite.AppContextForTest().DB(), nil, nil) + contract := testdatagen.FetchOrMakeReContract(suite.DB(), testdatagen.Assertions{}) + + rateAreaCode := uuid.Must(uuid.NewV4()).String()[0:5] + rateArea := testdatagen.FetchOrMakeReRateArea(suite.DB(), testdatagen.Assertions{ + ReRateArea: models.ReRateArea{ + ContractID: contract.ID, + IsOconus: true, + Name: fmt.Sprintf("Alaska-%s", rateAreaCode), + Contract: contract, + }, + }) + suite.NotNil(rateArea) + + us_country, err := models.FetchCountryByCode(suite.DB(), "US") + suite.NotNil(us_country) + suite.Nil(err) + + oconusRateArea, err := models.FetchOconusRateAreaByCityId(suite.DB(), usprc.ID.String()) + suite.NotNil(oconusRateArea) + suite.Nil(err) + + jppsoRegion, err := models.FetchJppsoRegionByCode(suite.DB(), "MAPK") + suite.NotNil(jppsoRegion) + suite.Nil(err) + + gblocAors, err := models.FetchGblocAorsByJppsoCodeRateAreaDept(suite.DB(), jppsoRegion.ID, oconusRateArea.ID, models.DepartmentIndicatorARMY.String()) + suite.NotNil(gblocAors) + suite.Nil(err) + + factory.FetchOrBuildDefaultContractor(suite.AppContextForTest().DB(), nil, nil) req := httptest.NewRequest("POST", "/orders", nil) req = suite.AuthenticateOfficeRequest(req, officeUser) @@ -175,6 +227,7 @@ func (suite *HandlerSuite) TestCreateOrderWithOCONUSValues() { suite.Assertions.Equal(sm.ID.String(), okResponse.Payload.CustomerID.String()) suite.Assertions.Equal(ordersType, okResponse.Payload.OrderType) suite.Assertions.Equal(handlers.FmtString("123456"), okResponse.Payload.OrderNumber) + suite.Assertions.Equal(ghcmessages.GBLOC("MAPK"), okResponse.Payload.OriginDutyLocationGBLOC) suite.Assertions.Equal(handlers.FmtString("E19A"), okResponse.Payload.Tac) suite.Assertions.Equal(handlers.FmtString("SacNumber"), okResponse.Payload.Sac) suite.Assertions.Equal(&deptIndicator, okResponse.Payload.DepartmentIndicator) @@ -403,7 +456,6 @@ func (suite *HandlerSuite) TestUpdateOrderHandlerWithAmendedUploads() { mock.Anything, mock.Anything, false, - false, ).Return(400, nil) setUpSignedCertificationCreatorMock := func(returnValue ...interface{}) services.SignedCertificationCreator { diff --git a/pkg/handlers/ghcapi/ppm_shipment_test.go b/pkg/handlers/ghcapi/ppm_shipment_test.go index bc8093032b5..c4a2bbc0834 100644 --- a/pkg/handlers/ghcapi/ppm_shipment_test.go +++ b/pkg/handlers/ghcapi/ppm_shipment_test.go @@ -386,8 +386,7 @@ func (suite *HandlerSuite) TestGetPPMSITEstimatedCostHandler() { ppmShipment.DestinationAddress = destinationAddress mockedPlanner := &routemocks.Planner{} - mockedPlanner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), - "90210", "30813", false, false).Return(2294, nil) + mockedPlanner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), "90210", "30813", false).Return(2294, nil) } setupData() diff --git a/pkg/handlers/ghcapi/queues.go b/pkg/handlers/ghcapi/queues.go index ed4980df650..202e5d3d6a0 100644 --- a/pkg/handlers/ghcapi/queues.go +++ b/pkg/handlers/ghcapi/queues.go @@ -64,6 +64,11 @@ func (h GetMovesQueueHandler) Handle(params queues.GetMovesQueueParams) middlewa CounselingOffice: params.CounselingOffice, } + var activeRole string + if params.ActiveRole != nil { + activeRole = *params.ActiveRole + } + // When no status filter applied, TOO should only see moves with status of New Move, Service Counseling Completed, or Approvals Requested if params.Status == nil { ListOrderParams.Status = []string{string(models.MoveStatusServiceCounselingCompleted), string(models.MoveStatusAPPROVALSREQUESTED), string(models.MoveStatusSUBMITTED)} @@ -165,7 +170,7 @@ func (h GetMovesQueueHandler) Handle(params queues.GetMovesQueueParams) middlewa } } - queueMoves := payloads.QueueMoves(moves, officeUsers, nil, officeUser, officeUsersSafety) + queueMoves := payloads.QueueMoves(moves, officeUsers, nil, officeUser, officeUsersSafety, activeRole) result := &ghcmessages.QueueMovesResult{ Page: *ListOrderParams.Page, @@ -271,6 +276,11 @@ func (h GetPaymentRequestsQueueHandler) Handle( CounselingOffice: params.CounselingOffice, } + var activeRole string + if params.ActiveRole != nil { + activeRole = *params.ActiveRole + } + listPaymentRequestParams.Status = []string{string(models.QueuePaymentRequestPaymentRequested)} // Let's set default values for page and perPage if we don't get arguments for them. We'll use 1 for page and 20 @@ -368,7 +378,7 @@ func (h GetPaymentRequestsQueueHandler) Handle( } } - queuePaymentRequests := payloads.QueuePaymentRequests(paymentRequests, officeUsers, officeUser, officeUsersSafety) + queuePaymentRequests := payloads.QueuePaymentRequests(paymentRequests, officeUsers, officeUser, officeUsersSafety, activeRole) result := &ghcmessages.QueuePaymentRequestsResult{ TotalCount: int64(count), @@ -429,6 +439,11 @@ func (h GetServicesCounselingQueueHandler) Handle( SCAssignedUser: params.AssignedTo, } + var activeRole string + if params.ActiveRole != nil { + activeRole = *params.ActiveRole + } + var requestedPpmStatus models.PPMShipmentStatus if params.NeedsPPMCloseout != nil && *params.NeedsPPMCloseout { requestedPpmStatus = models.PPMShipmentStatusNeedsCloseout @@ -538,7 +553,7 @@ func (h GetServicesCounselingQueueHandler) Handle( } } - queueMoves := payloads.QueueMoves(moves, officeUsers, &requestedPpmStatus, officeUser, officeUsersSafety) + queueMoves := payloads.QueueMoves(moves, officeUsers, &requestedPpmStatus, officeUser, officeUsersSafety, activeRole) result := &ghcmessages.QueueMovesResult{ Page: *ListOrderParams.Page, @@ -611,6 +626,27 @@ func (h GetBulkAssignmentDataHandler) Handle( return queues.NewGetBulkAssignmentDataInternalServerError(), err } + officeUserData = payloads.BulkAssignmentData(appCtx, moves, officeUsers, officeUser.TransportationOffice.ID) + case string(models.QueueTypeCloseout): + // fetch the Services Counselors who work at their office + officeUsers, err := h.OfficeUserFetcherPop.FetchOfficeUsersWithWorkloadByRoleAndOffice( + appCtx, + roles.RoleTypeServicesCounselor, + officeUser.TransportationOfficeID, + ) + if err != nil { + appCtx.Logger().Error("Error retreiving office users", zap.Error(err)) + return queues.NewGetBulkAssignmentDataInternalServerError(), err + } + // fetch the moves available to be assigned to their office users + moves, err := h.MoveFetcherBulkAssignment.FetchMovesForBulkAssignmentCloseout( + appCtx, officeUser.TransportationOffice.Gbloc, officeUser.TransportationOffice.ID, + ) + if err != nil { + appCtx.Logger().Error("Error retreiving moves", zap.Error(err)) + return queues.NewGetBulkAssignmentDataInternalServerError(), err + } + officeUserData = payloads.BulkAssignmentData(appCtx, moves, officeUsers, officeUser.TransportationOffice.ID) case string(models.QueueTypeTaskOrder): // fetch the TOOs who work at their office @@ -632,6 +668,27 @@ func (h GetBulkAssignmentDataHandler) Handle( return queues.NewGetBulkAssignmentDataInternalServerError(), err } + officeUserData = payloads.BulkAssignmentData(appCtx, moves, officeUsers, officeUser.TransportationOffice.ID) + case string(models.QueueTypePaymentRequest): + // fetch the TIOs who work at their office + officeUsers, err := h.OfficeUserFetcherPop.FetchOfficeUsersWithWorkloadByRoleAndOffice( + appCtx, + roles.RoleTypeTIO, + officeUser.TransportationOfficeID, + ) + if err != nil { + appCtx.Logger().Error("Error retreiving office users", zap.Error(err)) + return queues.NewGetBulkAssignmentDataInternalServerError(), err + } + // fetch the moves available to be assigned to their office users + moves, err := h.MoveFetcherBulkAssignment.FetchMovesForBulkAssignmentPaymentRequest( + appCtx, officeUser.TransportationOffice.Gbloc, officeUser.TransportationOffice.ID, + ) + if err != nil { + appCtx.Logger().Error("Error retreiving moves", zap.Error(err)) + return queues.NewGetBulkAssignmentDataInternalServerError(), err + } + officeUserData = payloads.BulkAssignmentData(appCtx, moves, officeUsers, officeUser.TransportationOffice.ID) } return queues.NewGetBulkAssignmentDataOK().WithPayload(&officeUserData), nil diff --git a/pkg/handlers/ghcapi/queues_test.go b/pkg/handlers/ghcapi/queues_test.go index f1eb8a3c106..6a658d6c2aa 100644 --- a/pkg/handlers/ghcapi/queues_test.go +++ b/pkg/handlers/ghcapi/queues_test.go @@ -1655,7 +1655,7 @@ func (suite *HandlerSuite) TestGetServicesCounselingQueueHandler() { } func (suite *HandlerSuite) TestGetBulkAssignmentDataHandler() { - suite.Run("returns an unauthorized error when an attempt is made by a non supervisor", func() { + suite.Run("SC - returns an unauthorized error when an attempt is made by a non supervisor", func() { officeUser := factory.BuildOfficeUserWithPrivileges(suite.DB(), []factory.Customization{ { Model: models.OfficeUser{ @@ -1689,7 +1689,7 @@ func (suite *HandlerSuite) TestGetBulkAssignmentDataHandler() { suite.IsNotErrResponse(response) suite.IsType(&queues.GetBulkAssignmentDataUnauthorized{}, response) }) - suite.Run("returns properly formatted bulk assignment data", func() { + suite.Run("SC - returns properly formatted bulk assignment data", func() { transportationOffice := factory.BuildTransportationOffice(suite.DB(), nil, nil) officeUser := factory.BuildOfficeUserWithPrivileges(suite.DB(), []factory.Customization{ @@ -1754,7 +1754,6 @@ func (suite *HandlerSuite) TestGetBulkAssignmentDataHandler() { suite.Len(payload.AvailableOfficeUsers, 1) suite.Len(payload.BulkAssignmentMoveIDs, 1) }) - suite.Run("TOO: returns properly formatted bulk assignment data", func() { transportationOffice := factory.BuildTransportationOffice(suite.DB(), nil, nil) @@ -1820,4 +1819,433 @@ func (suite *HandlerSuite) TestGetBulkAssignmentDataHandler() { suite.Len(payload.AvailableOfficeUsers, 1) suite.Len(payload.BulkAssignmentMoveIDs, 1) }) + suite.Run("returns properly formatted closeout bulk assignment data", func() { + transportationOffice := factory.BuildTransportationOffice(suite.DB(), nil, nil) + + officeUser := factory.BuildOfficeUserWithPrivileges(suite.DB(), []factory.Customization{ + { + Model: models.OfficeUser{ + Email: "officeuser1@example.com", + Active: true, + }, + }, + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + { + Model: models.User{ + Privileges: []models.Privilege{ + { + PrivilegeType: models.PrivilegeTypeSupervisor, + }, + }, + Roles: []roles.Role{ + { + RoleType: roles.RoleTypeServicesCounselor, + }, + }, + }, + }, + }, nil) + + submittedAt := time.Now() + + // move to appear in the return + factory.BuildMoveWithPPMShipment(suite.DB(), []factory.Customization{ + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CloseoutOffice, + }, + { + Model: models.PPMShipment{ + Status: models.PPMShipmentStatusNeedsCloseout, + SubmittedAt: &submittedAt, + }, + }, + { + Model: models.Move{ + Status: models.MoveStatusAPPROVED, + }, + }, + }, nil) + + request := httptest.NewRequest("GET", "/queues/bulk-assignment", nil) + request = suite.AuthenticateOfficeRequest(request, officeUser) + params := queues.GetBulkAssignmentDataParams{ + HTTPRequest: request, + QueueType: models.StringPointer("CLOSEOUT"), + } + handlerConfig := suite.HandlerConfig() + handler := GetBulkAssignmentDataHandler{ + handlerConfig, + officeusercreator.NewOfficeUserFetcherPop(), + movefetcher.NewMoveFetcherBulkAssignment(), + } + response := handler.Handle(params) + suite.IsNotErrResponse(response) + suite.IsType(&queues.GetBulkAssignmentDataOK{}, response) + payload := response.(*queues.GetBulkAssignmentDataOK).Payload + suite.NoError(payload.Validate(strfmt.Default)) + suite.Len(payload.AvailableOfficeUsers, 1) + suite.Len(payload.BulkAssignmentMoveIDs, 1) + }) + + suite.Run("TIO - returns an unauthorized error when an attempt is made by a non supervisor", func() { + officeUser := factory.BuildOfficeUserWithPrivileges(suite.DB(), []factory.Customization{ + { + Model: models.OfficeUser{ + Email: "officeuser1@example.com", + }, + }, + { + Model: models.User{ + Roles: []roles.Role{ + { + RoleType: roles.RoleTypeTIO, + }, + }, + }, + }, + }, nil) + + request := httptest.NewRequest("GET", "/queues/bulk-assignment", nil) + request = suite.AuthenticateOfficeRequest(request, officeUser) + params := queues.GetBulkAssignmentDataParams{ + HTTPRequest: request, + QueueType: models.StringPointer("PAYMENT_REQUEST"), + } + handlerConfig := suite.HandlerConfig() + handler := GetBulkAssignmentDataHandler{ + handlerConfig, + officeusercreator.NewOfficeUserFetcherPop(), + movefetcher.NewMoveFetcherBulkAssignment(), + } + response := handler.Handle(params) + suite.IsNotErrResponse(response) + suite.IsType(&queues.GetBulkAssignmentDataUnauthorized{}, response) + }) + suite.Run("TIO - returns properly formatted bulk assignment data", func() { + transportationOffice := factory.BuildTransportationOffice(suite.DB(), nil, nil) + + officeUser := factory.BuildOfficeUserWithPrivileges(suite.DB(), []factory.Customization{ + { + Model: models.OfficeUser{ + Email: "officeuser1@example.com", + Active: true, + }, + }, + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + { + Model: models.User{ + Privileges: []models.Privilege{ + { + PrivilegeType: models.PrivilegeTypeSupervisor, + }, + }, + Roles: []roles.Role{ + { + RoleType: roles.RoleTypeTIO, + }, + }, + }, + }, + }, nil) + + // payment request to appear in the return + move := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + }, + }, + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + }, nil) + factory.BuildPaymentRequest(suite.DB(), []factory.Customization{ + { + Model: models.PaymentRequest{ + ID: uuid.Must(uuid.NewV4()), + IsFinal: false, + Status: models.PaymentRequestStatusPending, + RejectionReason: nil, + }, + }, + { + Model: move, + LinkOnly: true, + }, + }, nil) + + request := httptest.NewRequest("GET", "/queues/bulk-assignment", nil) + request = suite.AuthenticateOfficeRequest(request, officeUser) + params := queues.GetBulkAssignmentDataParams{ + HTTPRequest: request, + QueueType: models.StringPointer("PAYMENT_REQUEST"), + } + handlerConfig := suite.HandlerConfig() + handler := GetBulkAssignmentDataHandler{ + handlerConfig, + officeusercreator.NewOfficeUserFetcherPop(), + movefetcher.NewMoveFetcherBulkAssignment(), + } + response := handler.Handle(params) + suite.IsNotErrResponse(response) + suite.IsType(&queues.GetBulkAssignmentDataOK{}, response) + payload := response.(*queues.GetBulkAssignmentDataOK).Payload + suite.NoError(payload.Validate(strfmt.Default)) + suite.Len(payload.AvailableOfficeUsers, 1) + suite.Len(payload.BulkAssignmentMoveIDs, 1) + }) +} + +type availableOfficeUserSubtestData struct { + officeUsers []models.OfficeUser + office models.TransportationOffice +} + +func (suite *HandlerSuite) TestAvailableOfficeUsers() { + setupOfficeUserData := func(role1 roles.RoleType, role2 roles.RoleType) availableOfficeUserSubtestData { + subtestData := &availableOfficeUserSubtestData{} + transportationOffice := factory.BuildTransportationOffice(suite.DB(), nil, nil) + + // lets generate a few office users + // these first two are what we want returned in the query + // office user 1 is the supervisor making the request + officeUser1 := factory.BuildOfficeUserWithPrivileges(suite.DB(), []factory.Customization{ + { + Model: models.OfficeUser{ + LastName: "Aname", + Email: "officeuser1@example.com", + Active: true, + }, + }, + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + { + Model: models.User{ + Privileges: []models.Privilege{ + { + PrivilegeType: models.PrivilegeTypeSupervisor, + }, + }, + Roles: []roles.Role{ + { + RoleType: role1, + }, + }, + }, + }, + }, nil) + + // officeUser2 is their underling + officeUser2 := factory.BuildOfficeUserWithPrivileges(suite.DB(), []factory.Customization{ + { + Model: models.OfficeUser{ + LastName: "Bname", + Email: "officeuser2@example.com", + Active: true, + }, + }, + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + { + Model: models.User{ + Roles: []roles.Role{ + { + RoleType: role1, + }, + }, + }, + }, + }, nil) + + // this office user shares their role but does NOT work at their office so should not be returned + factory.BuildOfficeUserWithPrivileges(suite.DB(), []factory.Customization{ + { + Model: models.OfficeUser{ + Email: "officeuser3@example.com", + Active: true, + }, + }, + { + Model: models.User{ + Roles: []roles.Role{ + { + RoleType: role1, + }, + }, + }, + }, + }, nil) + + // this office users works at their office, but doesn't share the same role, and should not be returned + factory.BuildOfficeUserWithPrivileges(suite.DB(), []factory.Customization{ + { + Model: models.OfficeUser{ + Email: "officeuser4@example.com", + Active: true, + }, + }, + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + { + Model: models.User{ + Roles: []roles.Role{ + { + RoleType: role2, + }, + }, + }, + }, + }, nil) + + availableOfficeUsers := []models.OfficeUser{officeUser1, officeUser2} + subtestData.officeUsers = availableOfficeUsers + subtestData.office = transportationOffice + return *subtestData + } + suite.Run("properly fetches a TOO supervisor's available office users for assignment", func() { + subtestData := setupOfficeUserData(roles.RoleTypeTOO, roles.RoleTypeServicesCounselor) + waf := entitlements.NewWeightAllotmentFetcher() + + hhgMove := factory.BuildSubmittedMove(suite.DB(), nil, nil) + + factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: hhgMove, + LinkOnly: true, + }, + { + Model: models.MTOShipment{ + Status: models.MTOShipmentStatusSubmitted, + }, + }, + }, nil) + + request := httptest.NewRequest("GET", "/queues/moves", nil) + request = suite.AuthenticateOfficeRequest(request, subtestData.officeUsers[0]) + params := queues.GetMovesQueueParams{ + HTTPRequest: request, + } + handlerConfig := suite.HandlerConfig() + mockUnlocker := movelocker.NewMoveUnlocker() + handler := GetMovesQueueHandler{ + handlerConfig, + order.NewOrderFetcher(waf), + mockUnlocker, + officeusercreator.NewOfficeUserFetcherPop(), + } + + response := handler.Handle(params) + suite.IsNotErrResponse(response) + suite.IsType(&queues.GetMovesQueueOK{}, response) + payload := response.(*queues.GetMovesQueueOK).Payload + + suite.NotNil(payload.QueueMoves) + suite.NotNil(payload.QueueMoves[0].AvailableOfficeUsers) + suite.Equal(2, len(payload.QueueMoves[0].AvailableOfficeUsers)) + suite.Equal(subtestData.officeUsers[0].ID.String(), payload.QueueMoves[0].AvailableOfficeUsers[0].OfficeUserID.String()) + suite.Equal(subtestData.officeUsers[1].ID.String(), payload.QueueMoves[0].AvailableOfficeUsers[1].OfficeUserID.String()) + }) + suite.Run("properly fetches a SC supervisor's available office users for assignment", func() { + subtestData := setupOfficeUserData(roles.RoleTypeServicesCounselor, roles.RoleTypeTOO) + waf := entitlements.NewWeightAllotmentFetcher() + + needsCounselingMove := factory.BuildNeedsServiceCounselingMove(suite.DB(), []factory.Customization{ + { + Model: subtestData.office, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + }, nil) + + factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: needsCounselingMove, + LinkOnly: true, + }, + }, nil) + + request := httptest.NewRequest("GET", "/queues/counseling", nil) + request = suite.AuthenticateOfficeRequest(request, subtestData.officeUsers[0]) + params := queues.GetServicesCounselingQueueParams{ + HTTPRequest: request, + } + handlerConfig := suite.HandlerConfig() + mockUnlocker := movelocker.NewMoveUnlocker() + handler := GetServicesCounselingQueueHandler{ + handlerConfig, + order.NewOrderFetcher(waf), + mockUnlocker, + officeusercreator.NewOfficeUserFetcherPop(), + } + + response := handler.Handle(params) + suite.IsNotErrResponse(response) + suite.IsType(&queues.GetServicesCounselingQueueOK{}, response) + payload := response.(*queues.GetServicesCounselingQueueOK).Payload + + suite.NotNil(payload.QueueMoves) + suite.NotNil(payload.QueueMoves[0].AvailableOfficeUsers) + suite.Equal(2, len(payload.QueueMoves[0].AvailableOfficeUsers)) + suite.Equal(subtestData.officeUsers[0].ID.String(), payload.QueueMoves[0].AvailableOfficeUsers[0].OfficeUserID.String()) + suite.Equal(subtestData.officeUsers[1].ID.String(), payload.QueueMoves[0].AvailableOfficeUsers[1].OfficeUserID.String()) + }) + + suite.Run("properly fetches a TIO supervisor's available office users for assignment", func() { + subtestData := setupOfficeUserData(roles.RoleTypeTIO, roles.RoleTypeTOO) + hhgMove := factory.BuildMoveWithShipment(suite.DB(), nil, nil) + + factory.BuildPaymentRequest(suite.DB(), []factory.Customization{ + { + Model: hhgMove, + LinkOnly: true, + }, + }, nil) + + request := httptest.NewRequest("GET", "/queues/payment-requests", nil) + request = suite.AuthenticateOfficeRequest(request, subtestData.officeUsers[0]) + params := queues.GetPaymentRequestsQueueParams{ + HTTPRequest: request, + } + handlerConfig := suite.HandlerConfig() + mockUnlocker := movelocker.NewMoveUnlocker() + handler := GetPaymentRequestsQueueHandler{ + handlerConfig, + paymentrequest.NewPaymentRequestListFetcher(), + mockUnlocker, + officeusercreator.NewOfficeUserFetcherPop(), + } + + response := handler.Handle(params) + suite.IsNotErrResponse(response) + suite.IsType(&queues.GetPaymentRequestsQueueOK{}, response) + payload := response.(*queues.GetPaymentRequestsQueueOK).Payload + + suite.NotNil(payload.QueuePaymentRequests) + suite.NotNil(payload.QueuePaymentRequests[0].AvailableOfficeUsers) + suite.Equal(2, len(payload.QueuePaymentRequests[0].AvailableOfficeUsers)) + suite.Equal(subtestData.officeUsers[0].ID.String(), payload.QueuePaymentRequests[0].AvailableOfficeUsers[0].OfficeUserID.String()) + suite.Equal(subtestData.officeUsers[1].ID.String(), payload.QueuePaymentRequests[0].AvailableOfficeUsers[1].OfficeUserID.String()) + }) + } diff --git a/pkg/handlers/ghcapi/tranportation_offices.go b/pkg/handlers/ghcapi/tranportation_offices.go index 405580923bb..b08f45cfb04 100644 --- a/pkg/handlers/ghcapi/tranportation_offices.go +++ b/pkg/handlers/ghcapi/tranportation_offices.go @@ -2,6 +2,7 @@ package ghcapi import ( "github.com/go-openapi/runtime/middleware" + "github.com/gofrs/uuid" "go.uber.org/zap" "github.com/transcom/mymove/pkg/appcontext" @@ -22,7 +23,7 @@ func (h GetTransportationOfficesHandler) Handle(params transportationofficeop.Ge // B-21022: forPpm param is set true. This is used by PPM closeout widget. Need to ensure certain offices are included/excluded // if location has ppm closedout enabled. - transportationOffices, err := h.TransportationOfficesFetcher.GetTransportationOffices(appCtx, params.Search, true) + transportationOffices, err := h.TransportationOfficesFetcher.GetTransportationOffices(appCtx, params.Search, true, false) if err != nil { appCtx.Logger().Error("Error searching for Transportation Offices: ", zap.Error(err)) @@ -43,7 +44,7 @@ func (h GetTransportationOfficesOpenHandler) Handle(params transportationofficeo return h.AuditableAppContextFromRequestWithErrors(params.HTTPRequest, func(appCtx appcontext.AppContext) (middleware.Responder, error) { - transportationOffices, err := h.TransportationOfficesFetcher.GetTransportationOffices(appCtx, params.Search, false) + transportationOffices, err := h.TransportationOfficesFetcher.GetTransportationOffices(appCtx, params.Search, false, false) if err != nil { appCtx.Logger().Error("Error searching for Transportation Offices: ", zap.Error(err)) return transportationofficeop.NewGetTransportationOfficesOpenInternalServerError(), err @@ -73,3 +74,27 @@ func (h GetTransportationOfficesGBLOCsHandler) Handle(params transportationoffic return transportationofficeop.NewGetTransportationOfficesGBLOCsOK().WithPayload(returnPayload), nil }) } + +// ShowCounselingOfficesHandler returns the counseling offices for a duty location ID +type ShowCounselingOfficesHandler struct { + handlers.HandlerConfig + services.TransportationOfficesFetcher +} + +// Handle retrieves the counseling offices in the system for a given duty location ID +func (h ShowCounselingOfficesHandler) Handle(params transportationofficeop.ShowCounselingOfficesParams) middleware.Responder { + return h.AuditableAppContextFromRequestWithErrors(params.HTTPRequest, + func(appCtx appcontext.AppContext) (middleware.Responder, error) { + dutyLocationID := uuid.FromStringOrNil(params.DutyLocationID.String()) + serviceMemberID := uuid.FromStringOrNil(params.ServiceMemberID.String()) + + counselingOffices, err := h.TransportationOfficesFetcher.GetCounselingOffices(appCtx, dutyLocationID, serviceMemberID) + if err != nil { + appCtx.Logger().Error("Error searching for Counseling Offices: ", zap.Error(err)) + return transportationofficeop.NewShowCounselingOfficesInternalServerError(), err + } + + returnPayload := payloads.CounselingOffices(*counselingOffices) + return transportationofficeop.NewShowCounselingOfficesOK().WithPayload(returnPayload), nil + }) +} diff --git a/pkg/handlers/ghcapi/transportation_offices_test.go b/pkg/handlers/ghcapi/transportation_offices_test.go index 92ac98f630b..b9305f4d4b7 100644 --- a/pkg/handlers/ghcapi/transportation_offices_test.go +++ b/pkg/handlers/ghcapi/transportation_offices_test.go @@ -1,13 +1,16 @@ package ghcapi import ( + "fmt" "net/http/httptest" "github.com/go-openapi/strfmt" "github.com/transcom/mymove/pkg/factory" transportationofficeop "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/transportation_office" + "github.com/transcom/mymove/pkg/handlers" "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services/address" transportationofficeservice "github.com/transcom/mymove/pkg/services/transportation_office" ) @@ -145,3 +148,63 @@ func (suite *HandlerSuite) TestGetTransportationOfficesGBLOCsHandler() { suite.Equal(transportationOffice1.Gbloc, responsePayload.Payload[0]) suite.Equal(transportationOffice2.Gbloc, responsePayload.Payload[1]) } + +func (suite *HandlerSuite) TestShowCounselingOfficesHandler() { + user := factory.BuildDefaultUser(suite.DB()) + serviceMember := factory.BuildServiceMember(suite.DB(), nil, nil) + + fetcher := transportationofficeservice.NewTransportationOfficesFetcher() + + newAddress := models.Address{ + StreetAddress1: "some address", + City: "city", + State: "CA", + PostalCode: "59801", + County: models.StringPointer("County"), + } + addressCreator := address.NewAddressCreator() + createdAddress, err := addressCreator.CreateAddress(suite.AppContextForTest(), &newAddress) + suite.NoError(err) + + origDutyLocation := factory.BuildDutyLocation(suite.DB(), []factory.Customization{ + { + Model: models.DutyLocation{ + AddressID: createdAddress.ID, + ProvidesServicesCounseling: true, + }, + }, + { + Model: models.TransportationOffice{ + Name: "New PPPO Travis AFB - USAF", + Gbloc: "KKFA", + ProvidesCloseout: true, + }, + }, + }, nil) + + path := fmt.Sprintf("/transportation_offices/%v/counseling_offices/%v", origDutyLocation.ID.String(), serviceMember.ID.String()) + req := httptest.NewRequest("GET", path, nil) + req = suite.AuthenticateUserRequest(req, user) + params := transportationofficeop.ShowCounselingOfficesParams{ + HTTPRequest: req, + DutyLocationID: *handlers.FmtUUID(origDutyLocation.ID), + } + + handler := ShowCounselingOfficesHandler{ + HandlerConfig: suite.HandlerConfig(), + TransportationOfficesFetcher: fetcher} + + response := handler.Handle(params) + suite.Assertions.IsType(&transportationofficeop.ShowCounselingOfficesOK{}, response) + responsePayload := response.(*transportationofficeop.ShowCounselingOfficesOK) + + // Validate outgoing payload + suite.NoError(responsePayload.Payload.Validate(strfmt.Default)) + var i int + for index, office := range responsePayload.Payload { + if *office.Name == "New PPPO Travis AFB - USAF" { + i = index + } + } + suite.NotNil(i) +} diff --git a/pkg/handlers/internalapi/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/internal/payloads/model_to_payload_test.go b/pkg/handlers/internalapi/internal/payloads/model_to_payload_test.go index 80f07a55433..8da707d4000 100644 --- a/pkg/handlers/internalapi/internal/payloads/model_to_payload_test.go +++ b/pkg/handlers/internalapi/internal/payloads/model_to_payload_test.go @@ -6,6 +6,7 @@ import ( "github.com/gofrs/uuid" "github.com/transcom/mymove/pkg/etag" + "github.com/transcom/mymove/pkg/factory" "github.com/transcom/mymove/pkg/gen/internalmessages" "github.com/transcom/mymove/pkg/handlers" "github.com/transcom/mymove/pkg/models" @@ -21,7 +22,8 @@ func (suite *PayloadsSuite) TestFetchPPMShipment() { state := "FL" postalcode := "33621" country := models.Country{ - Country: "US", + Country: "US", + CountryName: "United States", } county := "HILLSBOROUGH" @@ -120,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() 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 a70eec78915..2a06643c691 100644 --- a/pkg/handlers/internalapi/mto_shipment_test.go +++ b/pkg/handlers/internalapi/mto_shipment_test.go @@ -79,7 +79,6 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandlerV1() { mock.Anything, mock.Anything, false, - false, ).Return(400, nil) setUpSignedCertificationCreatorMock := func(returnValue ...interface{}) services.SignedCertificationCreator { @@ -764,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 6e663bff4ea..0911d006cf0 100644 --- a/pkg/handlers/internalapi/orders.go +++ b/pkg/handlers/internalapi/orders.go @@ -168,9 +168,26 @@ func (h CreateOrdersHandler) Handle(params ordersop.CreateOrdersParams) middlewa return handlers.ResponseForError(appCtx.Logger(), err), err } - newDutyLocationGBLOC, err := models.FetchGBLOCForPostalCode(appCtx.DB(), newDutyLocation.Address.PostalCode) - if err != nil { - return handlers.ResponseForError(appCtx.Logger(), err), err + var newDutyLocationGBLOC *string + if *newDutyLocation.Address.IsOconus { + newDutyLocationGBLOCOconus, err := models.FetchAddressGbloc(appCtx.DB(), newDutyLocation.Address, serviceMember) + if err != nil { + return nil, apperror.NewNotFoundError(newDutyLocation.ID, "while looking for New Duty Location Oconus GBLOC") + } + newDutyLocationGBLOC = newDutyLocationGBLOCOconus + } else { + newDutyLocationGBLOCConus, err := models.FetchGBLOCForPostalCode(appCtx.DB(), newDutyLocation.Address.PostalCode) + if err != nil { + switch err { + case sql.ErrNoRows: + return nil, apperror.NewNotFoundError(newDutyLocation.ID, "while looking for New Duty Location PostalCodeToGBLOC") + default: + err = apperror.NewBadDataError("New duty location GBLOC cannot be verified") + appCtx.Logger().Error(err.Error()) + return handlers.ResponseForError(appCtx.Logger(), err), err + } + } + newDutyLocationGBLOC = &newDutyLocationGBLOCConus.GBLOC } var dependentsTwelveAndOver *int @@ -184,14 +201,24 @@ func (h CreateOrdersHandler) Handle(params ordersop.CreateOrdersParams) middlewa dependentsUnderTwelve = models.IntPointer(int(*payload.DependentsUnderTwelve)) } - originDutyLocationGBLOC, err := models.FetchGBLOCForPostalCode(appCtx.DB(), originDutyLocation.Address.PostalCode) - if err != nil { - switch err { - case sql.ErrNoRows: - return nil, apperror.NewNotFoundError(originDutyLocation.ID, "while looking for Duty Location PostalCodeToGBLOC") - default: - return nil, apperror.NewQueryError("PostalCodeToGBLOC", err, "") + var originDutyLocationGBLOC *string + if *originDutyLocation.Address.IsOconus { + originDutyLocationGBLOCOconus, err := models.FetchAddressGbloc(appCtx.DB(), originDutyLocation.Address, serviceMember) + if err != nil { + return nil, apperror.NewNotFoundError(originDutyLocation.ID, "while looking for Origin Duty Location Oconus GBLOC") + } + originDutyLocationGBLOC = originDutyLocationGBLOCOconus + } else { + originDutyLocationGBLOCConus, err := models.FetchGBLOCForPostalCode(appCtx.DB(), originDutyLocation.Address.PostalCode) + if err != nil { + switch err { + case sql.ErrNoRows: + return nil, apperror.NewNotFoundError(originDutyLocation.ID, "while looking for Origin Duty Location PostalCodeToGBLOC") + default: + return nil, apperror.NewQueryError("PostalCodeToGBLOC", err, "") + } } + originDutyLocationGBLOC = &originDutyLocationGBLOCConus.GBLOC } grade := payload.Grade @@ -278,9 +305,9 @@ func (h CreateOrdersHandler) Handle(params ordersop.CreateOrdersParams) middlewa &originDutyLocation, grade, &entitlement, - &originDutyLocationGBLOC.GBLOC, + originDutyLocationGBLOC, packingAndShippingInstructions, - &newDutyLocationGBLOC.GBLOC, + newDutyLocationGBLOC, ) if err != nil || verrs.HasAny() { return handlers.ResponseForVErrors(appCtx.Logger(), verrs, err), err @@ -368,11 +395,26 @@ func (h UpdateOrdersHandler) Handle(params ordersop.UpdateOrdersParams) middlewa return handlers.ResponseForError(appCtx.Logger(), err), err } - newDutyLocationGBLOC, err := models.FetchGBLOCForPostalCode(appCtx.DB(), dutyLocation.Address.PostalCode) - if err != nil { - err = apperror.NewBadDataError("New duty location GBLOC cannot be verified") - appCtx.Logger().Error(err.Error()) - return handlers.ResponseForError(appCtx.Logger(), err), err + var newDutyLocationGBLOC *string + if *dutyLocation.Address.IsOconus { + newDutyLocationGBLOCOconus, err := models.FetchAddressGbloc(appCtx.DB(), dutyLocation.Address, order.ServiceMember) + if err != nil { + return nil, apperror.NewNotFoundError(dutyLocation.ID, "while looking for New Duty Location Oconus GBLOC") + } + newDutyLocationGBLOC = newDutyLocationGBLOCOconus + } else { + newDutyLocationGBLOCConus, err := models.FetchGBLOCForPostalCode(appCtx.DB(), dutyLocation.Address.PostalCode) + if err != nil { + switch err { + case sql.ErrNoRows: + return nil, apperror.NewNotFoundError(dutyLocation.ID, "while looking for New Duty Location PostalCodeToGBLOC") + default: + err = apperror.NewBadDataError("New duty location GBLOC cannot be verified") + appCtx.Logger().Error(err.Error()) + return handlers.ResponseForError(appCtx.Logger(), err), err + } + } + newDutyLocationGBLOC = &newDutyLocationGBLOCConus.GBLOC } if payload.OriginDutyLocationID != "" { @@ -387,14 +429,28 @@ func (h UpdateOrdersHandler) Handle(params ordersop.UpdateOrdersParams) middlewa order.OriginDutyLocation = &originDutyLocation order.OriginDutyLocationID = &originDutyLocationID - originGBLOC, originGBLOCerr := models.FetchGBLOCForPostalCode(appCtx.DB(), originDutyLocation.Address.PostalCode) - if originGBLOCerr != nil { - return handlers.ResponseForError(appCtx.Logger(), originGBLOCerr), originGBLOCerr + var originDutyLocationGBLOC *string + if *originDutyLocation.Address.IsOconus { + originDutyLocationGBLOCOconus, err := models.FetchAddressGbloc(appCtx.DB(), originDutyLocation.Address, order.ServiceMember) + if err != nil { + return handlers.ResponseForError(appCtx.Logger(), err), err + } + originDutyLocationGBLOC = originDutyLocationGBLOCOconus + } else { + originDutyLocationGBLOCConus, err := models.FetchGBLOCForPostalCode(appCtx.DB(), originDutyLocation.Address.PostalCode) + if err != nil { + switch err { + case sql.ErrNoRows: + return nil, apperror.NewNotFoundError(originDutyLocation.ID, "while looking for Origin Duty Location PostalCodeToGBLOC") + default: + return handlers.ResponseForError(appCtx.Logger(), err), err + } + } + originDutyLocationGBLOC = &originDutyLocationGBLOCConus.GBLOC } - order.OriginDutyLocationGBLOC = &originGBLOC.GBLOC + order.OriginDutyLocationGBLOC = originDutyLocationGBLOC if payload.MoveID != "" { - moveID, err := uuid.FromString(payload.MoveID.String()) if err != nil { return handlers.ResponseForError(appCtx.Logger(), err), err @@ -433,7 +489,7 @@ func (h UpdateOrdersHandler) Handle(params ordersop.UpdateOrdersParams) middlewa order.SpouseHasProGear = *payload.SpouseHasProGear order.NewDutyLocationID = dutyLocation.ID order.NewDutyLocation = dutyLocation - order.DestinationGBLOC = &newDutyLocationGBLOC.GBLOC + order.DestinationGBLOC = newDutyLocationGBLOC order.TAC = payload.Tac order.SAC = payload.Sac diff --git a/pkg/handlers/internalapi/orders_test.go b/pkg/handlers/internalapi/orders_test.go index bf1b4a4303c..020d7bbb8dc 100644 --- a/pkg/handlers/internalapi/orders_test.go +++ b/pkg/handlers/internalapi/orders_test.go @@ -22,126 +22,240 @@ import ( "github.com/transcom/mymove/pkg/services/move" orderservice "github.com/transcom/mymove/pkg/services/order" storageTest "github.com/transcom/mymove/pkg/storage/test" + "github.com/transcom/mymove/pkg/testdatagen" "github.com/transcom/mymove/pkg/uploader" ) func (suite *HandlerSuite) TestCreateOrder() { - sm := factory.BuildExtendedServiceMember(suite.DB(), nil, nil) - suite.Run("can create conus and oconus orders", func() { - testCases := []struct { - test string - isOconus bool - }{ - {test: "Can create OCONUS order", isOconus: true}, - {test: "Can create CONUS order", isOconus: false}, - } - for _, tc := range testCases { - address := factory.BuildAddress(suite.DB(), []factory.Customization{ - { - Model: models.Address{ - IsOconus: &tc.isOconus, - }, + customAffiliation := models.AffiliationARMY + sm := factory.BuildExtendedServiceMember(suite.DB(), []factory.Customization{ + { + Model: models.ServiceMember{ + Affiliation: &customAffiliation, + }, + }, + }, nil) + suite.Run("can create conus orders", func() { + address := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + IsOconus: models.BoolPointer(false), }, - }, nil) + }, + }, nil) - originDutyLocation := factory.BuildDutyLocation(suite.DB(), []factory.Customization{ - { - Model: models.DutyLocation{ - Name: factory.MakeRandomString(8), - }, + originDutyLocation := factory.BuildDutyLocation(suite.DB(), []factory.Customization{ + { + Model: models.DutyLocation{ + Name: factory.MakeRandomString(8), + }, + }, + { + Model: address, + LinkOnly: true, + }, + }, nil) + + dutyLocation := factory.FetchOrBuildCurrentDutyLocation(suite.DB()) + factory.FetchOrBuildPostalCodeToGBLOC(suite.DB(), dutyLocation.Address.PostalCode, "KKFA") + factory.FetchOrBuildDefaultContractor(suite.DB(), nil, nil) + req := httptest.NewRequest("POST", "/orders", nil) + req = suite.AuthenticateRequest(req, sm) + + hasDependents := true + spouseHasProGear := true + issueDate := time.Date(2018, time.March, 10, 0, 0, 0, 0, time.UTC) + reportByDate := time.Date(2018, time.August, 1, 0, 0, 0, 0, time.UTC) + ordersType := internalmessages.OrdersTypePERMANENTCHANGEOFSTATION + deptIndicator := internalmessages.DeptIndicatorAIRANDSPACEFORCE + payload := &internalmessages.CreateUpdateOrders{ + HasDependents: handlers.FmtBool(hasDependents), + SpouseHasProGear: handlers.FmtBool(spouseHasProGear), + IssueDate: handlers.FmtDate(issueDate), + ReportByDate: handlers.FmtDate(reportByDate), + OrdersType: internalmessages.NewOrdersType(ordersType), + OriginDutyLocationID: *handlers.FmtUUIDPtr(&originDutyLocation.ID), + NewDutyLocationID: handlers.FmtUUID(dutyLocation.ID), + ServiceMemberID: handlers.FmtUUID(sm.ID), + OrdersNumber: handlers.FmtString("123456"), + Tac: handlers.FmtString("E19A"), + Sac: handlers.FmtString("SacNumber"), + DepartmentIndicator: internalmessages.NewDeptIndicator(deptIndicator), + Grade: models.ServiceMemberGradeE1.Pointer(), + } + + params := ordersop.CreateOrdersParams{ + HTTPRequest: req, + CreateOrders: payload, + } + + fakeS3 := storageTest.NewFakeS3Storage(true) + handlerConfig := suite.HandlerConfig() + handlerConfig.SetFileStorer(fakeS3) + createHandler := CreateOrdersHandler{handlerConfig} + + response := createHandler.Handle(params) + + suite.Assertions.IsType(&ordersop.CreateOrdersCreated{}, response) + okResponse := response.(*ordersop.CreateOrdersCreated) + orderID := okResponse.Payload.ID.String() + createdOrder, _ := models.FetchOrder(suite.DB(), uuid.FromStringOrNil(orderID)) + var createdEntitlement models.Entitlement + err := suite.DB().Find(&createdEntitlement, createdOrder.EntitlementID) + suite.NoError(err) + suite.NotEmpty(createdEntitlement) + suite.Assertions.Equal(sm.ID.String(), okResponse.Payload.ServiceMemberID.String()) + suite.Assertions.Len(okResponse.Payload.Moves, 1) + suite.Assertions.Equal(ordersType, *okResponse.Payload.OrdersType) + suite.Assertions.Equal(handlers.FmtString("123456"), okResponse.Payload.OrdersNumber) + suite.Assertions.Equal(handlers.FmtString("E19A"), okResponse.Payload.Tac) + suite.Assertions.Equal(handlers.FmtString("SacNumber"), okResponse.Payload.Sac) + suite.Assertions.Equal(&deptIndicator, okResponse.Payload.DepartmentIndicator) + suite.Assertions.Equal(*models.Int64Pointer(8000), *okResponse.Payload.AuthorizedWeight) + suite.NotNil(&createdOrder.Entitlement) + suite.NotEmpty(createdOrder.SupplyAndServicesCostEstimate) + suite.NotEmpty(createdOrder.PackingAndShippingInstructions) + suite.NotEmpty(createdOrder.MethodOfPayment) + suite.NotEmpty(createdOrder.NAICS) + suite.Nil(createdEntitlement.AccompaniedTour) + suite.Nil(createdEntitlement.DependentsTwelveAndOver) + suite.Nil(createdEntitlement.DependentsUnderTwelve) + }) + + suite.Run("can create oconus orders", func() { + usprc, err := models.FindByZipCode(suite.AppContextForTest().DB(), "99801") + suite.NotNil(usprc) + suite.FatalNoError(err) + + address := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + IsOconus: models.BoolPointer(true), + UsPostRegionCityID: &usprc.ID, }, - { - Model: address, - LinkOnly: true, + }, + }, nil) + + originDutyLocation := factory.BuildDutyLocation(suite.DB(), []factory.Customization{ + { + Model: models.DutyLocation{ + Name: factory.MakeRandomString(8), + AddressID: address.ID, }, - }, nil) - - dutyLocation := factory.FetchOrBuildCurrentDutyLocation(suite.DB()) - factory.FetchOrBuildPostalCodeToGBLOC(suite.DB(), dutyLocation.Address.PostalCode, "KKFA") - factory.FetchOrBuildDefaultContractor(suite.DB(), nil, nil) - - req := httptest.NewRequest("POST", "/orders", nil) - req = suite.AuthenticateRequest(req, sm) - - hasDependents := true - spouseHasProGear := true - issueDate := time.Date(2018, time.March, 10, 0, 0, 0, 0, time.UTC) - reportByDate := time.Date(2018, time.August, 1, 0, 0, 0, 0, time.UTC) - ordersType := internalmessages.OrdersTypePERMANENTCHANGEOFSTATION - deptIndicator := internalmessages.DeptIndicatorAIRANDSPACEFORCE - payload := &internalmessages.CreateUpdateOrders{ - HasDependents: handlers.FmtBool(hasDependents), - SpouseHasProGear: handlers.FmtBool(spouseHasProGear), - IssueDate: handlers.FmtDate(issueDate), - ReportByDate: handlers.FmtDate(reportByDate), - OrdersType: internalmessages.NewOrdersType(ordersType), - OriginDutyLocationID: *handlers.FmtUUIDPtr(&originDutyLocation.ID), - NewDutyLocationID: handlers.FmtUUID(dutyLocation.ID), - ServiceMemberID: handlers.FmtUUID(sm.ID), - OrdersNumber: handlers.FmtString("123456"), - Tac: handlers.FmtString("E19A"), - Sac: handlers.FmtString("SacNumber"), - DepartmentIndicator: internalmessages.NewDeptIndicator(deptIndicator), - Grade: models.ServiceMemberGradeE1.Pointer(), - } + }, + }, nil) - if tc.isOconus { - payload.AccompaniedTour = models.BoolPointer(true) - payload.DependentsTwelveAndOver = models.Int64Pointer(5) - payload.DependentsUnderTwelve = models.Int64Pointer(5) - } + dutyLocation := factory.FetchOrBuildCurrentDutyLocation(suite.DB()) - params := ordersop.CreateOrdersParams{ - HTTPRequest: req, - CreateOrders: payload, - } + contract := testdatagen.FetchOrMakeReContract(suite.DB(), testdatagen.Assertions{}) - fakeS3 := storageTest.NewFakeS3Storage(true) - handlerConfig := suite.HandlerConfig() - handlerConfig.SetFileStorer(fakeS3) - createHandler := CreateOrdersHandler{handlerConfig} - - response := createHandler.Handle(params) - - suite.Assertions.IsType(&ordersop.CreateOrdersCreated{}, response) - okResponse := response.(*ordersop.CreateOrdersCreated) - orderID := okResponse.Payload.ID.String() - createdOrder, _ := models.FetchOrder(suite.DB(), uuid.FromStringOrNil(orderID)) - var createdEntitlement models.Entitlement - err := suite.DB().Find(&createdEntitlement, createdOrder.EntitlementID) - suite.NoError(err) - suite.NotEmpty(createdEntitlement) - suite.Assertions.Equal(sm.ID.String(), okResponse.Payload.ServiceMemberID.String()) - suite.Assertions.Len(okResponse.Payload.Moves, 1) - suite.Assertions.Equal(ordersType, *okResponse.Payload.OrdersType) - suite.Assertions.Equal(handlers.FmtString("123456"), okResponse.Payload.OrdersNumber) - suite.Assertions.Equal(handlers.FmtString("E19A"), okResponse.Payload.Tac) - suite.Assertions.Equal(handlers.FmtString("SacNumber"), okResponse.Payload.Sac) - suite.Assertions.Equal(&deptIndicator, okResponse.Payload.DepartmentIndicator) - suite.Assertions.Equal(*models.Int64Pointer(8000), *okResponse.Payload.AuthorizedWeight) - suite.NotNil(&createdOrder.Entitlement) - suite.NotEmpty(createdOrder.SupplyAndServicesCostEstimate) - suite.NotEmpty(createdOrder.PackingAndShippingInstructions) - suite.NotEmpty(createdOrder.MethodOfPayment) - suite.NotEmpty(createdOrder.NAICS) - if tc.isOconus { - suite.NotNil(createdEntitlement.AccompaniedTour) - suite.NotNil(createdEntitlement.DependentsTwelveAndOver) - suite.NotNil(createdEntitlement.DependentsUnderTwelve) - } else { - suite.Nil(createdEntitlement.AccompaniedTour) - suite.Nil(createdEntitlement.DependentsTwelveAndOver) - suite.Nil(createdEntitlement.DependentsUnderTwelve) - } + rateAreaCode := uuid.Must(uuid.NewV4()).String()[0:5] + rateArea := testdatagen.FetchOrMakeReRateArea(suite.DB(), testdatagen.Assertions{ + ReRateArea: models.ReRateArea{ + ContractID: contract.ID, + IsOconus: true, + Name: fmt.Sprintf("Alaska-%s", rateAreaCode), + Contract: contract, + }, + }) + suite.NotNil(rateArea) + suite.Nil(err) + + us_country, err := models.FetchCountryByCode(suite.DB(), "US") + suite.NotNil(us_country) + suite.Nil(err) + + oconusRateArea, err := models.FetchOconusRateAreaByCityId(suite.DB(), usprc.ID.String()) + suite.NotNil(oconusRateArea) + suite.Nil(err) + + jppsoRegion, err := models.FetchJppsoRegionByCode(suite.DB(), "MAPK") + suite.NotNil(jppsoRegion) + suite.Nil(err) + gblocAors, err := models.FetchGblocAorsByJppsoCodeRateAreaDept(suite.DB(), jppsoRegion.ID, oconusRateArea.ID, models.DepartmentIndicatorARMY.String()) + suite.NotNil(gblocAors) + suite.Nil(err) + + factory.FetchOrBuildDefaultContractor(suite.DB(), nil, nil) + req := httptest.NewRequest("POST", "/orders", nil) + req = suite.AuthenticateRequest(req, sm) + + hasDependents := true + spouseHasProGear := true + issueDate := time.Date(2018, time.March, 10, 0, 0, 0, 0, time.UTC) + reportByDate := time.Date(2018, time.August, 1, 0, 0, 0, 0, time.UTC) + ordersType := internalmessages.OrdersTypePERMANENTCHANGEOFSTATION + deptIndicator := internalmessages.DeptIndicatorAIRANDSPACEFORCE + payload := &internalmessages.CreateUpdateOrders{ + HasDependents: handlers.FmtBool(hasDependents), + SpouseHasProGear: handlers.FmtBool(spouseHasProGear), + IssueDate: handlers.FmtDate(issueDate), + ReportByDate: handlers.FmtDate(reportByDate), + OrdersType: internalmessages.NewOrdersType(ordersType), + OriginDutyLocationID: *handlers.FmtUUIDPtr(&originDutyLocation.ID), + NewDutyLocationID: handlers.FmtUUID(dutyLocation.ID), + ServiceMemberID: handlers.FmtUUID(sm.ID), + OrdersNumber: handlers.FmtString("123456"), + Tac: handlers.FmtString("E19A"), + Sac: handlers.FmtString("SacNumber"), + DepartmentIndicator: internalmessages.NewDeptIndicator(deptIndicator), + Grade: models.ServiceMemberGradeE1.Pointer(), + AccompaniedTour: models.BoolPointer(true), + DependentsTwelveAndOver: models.Int64Pointer(5), + DependentsUnderTwelve: models.Int64Pointer(5), } + + params := ordersop.CreateOrdersParams{ + HTTPRequest: req, + CreateOrders: payload, + } + + fakeS3 := storageTest.NewFakeS3Storage(true) + handlerConfig := suite.HandlerConfig() + handlerConfig.SetFileStorer(fakeS3) + createHandler := CreateOrdersHandler{handlerConfig} + response := createHandler.Handle(params) + + suite.Assertions.IsType(&ordersop.CreateOrdersCreated{}, response) + okResponse := response.(*ordersop.CreateOrdersCreated) + + orderID := okResponse.Payload.ID.String() + createdOrder, _ := models.FetchOrder(suite.DB(), uuid.FromStringOrNil(orderID)) + var createdEntitlement models.Entitlement + err = suite.DB().Find(&createdEntitlement, createdOrder.EntitlementID) + suite.NoError(err) + suite.NotEmpty(createdEntitlement) + suite.Assertions.Equal(sm.ID.String(), okResponse.Payload.ServiceMemberID.String()) + suite.Assertions.Len(okResponse.Payload.Moves, 1) + suite.Assertions.Equal(ordersType, *okResponse.Payload.OrdersType) + suite.Assertions.Equal(handlers.FmtString("123456"), okResponse.Payload.OrdersNumber) + suite.Assertions.Equal(handlers.FmtString("MAPK"), okResponse.Payload.OriginDutyLocationGbloc) + suite.Assertions.Equal(handlers.FmtString("E19A"), okResponse.Payload.Tac) + suite.Assertions.Equal(handlers.FmtString("SacNumber"), okResponse.Payload.Sac) + suite.Assertions.Equal(&deptIndicator, okResponse.Payload.DepartmentIndicator) + suite.Assertions.Equal(*models.Int64Pointer(8000), *okResponse.Payload.AuthorizedWeight) + suite.NotNil(&createdOrder.Entitlement) + suite.NotEmpty(createdOrder.SupplyAndServicesCostEstimate) + suite.NotEmpty(createdOrder.PackingAndShippingInstructions) + suite.NotEmpty(createdOrder.MethodOfPayment) + suite.NotEmpty(createdOrder.NAICS) + suite.NotNil(createdEntitlement.AccompaniedTour) + suite.NotNil(createdEntitlement.DependentsTwelveAndOver) + suite.NotNil(createdEntitlement.DependentsUnderTwelve) + }) suite.Run("properly handles entitlement validation", func() { + usprc, err := models.FindByZipCode(suite.AppContextForTest().DB(), "99506") + suite.NotNil(usprc) + suite.FatalNoError(err) + address := factory.BuildAddress(suite.DB(), []factory.Customization{ { Model: models.Address{ - IsOconus: models.BoolPointer(true), + IsOconus: models.BoolPointer(true), + UsPostRegionCityID: &usprc.ID, }, }, }, nil) @@ -149,17 +263,43 @@ func (suite *HandlerSuite) TestCreateOrder() { originDutyLocation := factory.BuildDutyLocation(suite.DB(), []factory.Customization{ { Model: models.DutyLocation{ - Name: factory.MakeRandomString(8), + Name: factory.MakeRandomString(8), + AddressID: address.ID, }, }, - { - Model: address, - LinkOnly: true, - }, }, nil) dutyLocation := factory.FetchOrBuildCurrentDutyLocation(suite.DB()) - factory.FetchOrBuildPostalCodeToGBLOC(suite.DB(), dutyLocation.Address.PostalCode, "KKFA") + + contract := testdatagen.FetchOrMakeReContract(suite.DB(), testdatagen.Assertions{}) + + rateAreaCode := uuid.Must(uuid.NewV4()).String()[0:5] + rateArea := testdatagen.FetchOrMakeReRateArea(suite.DB(), testdatagen.Assertions{ + ReRateArea: models.ReRateArea{ + ContractID: contract.ID, + IsOconus: true, + Name: fmt.Sprintf("Alaska-%s", rateAreaCode), + Contract: contract, + }, + }) + suite.NotNil(rateArea) + + us_country, err := models.FetchCountryByCode(suite.DB(), "US") + suite.NotNil(us_country) + suite.Nil(err) + + oconusRateArea, err := models.FetchOconusRateAreaByCityId(suite.DB(), usprc.ID.String()) + suite.NotNil(oconusRateArea) + suite.Nil(err) + + jppsoRegion, err := models.FetchJppsoRegionByCode(suite.DB(), "MBFL") + suite.NotNil(jppsoRegion) + suite.Nil(err) + + gblocAors, err := models.FetchGblocAorsByJppsoCodeRateAreaDept(suite.DB(), jppsoRegion.ID, oconusRateArea.ID, models.DepartmentIndicatorARMY.String()) + suite.NotNil(gblocAors) + suite.Nil(err) + factory.FetchOrBuildDefaultContractor(suite.DB(), nil, nil) req := httptest.NewRequest("POST", "/orders", nil) @@ -577,144 +717,273 @@ func (suite *HandlerSuite) TestUploadAmendedOrdersHandlerIntegration() { func (suite *HandlerSuite) TestUpdateOrdersHandler() { waf := entitlements.NewWeightAllotmentFetcher() - suite.Run("Can update CONUS and OCONUS orders", func() { - testCases := []struct { - isOconus bool - }{ - {isOconus: true}, - {isOconus: false}, - } - for _, tc := range testCases { - address := factory.BuildAddress(suite.DB(), []factory.Customization{ - { - Model: models.Address{ - IsOconus: &tc.isOconus, - }, + suite.Run("Can update CONUS orders", func() { + address := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + IsOconus: models.BoolPointer(false), }, - }, nil) + }, + }, nil) - // Set duty location to either CONUS or OCONUS - dutyLocation := factory.BuildDutyLocation(suite.DB(), []factory.Customization{ - { - Model: models.DutyLocation{ - ProvidesServicesCounseling: false, - }, + originDutyLocation := factory.BuildDutyLocation(suite.DB(), []factory.Customization{ + { + Model: models.DutyLocation{ + Name: factory.MakeRandomString(8), }, - { - Model: address, - LinkOnly: true, + }, + { + Model: address, + LinkOnly: true, + }, + }, nil) + order := factory.BuildOrder(suite.DB(), []factory.Customization{ + { + Model: originDutyLocation, + LinkOnly: true, + Type: &factory.DutyLocations.OriginDutyLocation, + }, + }, nil) + move := factory.BuildMove(suite.DB(), []factory.Customization{ + { + Model: order, + LinkOnly: true, + }}, nil) + + newDutyLocation := factory.BuildDutyLocation(suite.DB(), nil, nil) + + newOrdersType := internalmessages.OrdersTypePERMANENTCHANGEOFSTATION + newOrdersNumber := "123456" + issueDate := time.Date(2018, time.March, 10, 0, 0, 0, 0, time.UTC) + reportByDate := time.Date(2018, time.August, 1, 0, 0, 0, 0, time.UTC) + deptIndicator := internalmessages.DeptIndicatorAIRANDSPACEFORCE + + payload := &internalmessages.CreateUpdateOrders{ + OrdersNumber: handlers.FmtString(newOrdersNumber), + OrdersType: &newOrdersType, + NewDutyLocationID: handlers.FmtUUID(newDutyLocation.ID), + OriginDutyLocationID: *handlers.FmtUUID(*order.OriginDutyLocationID), + IssueDate: handlers.FmtDate(issueDate), + ReportByDate: handlers.FmtDate(reportByDate), + DepartmentIndicator: &deptIndicator, + HasDependents: handlers.FmtBool(false), + SpouseHasProGear: handlers.FmtBool(false), + Grade: models.ServiceMemberGradeE4.Pointer(), + MoveID: *handlers.FmtUUID(move.ID), + CounselingOfficeID: handlers.FmtUUID(*newDutyLocation.TransportationOfficeID), + ServiceMemberID: handlers.FmtUUID(order.ServiceMemberID), + } + + path := fmt.Sprintf("/orders/%v", order.ID.String()) + req := httptest.NewRequest("PUT", path, nil) + req = suite.AuthenticateRequest(req, order.ServiceMember) + + params := ordersop.UpdateOrdersParams{ + HTTPRequest: req, + OrdersID: *handlers.FmtUUID(order.ID), + UpdateOrders: payload, + } + + fakeS3 := storageTest.NewFakeS3Storage(true) + handlerConfig := suite.HandlerConfig() + handlerConfig.SetFileStorer(fakeS3) + + handler := UpdateOrdersHandler{handlerConfig} + + response := handler.Handle(params) + + suite.IsType(&ordersop.UpdateOrdersOK{}, response) + okResponse := response.(*ordersop.UpdateOrdersOK) + + suite.NoError(okResponse.Payload.Validate(strfmt.Default)) + suite.Equal(string(newOrdersType), string(*okResponse.Payload.OrdersType)) + suite.Equal(newOrdersNumber, *okResponse.Payload.OrdersNumber) + + updatedOrder, err := models.FetchOrder(suite.DB(), order.ID) + suite.NoError(err) + suite.Equal(payload.Grade, updatedOrder.Grade) + suite.Equal(*okResponse.Payload.AuthorizedWeight, int64(7000)) // E4 authorized weight is 7000, make sure we return that in the response + expectedUpdatedOrderWeightAllotment, err := waf.GetWeightAllotment(suite.AppContextForTest(), string(*updatedOrder.Grade), updatedOrder.OrdersType) + suite.NoError(err) + expectedUpdatedOrderAuthorizedWeight := expectedUpdatedOrderWeightAllotment.TotalWeightSelf + if *payload.HasDependents { + expectedUpdatedOrderAuthorizedWeight = expectedUpdatedOrderWeightAllotment.TotalWeightSelfPlusDependents + } + + expectedOriginalOrderWeightAllotment, err := waf.GetWeightAllotment(suite.AppContextForTest(), string(*order.Grade), updatedOrder.OrdersType) + suite.NoError(err) + expectedOriginalOrderAuthorizedWeight := expectedOriginalOrderWeightAllotment.TotalWeightSelf + if *payload.HasDependents { + expectedUpdatedOrderAuthorizedWeight = expectedOriginalOrderWeightAllotment.TotalWeightSelfPlusDependents + } + + suite.Equal(expectedUpdatedOrderAuthorizedWeight, 7000) // Ensure that when GetWeightAllotment is recalculated that it also returns 7000. This ensures that the database stored the correct information + suite.Equal(expectedOriginalOrderAuthorizedWeight, 5000) // The order was created as an E1. Ensure that the E1 authorized weight is 5000. + suite.Equal(string(newOrdersType), string(updatedOrder.OrdersType)) + // Check updated entitlement + var updatedEntitlement models.Entitlement + err = suite.DB().Find(&updatedEntitlement, updatedOrder.EntitlementID) + suite.NoError(err) + suite.NotEmpty(updatedEntitlement) + + suite.Nil(updatedEntitlement.AccompaniedTour) + suite.Nil(updatedEntitlement.DependentsTwelveAndOver) + suite.Nil(updatedEntitlement.DependentsUnderTwelve) + }) + + suite.Run("Can update OCONUS orders", func() { + usprc, err := models.FindByZipCode(suite.AppContextForTest().DB(), "99801") + suite.NotNil(usprc) + suite.FatalNoError(err) + + address := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + IsOconus: models.BoolPointer(true), + UsPostRegionCityID: &usprc.ID, }, - }, nil) - order := factory.BuildOrder(suite.DB(), []factory.Customization{ - { - Model: dutyLocation, - LinkOnly: true, - Type: &factory.DutyLocations.OriginDutyLocation, + }, + }, nil) + + originDutyLocation := factory.BuildDutyLocation(suite.DB(), []factory.Customization{ + { + Model: models.DutyLocation{ + Name: factory.MakeRandomString(8), + AddressID: address.ID, }, - }, nil) - move := factory.BuildMove(suite.DB(), []factory.Customization{ - { - Model: order, - LinkOnly: true, - }}, nil) - - newDutyLocation := factory.BuildDutyLocation(suite.DB(), nil, nil) - newTransportationOffice := factory.BuildTransportationOffice(suite.DB(), nil, nil) - newDutyLocation.TransportationOffice = newTransportationOffice - - newOrdersType := internalmessages.OrdersTypePERMANENTCHANGEOFSTATION - newOrdersNumber := "123456" - issueDate := time.Date(2018, time.March, 10, 0, 0, 0, 0, time.UTC) - reportByDate := time.Date(2018, time.August, 1, 0, 0, 0, 0, time.UTC) - deptIndicator := internalmessages.DeptIndicatorAIRANDSPACEFORCE - - payload := &internalmessages.CreateUpdateOrders{ - OrdersNumber: handlers.FmtString(newOrdersNumber), - OrdersType: &newOrdersType, - NewDutyLocationID: handlers.FmtUUID(newDutyLocation.ID), - OriginDutyLocationID: *handlers.FmtUUID(*order.OriginDutyLocationID), - IssueDate: handlers.FmtDate(issueDate), - ReportByDate: handlers.FmtDate(reportByDate), - DepartmentIndicator: &deptIndicator, - HasDependents: handlers.FmtBool(false), - SpouseHasProGear: handlers.FmtBool(false), - Grade: models.ServiceMemberGradeE4.Pointer(), - MoveID: *handlers.FmtUUID(move.ID), - CounselingOfficeID: handlers.FmtUUID(*newDutyLocation.TransportationOfficeID), - ServiceMemberID: handlers.FmtUUID(order.ServiceMemberID), - } - // The default move factory does not include OCONUS fields, set these - // new fields conditionally for the update - if tc.isOconus { - payload.AccompaniedTour = models.BoolPointer(true) - payload.DependentsTwelveAndOver = models.Int64Pointer(5) - payload.DependentsUnderTwelve = models.Int64Pointer(5) - } + }, + }, nil) - path := fmt.Sprintf("/orders/%v", order.ID.String()) - req := httptest.NewRequest("PUT", path, nil) - req = suite.AuthenticateRequest(req, order.ServiceMember) + order := factory.BuildOrder(suite.DB(), []factory.Customization{ + { + Model: originDutyLocation, + LinkOnly: true, + Type: &factory.DutyLocations.OriginDutyLocation, + }, + }, nil) - params := ordersop.UpdateOrdersParams{ - HTTPRequest: req, - OrdersID: *handlers.FmtUUID(order.ID), - UpdateOrders: payload, - } + move := factory.BuildMove(suite.DB(), []factory.Customization{ + { + Model: order, + LinkOnly: true, + }}, nil) - fakeS3 := storageTest.NewFakeS3Storage(true) - handlerConfig := suite.HandlerConfig() - handlerConfig.SetFileStorer(fakeS3) + newDutyLocation := factory.BuildDutyLocation(suite.DB(), nil, nil) - handler := UpdateOrdersHandler{handlerConfig} + contract := testdatagen.FetchOrMakeReContract(suite.DB(), testdatagen.Assertions{}) - response := handler.Handle(params) + rateAreaCode := uuid.Must(uuid.NewV4()).String()[0:5] + rateArea := testdatagen.FetchOrMakeReRateArea(suite.DB(), testdatagen.Assertions{ + ReRateArea: models.ReRateArea{ + ContractID: contract.ID, + IsOconus: true, + Name: fmt.Sprintf("Alaska-%s", rateAreaCode), + Contract: contract, + }, + }) + suite.NotNil(rateArea) + suite.Nil(err) - suite.IsType(&ordersop.UpdateOrdersOK{}, response) - okResponse := response.(*ordersop.UpdateOrdersOK) + us_country, err := models.FetchCountryByCode(suite.DB(), "US") + suite.NotNil(us_country) + suite.Nil(err) - suite.NoError(okResponse.Payload.Validate(strfmt.Default)) - suite.Equal(string(newOrdersType), string(*okResponse.Payload.OrdersType)) - suite.Equal(newOrdersNumber, *okResponse.Payload.OrdersNumber) + oconusRateArea, err := models.FetchOconusRateAreaByCityId(suite.DB(), usprc.ID.String()) + suite.NotNil(oconusRateArea) + suite.Nil(err) - updatedOrder, err := models.FetchOrder(suite.DB(), order.ID) - suite.NoError(err) - suite.Equal(payload.Grade, updatedOrder.Grade) - suite.Equal(*okResponse.Payload.AuthorizedWeight, int64(7000)) // E4 authorized weight is 7000, make sure we return that in the response - expectedUpdatedOrderWeightAllotment, err := waf.GetWeightAllotment(suite.AppContextForTest(), string(*updatedOrder.Grade), updatedOrder.OrdersType) - suite.NoError(err) - expectedUpdatedOrderAuthorizedWeight := expectedUpdatedOrderWeightAllotment.TotalWeightSelf - if *payload.HasDependents { - expectedUpdatedOrderAuthorizedWeight = expectedUpdatedOrderWeightAllotment.TotalWeightSelfPlusDependents - } + jppsoRegion, err := models.FetchJppsoRegionByCode(suite.DB(), "MAPK") + suite.NotNil(jppsoRegion) + suite.Nil(err) - expectedOriginalOrderWeightAllotment, err := waf.GetWeightAllotment(suite.AppContextForTest(), string(*order.Grade), updatedOrder.OrdersType) - suite.NoError(err) - expectedOriginalOrderAuthorizedWeight := expectedOriginalOrderWeightAllotment.TotalWeightSelf - if *payload.HasDependents { - expectedUpdatedOrderAuthorizedWeight = expectedOriginalOrderWeightAllotment.TotalWeightSelfPlusDependents - } + gblocAors, err := models.FetchGblocAorsByJppsoCodeRateAreaDept(suite.DB(), jppsoRegion.ID, oconusRateArea.ID, models.DepartmentIndicatorARMY.String()) + suite.NotNil(gblocAors) + suite.Nil(err) - suite.Equal(expectedUpdatedOrderAuthorizedWeight, 7000) // Ensure that when GetWeightAllotment is recalculated that it also returns 7000. This ensures that the database stored the correct information - suite.Equal(expectedOriginalOrderAuthorizedWeight, 5000) // The order was created as an E1. Ensure that the E1 authorized weight is 5000. - suite.Equal(string(newOrdersType), string(updatedOrder.OrdersType)) - // Check updated entitlement - var updatedEntitlement models.Entitlement - err = suite.DB().Find(&updatedEntitlement, updatedOrder.EntitlementID) - suite.NoError(err) - suite.NotEmpty(updatedEntitlement) - - if tc.isOconus { - suite.NotNil(updatedEntitlement.AccompaniedTour) - suite.NotNil(updatedEntitlement.DependentsTwelveAndOver) - suite.NotNil(updatedEntitlement.DependentsUnderTwelve) - } else { - suite.Nil(updatedEntitlement.AccompaniedTour) - suite.Nil(updatedEntitlement.DependentsTwelveAndOver) - suite.Nil(updatedEntitlement.DependentsUnderTwelve) - } + newOrdersType := internalmessages.OrdersTypePERMANENTCHANGEOFSTATION + newOrdersNumber := "123456" + issueDate := time.Date(2018, time.March, 10, 0, 0, 0, 0, time.UTC) + reportByDate := time.Date(2018, time.August, 1, 0, 0, 0, 0, time.UTC) + deptIndicator := internalmessages.DeptIndicatorAIRANDSPACEFORCE + + payload := &internalmessages.CreateUpdateOrders{ + OrdersNumber: handlers.FmtString(newOrdersNumber), + OrdersType: &newOrdersType, + NewDutyLocationID: handlers.FmtUUID(newDutyLocation.ID), + OriginDutyLocationID: *handlers.FmtUUID(*order.OriginDutyLocationID), + IssueDate: handlers.FmtDate(issueDate), + ReportByDate: handlers.FmtDate(reportByDate), + DepartmentIndicator: &deptIndicator, + HasDependents: handlers.FmtBool(false), + SpouseHasProGear: handlers.FmtBool(false), + Grade: models.ServiceMemberGradeE4.Pointer(), + MoveID: *handlers.FmtUUID(move.ID), + CounselingOfficeID: handlers.FmtUUID(*newDutyLocation.TransportationOfficeID), + ServiceMemberID: handlers.FmtUUID(order.ServiceMemberID), + } + + payload.AccompaniedTour = models.BoolPointer(true) + payload.DependentsTwelveAndOver = models.Int64Pointer(5) + payload.DependentsUnderTwelve = models.Int64Pointer(5) + + path := fmt.Sprintf("/orders/%v", order.ID.String()) + req := httptest.NewRequest("PUT", path, nil) + req = suite.AuthenticateRequest(req, order.ServiceMember) + + params := ordersop.UpdateOrdersParams{ + HTTPRequest: req, + OrdersID: *handlers.FmtUUID(order.ID), + UpdateOrders: payload, } + + fakeS3 := storageTest.NewFakeS3Storage(true) + handlerConfig := suite.HandlerConfig() + handlerConfig.SetFileStorer(fakeS3) + + handler := UpdateOrdersHandler{handlerConfig} + + response := handler.Handle(params) + + suite.IsType(&ordersop.UpdateOrdersOK{}, response) + okResponse := response.(*ordersop.UpdateOrdersOK) + + suite.NoError(okResponse.Payload.Validate(strfmt.Default)) + suite.Equal(string(newOrdersType), string(*okResponse.Payload.OrdersType)) + suite.Equal(newOrdersNumber, *okResponse.Payload.OrdersNumber) + + updatedOrder, err := models.FetchOrder(suite.DB(), order.ID) + suite.NoError(err) + suite.Equal(payload.Grade, updatedOrder.Grade) + suite.Equal(*okResponse.Payload.AuthorizedWeight, int64(7000)) // E4 authorized weight is 7000, make sure we return that in the response + expectedUpdatedOrderWeightAllotment, err := waf.GetWeightAllotment(suite.AppContextForTest(), string(*updatedOrder.Grade), updatedOrder.OrdersType) + suite.NoError(err) + expectedUpdatedOrderAuthorizedWeight := expectedUpdatedOrderWeightAllotment.TotalWeightSelf + if *payload.HasDependents { + expectedUpdatedOrderAuthorizedWeight = expectedUpdatedOrderWeightAllotment.TotalWeightSelfPlusDependents + } + + expectedOriginalOrderWeightAllotment, err := waf.GetWeightAllotment(suite.AppContextForTest(), string(*order.Grade), updatedOrder.OrdersType) + suite.NoError(err) + expectedOriginalOrderAuthorizedWeight := expectedOriginalOrderWeightAllotment.TotalWeightSelf + if *payload.HasDependents { + expectedUpdatedOrderAuthorizedWeight = expectedOriginalOrderWeightAllotment.TotalWeightSelfPlusDependents + } + + suite.Equal(expectedUpdatedOrderAuthorizedWeight, 7000) // Ensure that when GetWeightAllotment is recalculated that it also returns 7000. This ensures that the database stored the correct information + suite.Equal(expectedOriginalOrderAuthorizedWeight, 5000) // The order was created as an E1. Ensure that the E1 authorized weight is 5000. + suite.Equal(string(newOrdersType), string(updatedOrder.OrdersType)) + // Check updated entitlement + var updatedEntitlement models.Entitlement + err = suite.DB().Find(&updatedEntitlement, updatedOrder.EntitlementID) + suite.NoError(err) + suite.NotEmpty(updatedEntitlement) + + suite.NotNil(updatedEntitlement.AccompaniedTour) + suite.NotNil(updatedEntitlement.DependentsTwelveAndOver) + suite.NotNil(updatedEntitlement.DependentsUnderTwelve) }) + } func (suite *HandlerSuite) TestUpdateOrdersHandlerOriginPostalCodeAndGBLOC() { diff --git a/pkg/handlers/internalapi/transportation_offices.go b/pkg/handlers/internalapi/transportation_offices.go index 6b535fd9ba9..7f8529fe479 100644 --- a/pkg/handlers/internalapi/transportation_offices.go +++ b/pkg/handlers/internalapi/transportation_offices.go @@ -51,7 +51,7 @@ func (h GetTransportationOfficesHandler) Handle(params transportationofficeop.Ge return transportationofficeop.NewGetTransportationOfficesForbidden(), noServiceMemberIDErr } - transportationOffices, err := h.TransportationOfficesFetcher.GetTransportationOffices(appCtx, params.Search, true) + transportationOffices, err := h.TransportationOfficesFetcher.GetTransportationOffices(appCtx, params.Search, true, false) if err != nil { appCtx.Logger().Error("Error searching for Transportation Offices: ", zap.Error(err)) return transportationofficeop.NewGetTransportationOfficesInternalServerError(), err @@ -74,7 +74,7 @@ func (h ShowCounselingOfficesHandler) Handle(params transportationofficeop.ShowC func(appCtx appcontext.AppContext) (middleware.Responder, error) { dutyLocationID := uuid.FromStringOrNil(params.DutyLocationID.String()) - counselingOffices, err := h.TransportationOfficesFetcher.GetCounselingOffices(appCtx, dutyLocationID) + counselingOffices, err := h.TransportationOfficesFetcher.GetCounselingOffices(appCtx, dutyLocationID, appCtx.Session().ServiceMemberID) if err != nil { appCtx.Logger().Error("Error searching for Counseling Offices: ", zap.Error(err)) return transportationofficeop.NewShowCounselingOfficesInternalServerError(), err diff --git a/pkg/handlers/internalapi/transportation_offices_test.go b/pkg/handlers/internalapi/transportation_offices_test.go index b41c5aff7bf..64cd3db6cb2 100644 --- a/pkg/handlers/internalapi/transportation_offices_test.go +++ b/pkg/handlers/internalapi/transportation_offices_test.go @@ -153,7 +153,6 @@ func (suite *HandlerSuite) TestShowCounselingOfficesHandler() { }, }, }, nil) - suite.MustSave(&origDutyLocation) path := fmt.Sprintf("/transportation_offices/%v/counseling_offices", origDutyLocation.ID.String()) req := httptest.NewRequest("GET", path, nil) @@ -174,4 +173,13 @@ func (suite *HandlerSuite) TestShowCounselingOfficesHandler() { // Validate outgoing payload suite.NoError(responsePayload.Payload.Validate(strfmt.Default)) + // Validate outgoing payload + suite.NoError(responsePayload.Payload.Validate(strfmt.Default)) + var i int + for index, office := range responsePayload.Payload { + if *office.Name == "New PPPO Travis AFB - USAF" { + i = index + } + } + suite.NotNil(i) } diff --git a/pkg/handlers/primeapi/api.go b/pkg/handlers/primeapi/api.go index 2d8814925bc..4eab1923c9f 100644 --- a/pkg/handlers/primeapi/api.go +++ b/pkg/handlers/primeapi/api.go @@ -53,7 +53,7 @@ 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()) userUploader, err := uploader.NewUserUploader(handlerConfig.FileStorer(), uploader.MaxCustomerUserUploadFileSizeLimit) if err != nil { diff --git a/pkg/handlers/primeapi/move_task_order_test.go b/pkg/handlers/primeapi/move_task_order_test.go index f4ed43d4760..cfbc270140d 100644 --- a/pkg/handlers/primeapi/move_task_order_test.go +++ b/pkg/handlers/primeapi/move_task_order_test.go @@ -1736,7 +1736,6 @@ func (suite *HandlerSuite) TestUpdateMTOPostCounselingInfo() { mock.Anything, mock.Anything, false, - false, ).Return(400, nil) setUpSignedCertificationCreatorMock := func(returnValue ...interface{}) services.SignedCertificationCreator { @@ -1822,7 +1821,6 @@ func (suite *HandlerSuite) TestUpdateMTOPostCounselingInfo() { mock.Anything, mock.Anything, false, - false, ).Return(400, nil) setUpSignedCertificationCreatorMock := func(returnValue ...interface{}) services.SignedCertificationCreator { diff --git a/pkg/handlers/primeapi/mto_service_item.go b/pkg/handlers/primeapi/mto_service_item.go index 7dad26d373b..646c6d17bfb 100644 --- a/pkg/handlers/primeapi/mto_service_item.go +++ b/pkg/handlers/primeapi/mto_service_item.go @@ -28,6 +28,7 @@ var CreateableServiceItemMap = map[primemessages.MTOServiceItemModelType]bool{ primemessages.MTOServiceItemModelTypeMTOServiceItemOriginSIT: true, primemessages.MTOServiceItemModelTypeMTOServiceItemDestSIT: true, primemessages.MTOServiceItemModelTypeMTOServiceItemShuttle: true, + primemessages.MTOServiceItemModelTypeMTOServiceItemInternationalShuttle: true, primemessages.MTOServiceItemModelTypeMTOServiceItemDomesticCrating: true, primemessages.MTOServiceItemModelTypeMTOServiceItemInternationalCrating: true, } diff --git a/pkg/handlers/primeapi/mto_service_item_test.go b/pkg/handlers/primeapi/mto_service_item_test.go index e408e4085b8..319b3223705 100644 --- a/pkg/handlers/primeapi/mto_service_item_test.go +++ b/pkg/handlers/primeapi/mto_service_item_test.go @@ -116,7 +116,6 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemHandler() { 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{ @@ -176,7 +175,54 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemHandler() { 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(params.Body.Validate(strfmt.Default)) + + response := handler.Handle(params) + suite.IsType(&mtoserviceitemops.CreateMTOServiceItemOK{}, response) + okResponse := response.(*mtoserviceitemops.CreateMTOServiceItemOK) + + suite.NotZero(okResponse.Payload[0].ID()) + }) + + suite.Run("Successful POST for Creating International Shuttling without PrimeEstimatedWeight set - Integration Test", func() { + mto := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) + mtoShipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: mto, + LinkOnly: true, + }, + }, nil) + mtoShipment.PrimeEstimatedWeight = nil + req := httptest.NewRequest("POST", "/mto-service-items", nil) + reason := "lorem ipsum" + + mtoServiceItem := models.MTOServiceItem{ + MoveTaskOrderID: mto.ID, + MTOShipmentID: &mtoShipment.ID, + ReService: models.ReService{Code: models.ReServiceCodeIOSHUT}, + Reason: &reason, + } + + params := mtoserviceitemops.CreateMTOServiceItemParams{ + HTTPRequest: req, + Body: payloads.MTOServiceItem(&mtoServiceItem), + } + + moveRouter := moverouter.NewMoveRouter() + planner := &routemocks.Planner{} + planner.On("ZipTransitDistance", + mock.AnythingOfType("*appcontext.appContext"), + mock.Anything, + mock.Anything, ).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{ @@ -316,7 +362,6 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemHandler() { 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{ @@ -362,7 +407,6 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemHandler() { 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{ @@ -577,7 +621,6 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemDomesticCratingHandler() { 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{ @@ -616,7 +659,6 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemDomesticCratingHandler() { 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{ @@ -741,7 +783,6 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemOriginSITHandler() { 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{ @@ -789,7 +830,6 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemOriginSITHandler() { 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{ @@ -860,7 +900,6 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemOriginSITHandler() { 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{ @@ -946,7 +985,6 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemOriginSITHandlerWithDOFSITNoA 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{ @@ -1058,7 +1096,6 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemOriginSITHandlerWithDOFSITWit 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{ @@ -1233,7 +1270,6 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemDestSITHandler() { 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{ @@ -1309,7 +1345,6 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemDestSITHandler() { 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{ @@ -1344,7 +1379,6 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemDestSITHandler() { 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{ @@ -1402,7 +1436,6 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemDestSITHandler() { 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{ @@ -1458,7 +1491,6 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemDestSITHandler() { 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{ @@ -1658,11 +1690,10 @@ func (suite *HandlerSuite) TestUpdateMTOServiceItemDDDSIT() { mock.Anything, mock.Anything, false, - false, ).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 @@ -1943,11 +1974,10 @@ func (suite *HandlerSuite) TestUpdateMTOServiceItemDOPSIT() { mock.Anything, mock.Anything, false, - false, ).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_address_test.go b/pkg/handlers/primeapi/mto_shipment_address_test.go index cb662b28dfe..71235074946 100644 --- a/pkg/handlers/primeapi/mto_shipment_address_test.go +++ b/pkg/handlers/primeapi/mto_shipment_address_test.go @@ -48,7 +48,6 @@ func (suite *HandlerSuite) TestUpdateMTOShipmentAddressHandler() { mock.Anything, mock.Anything, false, - false, ).Return(400, nil) // Create handler handler := UpdateMTOShipmentAddressHandler{ diff --git a/pkg/handlers/primeapi/mto_shipment_test.go b/pkg/handlers/primeapi/mto_shipment_test.go index 7e5539b18fd..1a9b23790ac 100644 --- a/pkg/handlers/primeapi/mto_shipment_test.go +++ b/pkg/handlers/primeapi/mto_shipment_test.go @@ -207,7 +207,6 @@ func (suite *HandlerSuite) TestUpdateMTOShipmentStatusHandler() { mock.Anything, mock.Anything, false, - false, ).Return(400, nil) moveRouter := moveservices.NewMoveRouter() addressUpdater := address.NewAddressUpdater() @@ -229,7 +228,6 @@ func (suite *HandlerSuite) TestUpdateMTOShipmentStatusHandler() { mock.Anything, mock.Anything, false, - false, ).Return(400, nil) handler := UpdateMTOShipmentStatusHandler{ handlerConfig, @@ -422,7 +420,6 @@ func (suite *HandlerSuite) TestDeleteMTOShipmentHandler() { mock.Anything, mock.Anything, false, - false, ).Return(400, nil) setUpSignedCertificationCreatorMock := func(returnValue ...interface{}) services.SignedCertificationCreator { diff --git a/pkg/handlers/primeapi/payloads/model_to_payload.go b/pkg/handlers/primeapi/payloads/model_to_payload.go index 5388576c706..3c8a3f1b292 100644 --- a/pkg/handlers/primeapi/payloads/model_to_payload.go +++ b/pkg/handlers/primeapi/payloads/model_to_payload.go @@ -35,9 +35,11 @@ func MoveTaskOrder(appCtx appcontext.AppContext, moveTaskOrder *models.Move) *pr if err != nil { destGbloc = "" } - destZip, err = moveTaskOrder.GetDestinationPostalCode(db) + destinationAddress, err := moveTaskOrder.GetDestinationAddress(appCtx.DB()) if err != nil { destZip = "" + } else { + destZip = destinationAddress.PostalCode } payload := &primemessages.MoveTaskOrder{ @@ -92,9 +94,11 @@ func ListMove(move *models.Move, appCtx appcontext.AppContext, moveOrderAmendmen if err != nil { destGbloc = "" } - destZip, err = move.GetDestinationPostalCode(db) + destinationAddress, err := move.GetDestinationAddress(appCtx.DB()) if err != nil { destZip = "" + } else { + destZip = destinationAddress.PostalCode } payload := &primemessages.ListMove{ @@ -258,6 +262,10 @@ func Entitlement(entitlement *models.Entitlement) *primemessages.Entitlements { if entitlement.UBAllowance != nil { ubAllowance = int64(*entitlement.UBAllowance) } + var weightRestriction int64 + if entitlement.WeightRestriction != nil { + weightRestriction = int64(*entitlement.WeightRestriction) + } return &primemessages.Entitlements{ ID: strfmt.UUID(entitlement.ID.String()), AuthorizedWeight: authorizedWeight, @@ -270,10 +278,11 @@ func Entitlement(entitlement *models.Entitlement) *primemessages.Entitlements { ProGearWeightSpouse: int64(entitlement.ProGearWeightSpouse), RequiredMedicalEquipmentWeight: int64(entitlement.RequiredMedicalEquipmentWeight), OrganizationalClothingAndIndividualEquipment: entitlement.OrganizationalClothingAndIndividualEquipment, - StorageInTransit: sit, - TotalDependents: totalDependents, - TotalWeight: totalWeight, - ETag: etag.GenerateEtag(entitlement.UpdatedAt), + StorageInTransit: sit, + TotalDependents: totalDependents, + TotalWeight: totalWeight, + WeightRestriction: &weightRestriction, + ETag: etag.GenerateEtag(entitlement.UpdatedAt), } } @@ -832,6 +841,32 @@ func MTOServiceItem(mtoServiceItem *models.MTOServiceItem) primemessages.MTOServ PortCode: portCode, } + case models.ReServiceCodeIDSHUT, models.ReServiceCodeIOSHUT: + shuttleSI := &primemessages.MTOServiceItemInternationalShuttle{ + ReServiceCode: handlers.FmtString(string(mtoServiceItem.ReService.Code)), + Reason: mtoServiceItem.Reason, + RequestApprovalsRequestedStatus: mtoServiceItem.RequestedApprovalsRequestedStatus, + EstimatedWeight: handlers.FmtPoundPtr(mtoServiceItem.EstimatedWeight), + ActualWeight: handlers.FmtPoundPtr(mtoServiceItem.ActualWeight), + } + + if mtoServiceItem.ReService.Code == models.ReServiceCodeIOSHUT && mtoServiceItem.MTOShipment.PickupAddress != nil { + if *mtoServiceItem.MTOShipment.PickupAddress.IsOconus { + shuttleSI.Market = models.MarketOconus.FullString() + } else { + shuttleSI.Market = models.MarketConus.FullString() + } + } + + if mtoServiceItem.ReService.Code == models.ReServiceCodeIDSHUT && mtoServiceItem.MTOShipment.DestinationAddress != nil { + if *mtoServiceItem.MTOShipment.DestinationAddress.IsOconus { + shuttleSI.Market = models.MarketOconus.FullString() + } else { + shuttleSI.Market = models.MarketConus.FullString() + } + } + + payload = shuttleSI default: // otherwise, basic service item payload = &primemessages.MTOServiceItemBasic{ diff --git a/pkg/handlers/primeapi/payloads/model_to_payload_test.go b/pkg/handlers/primeapi/payloads/model_to_payload_test.go index 58c8e1be196..f070c4342d2 100644 --- a/pkg/handlers/primeapi/payloads/model_to_payload_test.go +++ b/pkg/handlers/primeapi/payloads/model_to_payload_test.go @@ -351,6 +351,7 @@ func (suite *PayloadsSuite) TestEntitlement() { ProGearWeightSpouse: 750, CreatedAt: time.Now(), UpdatedAt: time.Now(), + WeightRestriction: models.IntPointer(1000), } // TotalWeight needs to read from the internal weightAllotment, in this case 7000 lbs w/o dependents and @@ -373,6 +374,7 @@ func (suite *PayloadsSuite) TestEntitlement() { suite.Equal(true, payload.OrganizationalClothingAndIndividualEquipment) suite.Equal(int64(1000), payload.ProGearWeight) suite.Equal(int64(750), payload.ProGearWeightSpouse) + suite.Equal(int64(1000), *payload.WeightRestriction) suite.NotEmpty(payload.ETag) suite.Equal(etag.GenerateEtag(entitlement.UpdatedAt), payload.ETag) }) @@ -963,6 +965,88 @@ func (suite *PayloadsSuite) TestMTOServiceItemDDSHUT() { suite.True(ok) } +func (suite *PayloadsSuite) TestMTOServiceItemIDSHUT() { + reServiceCode := models.ReServiceCodeIDSHUT + 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) + standaloneCrate := false + + mtoServiceItemIDSHUT := &models.MTOServiceItem{ + ID: uuid.Must(uuid.NewV4()), + ReService: models.ReService{Code: reServiceCode}, + Reason: &reason, + StandaloneCrate: &standaloneCrate, + 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, + }, + }, + } + + resultIDSHUT := MTOServiceItem(mtoServiceItemIDSHUT) + + suite.NotNil(resultIDSHUT) + + _, ok := resultIDSHUT.(*primemessages.MTOServiceItemInternationalShuttle) + + suite.True(ok) +} + +func (suite *PayloadsSuite) TestMTOServiceItemIOSHUT() { + reServiceCode := models.ReServiceCodeIOSHUT + 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) + standaloneCrate := false + + mtoServiceItemIOSHUT := &models.MTOServiceItem{ + ID: uuid.Must(uuid.NewV4()), + ReService: models.ReService{Code: reServiceCode}, + Reason: &reason, + StandaloneCrate: &standaloneCrate, + 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, + }, + }, + } + + resultIOSHUT := MTOServiceItem(mtoServiceItemIOSHUT) + + suite.NotNil(resultIOSHUT) + + _, ok := resultIOSHUT.(*primemessages.MTOServiceItemInternationalShuttle) + + suite.True(ok) +} + func (suite *PayloadsSuite) TestDestinationPostalCodeAndGBLOC() { moveID := uuid.Must(uuid.NewV4()) moveLocator := "TESTTEST" diff --git a/pkg/handlers/primeapi/payloads/payload_to_model.go b/pkg/handlers/primeapi/payloads/payload_to_model.go index 08a64b02b82..53362a88b84 100644 --- a/pkg/handlers/primeapi/payloads/payload_to_model.go +++ b/pkg/handlers/primeapi/payloads/payload_to_model.go @@ -537,6 +537,14 @@ func MTOServiceItemModel(mtoServiceItem primemessages.MTOServiceItem) (*models.M model.EstimatedWeight = handlers.PoundPtrFromInt64Ptr(shuttleService.EstimatedWeight) model.ActualWeight = handlers.PoundPtrFromInt64Ptr(shuttleService.ActualWeight) + case primemessages.MTOServiceItemModelTypeMTOServiceItemInternationalShuttle: + shuttleService := mtoServiceItem.(*primemessages.MTOServiceItemInternationalShuttle) + // 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.MTOServiceItemModelTypeMTOServiceItemDomesticCrating: domesticCrating := mtoServiceItem.(*primemessages.MTOServiceItemDomesticCrating) @@ -722,6 +730,20 @@ func MTOServiceItemModelFromUpdate(mtoServiceItemID string, mtoServiceItem prime model.EstimatedWeight = handlers.PoundPtrFromInt64Ptr(shuttle.EstimatedWeight) model.ActualWeight = handlers.PoundPtrFromInt64Ptr(shuttle.ActualWeight) + if verrs != nil && verrs.HasAny() { + return nil, verrs + } + case primemessages.UpdateMTOServiceItemModelTypeUpdateMTOServiceItemInternationalShuttle: + shuttle := mtoServiceItem.(*primemessages.UpdateMTOServiceItemInternationalShuttle) + model.EstimatedWeight = handlers.PoundPtrFromInt64Ptr(shuttle.EstimatedWeight) + model.ActualWeight = handlers.PoundPtrFromInt64Ptr(shuttle.ActualWeight) + + if shuttle.RequestApprovalsRequestedStatus != nil { + pointerValue := *shuttle.RequestApprovalsRequestedStatus + model.RequestedApprovalsRequestedStatus = &pointerValue + model.Status = models.MTOServiceItemStatusSubmitted + } + if verrs != nil && verrs.HasAny() { return nil, verrs } diff --git a/pkg/handlers/primeapi/payloads/payload_to_model_test.go b/pkg/handlers/primeapi/payloads/payload_to_model_test.go index 4ceff5aeb1f..9b5ec6f69a5 100644 --- a/pkg/handlers/primeapi/payloads/payload_to_model_test.go +++ b/pkg/handlers/primeapi/payloads/payload_to_model_test.go @@ -29,7 +29,13 @@ func (suite *PayloadsSuite) TestMTOServiceItemModel() { // DCRT Service Item itemMeasurement := int32(1100) crateMeasurement := int32(1200) + estimatedWeight := int64(1000) + actualWeight := int64(1000) dcrtCode := models.ReServiceCodeDCRT.String() + ddshutCode := models.ReServiceCodeDDSHUT.String() + doshutCode := models.ReServiceCodeDOSHUT.String() + idshutCode := models.ReServiceCodeIDSHUT.String() + ioshutCode := models.ReServiceCodeIOSHUT.String() reason := "Reason" description := "Description" standaloneCrate := false @@ -52,12 +58,49 @@ func (suite *PayloadsSuite) TestMTOServiceItemModel() { Description: &description, StandaloneCrate: &standaloneCrate, } + DCRTServiceItem.Item.MTOServiceItemDimension = *item DCRTServiceItem.Crate.MTOServiceItemDimension = *crate DCRTServiceItem.SetMoveTaskOrderID(handlers.FmtUUID(moveTaskOrderIDField)) DCRTServiceItem.SetMtoShipmentID(*mtoShipmentIDString) + DDSHUTServiceItem := &primemessages.MTOServiceItemShuttle{ + ReServiceCode: &ddshutCode, + Reason: &reason, + EstimatedWeight: &estimatedWeight, + ActualWeight: &actualWeight, + } + DDSHUTServiceItem.SetMoveTaskOrderID(handlers.FmtUUID(moveTaskOrderIDField)) + DDSHUTServiceItem.SetMtoShipmentID(*mtoShipmentIDString) + + DOSHUTServiceItem := &primemessages.MTOServiceItemShuttle{ + ReServiceCode: &doshutCode, + Reason: &reason, + EstimatedWeight: &estimatedWeight, + ActualWeight: &actualWeight, + } + DOSHUTServiceItem.SetMoveTaskOrderID(handlers.FmtUUID(moveTaskOrderIDField)) + DOSHUTServiceItem.SetMtoShipmentID(*mtoShipmentIDString) + + IDSHUTServiceItem := &primemessages.MTOServiceItemInternationalShuttle{ + ReServiceCode: &idshutCode, + Reason: &reason, + EstimatedWeight: &estimatedWeight, + ActualWeight: &actualWeight, + } + IDSHUTServiceItem.SetMoveTaskOrderID(handlers.FmtUUID(moveTaskOrderIDField)) + IDSHUTServiceItem.SetMtoShipmentID(*mtoShipmentIDString) + + IOSHUTServiceItem := &primemessages.MTOServiceItemInternationalShuttle{ + ReServiceCode: &ioshutCode, + Reason: &reason, + EstimatedWeight: &estimatedWeight, + ActualWeight: &actualWeight, + } + IOSHUTServiceItem.SetMoveTaskOrderID(handlers.FmtUUID(moveTaskOrderIDField)) + IOSHUTServiceItem.SetMtoShipmentID(*mtoShipmentIDString) + originReason := "storage at origin" originServiceCode := models.ReServiceCodeDOFSIT.String() originSITEntryDate := strfmt.Date(time.Now()) @@ -240,6 +283,46 @@ func (suite *PayloadsSuite) TestMTOServiceItemModel() { suite.Equal(unit.ThousandthInches(*ICRTServiceItem.Crate.Length), icurtReturnedCrate.Length) }) + suite.Run("Success - Returns a DDSHUT service item model", func() { + returnedModel, verrs := MTOServiceItemModel(DDSHUTServiceItem) + + suite.NoVerrs(verrs) + suite.Equal(moveTaskOrderIDField.String(), returnedModel.MoveTaskOrderID.String()) + suite.Equal(mtoShipmentIDField.String(), returnedModel.MTOShipmentID.String()) + suite.Equal(models.ReServiceCodeDDSHUT, returnedModel.ReService.Code) + suite.Equal(DDSHUTServiceItem.Reason, returnedModel.Reason) + }) + + suite.Run("Success - Returns a DOSHUT service item model", func() { + returnedModel, verrs := MTOServiceItemModel(DOSHUTServiceItem) + + suite.NoVerrs(verrs) + suite.Equal(moveTaskOrderIDField.String(), returnedModel.MoveTaskOrderID.String()) + suite.Equal(mtoShipmentIDField.String(), returnedModel.MTOShipmentID.String()) + suite.Equal(models.ReServiceCodeDOSHUT, returnedModel.ReService.Code) + suite.Equal(DOSHUTServiceItem.Reason, returnedModel.Reason) + }) + + suite.Run("Success - Returns a IOSHUT service item model", func() { + returnedModel, verrs := MTOServiceItemModel(IOSHUTServiceItem) + + suite.NoVerrs(verrs) + suite.Equal(moveTaskOrderIDField.String(), returnedModel.MoveTaskOrderID.String()) + suite.Equal(mtoShipmentIDField.String(), returnedModel.MTOShipmentID.String()) + suite.Equal(models.ReServiceCodeIOSHUT, returnedModel.ReService.Code) + suite.Equal(IOSHUTServiceItem.Reason, returnedModel.Reason) + }) + + suite.Run("Success - Returns a IDSHUT service item model", func() { + returnedModel, verrs := MTOServiceItemModel(IDSHUTServiceItem) + + suite.NoVerrs(verrs) + suite.Equal(moveTaskOrderIDField.String(), returnedModel.MoveTaskOrderID.String()) + suite.Equal(mtoShipmentIDField.String(), returnedModel.MTOShipmentID.String()) + suite.Equal(models.ReServiceCodeIDSHUT, returnedModel.ReService.Code) + suite.Equal(IDSHUTServiceItem.Reason, returnedModel.Reason) + }) + suite.Run("Fail - Returns error for ICRT/IUCRT service item because of validation error", func() { // ICRT icrtCode := models.ReServiceCodeICRT.String() diff --git a/pkg/handlers/primeapi/payment_request_test.go b/pkg/handlers/primeapi/payment_request_test.go index 114f03aa49b..1ca4f2965e7 100644 --- a/pkg/handlers/primeapi/payment_request_test.go +++ b/pkg/handlers/primeapi/payment_request_test.go @@ -747,7 +747,6 @@ func (suite *HandlerSuite) TestCreatePaymentRequestHandlerNewPaymentRequestCreat "90210", "94535", false, - false, ).Return(defaultZipDistance, nil) paymentRequestCreator := paymentrequest.NewPaymentRequestCreator( @@ -908,7 +907,6 @@ func (suite *HandlerSuite) TestCreatePaymentRequestHandlerInvalidMTOReferenceID( "90210", "94535", false, - false, ).Return(defaultZipDistance, nil) paymentRequestCreator := paymentrequest.NewPaymentRequestCreator( @@ -968,7 +966,6 @@ func (suite *HandlerSuite) TestCreatePaymentRequestHandlerInvalidMTOReferenceID( "90210", "94535", false, - false, ).Return(defaultZipDistance, nil) paymentRequestCreator := paymentrequest.NewPaymentRequestCreator( diff --git a/pkg/handlers/primeapiv2/api.go b/pkg/handlers/primeapiv2/api.go index 2d22387e986..44e8ca916ef 100644 --- a/pkg/handlers/primeapiv2/api.go +++ b/pkg/handlers/primeapiv2/api.go @@ -53,9 +53,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, ) @@ -97,10 +109,11 @@ 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(), } return primeAPIV2 diff --git a/pkg/handlers/primeapiv2/mto_service_item.go b/pkg/handlers/primeapiv2/mto_service_item.go index 54ea0540344..5188ccea511 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.MTOServiceItemModelTypeMTOServiceItemInternationalShuttle: true, primev2messages.MTOServiceItemModelTypeMTOServiceItemDomesticCrating: true, primev2messages.MTOServiceItemModelTypeMTOServiceItemInternationalCrating: true, } diff --git a/pkg/handlers/primeapiv2/mto_service_item_test.go b/pkg/handlers/primeapiv2/mto_service_item_test.go index 8875c35a851..a48c9bfe87d 100644 --- a/pkg/handlers/primeapiv2/mto_service_item_test.go +++ b/pkg/handlers/primeapiv2/mto_service_item_test.go @@ -110,7 +110,6 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemHandler() { 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{ @@ -170,7 +169,55 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemHandler() { 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(params.Body.Validate(strfmt.Default)) + + response := handler.Handle(params) + suite.IsType(&mtoserviceitemops.CreateMTOServiceItemOK{}, response) + okResponse := response.(*mtoserviceitemops.CreateMTOServiceItemOK) + + suite.NotZero(okResponse.Payload[0].ID()) + }) + + suite.Run("Successful POST for Creating International Shuttling without PrimeEstimatedWeight set - Integration Test", func() { + mto := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) + mtoShipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: mto, + LinkOnly: true, + }, + }, nil) + mtoShipment.PrimeEstimatedWeight = nil + factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeDOSHUT) + req := httptest.NewRequest("POST", "/mto-service-items", nil) + reason := "lorem ipsum" + + mtoServiceItem := models.MTOServiceItem{ + MoveTaskOrderID: mto.ID, + MTOShipmentID: &mtoShipment.ID, + ReService: models.ReService{Code: models.ReServiceCodeIOSHUT}, + Reason: &reason, + } + + params := mtoserviceitemops.CreateMTOServiceItemParams{ + HTTPRequest: req, + Body: payloads.MTOServiceItem(&mtoServiceItem), + } + + moveRouter := moverouter.NewMoveRouter() + planner := &routemocks.Planner{} + planner.On("ZipTransitDistance", + mock.AnythingOfType("*appcontext.appContext"), + mock.Anything, + mock.Anything, ).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{ @@ -310,7 +357,6 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemHandler() { 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{ @@ -356,7 +402,6 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemHandler() { 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{ @@ -571,7 +616,6 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemDomesticCratingHandler() { 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{ @@ -610,7 +654,6 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemDomesticCratingHandler() { 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{ @@ -735,7 +778,6 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemOriginSITHandler() { 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{ @@ -783,7 +825,6 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemOriginSITHandler() { 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{ @@ -854,7 +895,6 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemOriginSITHandler() { 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{ @@ -940,7 +980,6 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemOriginSITHandlerWithDOFSITNoA 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{ @@ -1050,7 +1089,6 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemOriginSITHandlerWithDOFSITWit 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{ @@ -1219,7 +1257,6 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemDestSITHandler() { 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{ @@ -1295,7 +1332,6 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemDestSITHandler() { 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{ @@ -1330,7 +1366,6 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemDestSITHandler() { 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{ @@ -1388,7 +1423,6 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemDestSITHandler() { 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{ @@ -1454,7 +1488,6 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemDestSITHandler() { 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{ diff --git a/pkg/handlers/primeapiv2/mto_shipment.go b/pkg/handlers/primeapiv2/mto_shipment.go index 35672bbc02e..d4a5e5012da 100644 --- a/pkg/handlers/primeapiv2/mto_shipment.go +++ b/pkg/handlers/primeapiv2/mto_shipment.go @@ -17,6 +17,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" ) @@ -169,6 +170,7 @@ func (h CreateMTOShipmentHandler) Handle(params mtoshipmentops.CreateMTOShipment type UpdateMTOShipmentHandler struct { handlers.HandlerConfig services.ShipmentUpdater + planner route.Planner } // Handle handler that updates a mto shipment diff --git a/pkg/handlers/primeapiv2/mto_shipment_test.go b/pkg/handlers/primeapiv2/mto_shipment_test.go index aaf6ba180d6..83134e3b522 100644 --- a/pkg/handlers/primeapiv2/mto_shipment_test.go +++ b/pkg/handlers/primeapiv2/mto_shipment_test.go @@ -53,7 +53,6 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { mock.Anything, mock.Anything, false, - false, ).Return(400, nil) setUpSignedCertificationCreatorMock := func(returnValue ...interface{}) services.SignedCertificationCreator { diff --git a/pkg/handlers/primeapiv2/payloads/model_to_payload.go b/pkg/handlers/primeapiv2/payloads/model_to_payload.go index 31bdb5cff17..106a3bd5cf3 100644 --- a/pkg/handlers/primeapiv2/payloads/model_to_payload.go +++ b/pkg/handlers/primeapiv2/payloads/model_to_payload.go @@ -32,9 +32,11 @@ func MoveTaskOrder(appCtx appcontext.AppContext, moveTaskOrder *models.Move) *pr if err != nil { destGbloc = "" } - destZip, err = moveTaskOrder.GetDestinationPostalCode(db) + destinationAddress, err := moveTaskOrder.GetDestinationAddress(appCtx.DB()) if err != nil { destZip = "" + } else { + destZip = destinationAddress.PostalCode } payload := &primev2messages.MoveTaskOrder{ @@ -189,6 +191,10 @@ func Entitlement(entitlement *models.Entitlement) *primev2messages.Entitlements if entitlement.UBAllowance != nil { ubAllowance = int64(*entitlement.UBAllowance) } + var weightRestriction int64 + if entitlement.WeightRestriction != nil { + weightRestriction = int64(*entitlement.WeightRestriction) + } return &primev2messages.Entitlements{ ID: strfmt.UUID(entitlement.ID.String()), AuthorizedWeight: authorizedWeight, @@ -201,10 +207,11 @@ func Entitlement(entitlement *models.Entitlement) *primev2messages.Entitlements ProGearWeightSpouse: int64(entitlement.ProGearWeightSpouse), RequiredMedicalEquipmentWeight: int64(entitlement.RequiredMedicalEquipmentWeight), OrganizationalClothingAndIndividualEquipment: entitlement.OrganizationalClothingAndIndividualEquipment, - StorageInTransit: sit, - TotalDependents: totalDependents, - TotalWeight: totalWeight, - ETag: etag.GenerateEtag(entitlement.UpdatedAt), + StorageInTransit: sit, + TotalDependents: totalDependents, + TotalWeight: totalWeight, + WeightRestriction: &weightRestriction, + ETag: etag.GenerateEtag(entitlement.UpdatedAt), } } @@ -724,6 +731,32 @@ func MTOServiceItem(mtoServiceItem *models.MTOServiceItem) primev2messages.MTOSe EstimatedWeight: handlers.FmtPoundPtr(mtoServiceItem.EstimatedWeight), ActualWeight: handlers.FmtPoundPtr(mtoServiceItem.ActualWeight), } + case models.ReServiceCodeIDSHUT, models.ReServiceCodeIOSHUT: + shuttleSI := &primev2messages.MTOServiceItemInternationalShuttle{ + ReServiceCode: handlers.FmtString(string(mtoServiceItem.ReService.Code)), + Reason: mtoServiceItem.Reason, + RequestApprovalsRequestedStatus: mtoServiceItem.RequestedApprovalsRequestedStatus, + EstimatedWeight: handlers.FmtPoundPtr(mtoServiceItem.EstimatedWeight), + ActualWeight: handlers.FmtPoundPtr(mtoServiceItem.ActualWeight), + } + + if mtoServiceItem.ReService.Code == models.ReServiceCodeIOSHUT && mtoServiceItem.MTOShipment.PickupAddress != nil { + if *mtoServiceItem.MTOShipment.PickupAddress.IsOconus { + shuttleSI.Market = models.MarketOconus.FullString() + } else { + shuttleSI.Market = models.MarketConus.FullString() + } + } + + if mtoServiceItem.ReService.Code == models.ReServiceCodeIDSHUT && mtoServiceItem.MTOShipment.DestinationAddress != nil { + if *mtoServiceItem.MTOShipment.DestinationAddress.IsOconus { + shuttleSI.Market = models.MarketOconus.FullString() + } else { + shuttleSI.Market = models.MarketConus.FullString() + } + } + + payload = shuttleSI default: // otherwise, basic service item payload = &primev2messages.MTOServiceItemBasic{ diff --git a/pkg/handlers/primeapiv2/payloads/model_to_payload_test.go b/pkg/handlers/primeapiv2/payloads/model_to_payload_test.go index addfb930e63..7b68e2a8e69 100644 --- a/pkg/handlers/primeapiv2/payloads/model_to_payload_test.go +++ b/pkg/handlers/primeapiv2/payloads/model_to_payload_test.go @@ -303,6 +303,7 @@ func (suite *PayloadsSuite) TestEntitlement() { ProGearWeightSpouse: 0, CreatedAt: time.Now(), UpdatedAt: time.Now(), + WeightRestriction: models.IntPointer(1000), } payload := Entitlement(&entitlement) @@ -325,6 +326,7 @@ func (suite *PayloadsSuite) TestEntitlement() { suite.Equal(int64(0), payload.TotalDependents) suite.Equal(int64(0), payload.TotalWeight) suite.Equal(int64(0), *payload.UnaccompaniedBaggageAllowance) + suite.Equal(int64(1000), *payload.WeightRestriction) }) suite.Run("Success - Returns the entitlement payload with all optional fields populated", func() { @@ -873,6 +875,88 @@ func (suite *PayloadsSuite) TestMTOServiceItemDDSHUT() { suite.True(ok) } +func (suite *PayloadsSuite) TestMTOServiceItemIDSHUT() { + reServiceCode := models.ReServiceCodeIDSHUT + 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) + standaloneCrate := false + + mtoServiceItemIDSHUT := &models.MTOServiceItem{ + ID: uuid.Must(uuid.NewV4()), + ReService: models.ReService{Code: reServiceCode}, + Reason: &reason, + StandaloneCrate: &standaloneCrate, + 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, + }, + }, + } + + resultIDSHUT := MTOServiceItem(mtoServiceItemIDSHUT) + + suite.NotNil(resultIDSHUT) + + _, ok := resultIDSHUT.(*primev2messages.MTOServiceItemInternationalShuttle) + + suite.True(ok) +} + +func (suite *PayloadsSuite) TestMTOServiceItemIOSHUT() { + reServiceCode := models.ReServiceCodeIOSHUT + 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) + standaloneCrate := false + + mtoServiceItemIOSHUT := &models.MTOServiceItem{ + ID: uuid.Must(uuid.NewV4()), + ReService: models.ReService{Code: reServiceCode}, + Reason: &reason, + StandaloneCrate: &standaloneCrate, + 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, + }, + }, + } + + resultIOSHUT := MTOServiceItem(mtoServiceItemIOSHUT) + + suite.NotNil(resultIOSHUT) + + _, ok := resultIOSHUT.(*primev2messages.MTOServiceItemInternationalShuttle) + + suite.True(ok) +} + func (suite *PayloadsSuite) TestStorageFacilityPayload() { phone := "555" email := "email" diff --git a/pkg/handlers/primeapiv2/payloads/payload_to_model.go b/pkg/handlers/primeapiv2/payloads/payload_to_model.go index 40c697b3672..b628bcc0502 100644 --- a/pkg/handlers/primeapiv2/payloads/payload_to_model.go +++ b/pkg/handlers/primeapiv2/payloads/payload_to_model.go @@ -630,6 +630,14 @@ func MTOServiceItemModel(mtoServiceItem primev2messages.MTOServiceItem) (*models model.EstimatedWeight = handlers.PoundPtrFromInt64Ptr(shuttleService.EstimatedWeight) model.ActualWeight = handlers.PoundPtrFromInt64Ptr(shuttleService.ActualWeight) + case primev2messages.MTOServiceItemModelTypeMTOServiceItemInternationalShuttle: + shuttleService := mtoServiceItem.(*primev2messages.MTOServiceItemInternationalShuttle) + // 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.MTOServiceItemModelTypeMTOServiceItemDomesticCrating: domesticCrating := mtoServiceItem.(*primev2messages.MTOServiceItemDomesticCrating) @@ -815,6 +823,20 @@ func MTOServiceItemModelFromUpdate(mtoServiceItemID string, mtoServiceItem prime model.EstimatedWeight = handlers.PoundPtrFromInt64Ptr(shuttle.EstimatedWeight) model.ActualWeight = handlers.PoundPtrFromInt64Ptr(shuttle.ActualWeight) + if verrs != nil && verrs.HasAny() { + return nil, verrs + } + case primev2messages.UpdateMTOServiceItemModelTypeUpdateMTOServiceItemInternationalShuttle: + shuttle := mtoServiceItem.(*primev2messages.UpdateMTOServiceItemInternationalShuttle) + model.EstimatedWeight = handlers.PoundPtrFromInt64Ptr(shuttle.EstimatedWeight) + model.ActualWeight = handlers.PoundPtrFromInt64Ptr(shuttle.ActualWeight) + + if shuttle.RequestApprovalsRequestedStatus != nil { + pointerValue := *shuttle.RequestApprovalsRequestedStatus + model.RequestedApprovalsRequestedStatus = &pointerValue + model.Status = models.MTOServiceItemStatusSubmitted + } + if verrs != nil && verrs.HasAny() { return nil, verrs } diff --git a/pkg/handlers/primeapiv2/payloads/payload_to_model_test.go b/pkg/handlers/primeapiv2/payloads/payload_to_model_test.go index a576e36173e..3df180b58ea 100644 --- a/pkg/handlers/primeapiv2/payloads/payload_to_model_test.go +++ b/pkg/handlers/primeapiv2/payloads/payload_to_model_test.go @@ -29,7 +29,13 @@ func (suite *PayloadsSuite) TestMTOServiceItemModel() { // DCRT Service Item itemMeasurement := int32(1100) crateMeasurement := int32(1200) + estimatedWeight := int64(1000) + actualWeight := int64(1000) dcrtCode := models.ReServiceCodeDCRT.String() + ddshutCode := models.ReServiceCodeDDSHUT.String() + doshutCode := models.ReServiceCodeDOSHUT.String() + idshutCode := models.ReServiceCodeIDSHUT.String() + ioshutCode := models.ReServiceCodeIOSHUT.String() reason := "Reason" description := "Description" standaloneCrate := false @@ -46,6 +52,42 @@ func (suite *PayloadsSuite) TestMTOServiceItemModel() { Length: &crateMeasurement, } + DDSHUTServiceItem := &primev2messages.MTOServiceItemShuttle{ + ReServiceCode: &ddshutCode, + Reason: &reason, + EstimatedWeight: &estimatedWeight, + ActualWeight: &actualWeight, + } + DDSHUTServiceItem.SetMoveTaskOrderID(handlers.FmtUUID(moveTaskOrderIDField)) + DDSHUTServiceItem.SetMtoShipmentID(*mtoShipmentIDString) + + DOSHUTServiceItem := &primev2messages.MTOServiceItemShuttle{ + ReServiceCode: &doshutCode, + Reason: &reason, + EstimatedWeight: &estimatedWeight, + ActualWeight: &actualWeight, + } + DOSHUTServiceItem.SetMoveTaskOrderID(handlers.FmtUUID(moveTaskOrderIDField)) + DOSHUTServiceItem.SetMtoShipmentID(*mtoShipmentIDString) + + IDSHUTServiceItem := &primev2messages.MTOServiceItemInternationalShuttle{ + ReServiceCode: &idshutCode, + Reason: &reason, + EstimatedWeight: &estimatedWeight, + ActualWeight: &actualWeight, + } + IDSHUTServiceItem.SetMoveTaskOrderID(handlers.FmtUUID(moveTaskOrderIDField)) + IDSHUTServiceItem.SetMtoShipmentID(*mtoShipmentIDString) + + IOSHUTServiceItem := &primev2messages.MTOServiceItemInternationalShuttle{ + ReServiceCode: &ioshutCode, + Reason: &reason, + EstimatedWeight: &estimatedWeight, + ActualWeight: &actualWeight, + } + IOSHUTServiceItem.SetMoveTaskOrderID(handlers.FmtUUID(moveTaskOrderIDField)) + IOSHUTServiceItem.SetMtoShipmentID(*mtoShipmentIDString) + DCRTServiceItem := &primev2messages.MTOServiceItemDomesticCrating{ ReServiceCode: &dcrtCode, Reason: &reason, @@ -219,6 +261,46 @@ func (suite *PayloadsSuite) TestMTOServiceItemModel() { suite.Equal(unit.ThousandthInches(*ICRTServiceItem.Crate.Length), icurtReturnedCrate.Length) }) + suite.Run("Success - Returns a DDSHUT service item model", func() { + returnedModel, verrs := MTOServiceItemModel(DDSHUTServiceItem) + + suite.NoVerrs(verrs) + suite.Equal(moveTaskOrderIDField.String(), returnedModel.MoveTaskOrderID.String()) + suite.Equal(mtoShipmentIDField.String(), returnedModel.MTOShipmentID.String()) + suite.Equal(models.ReServiceCodeDDSHUT, returnedModel.ReService.Code) + suite.Equal(DDSHUTServiceItem.Reason, returnedModel.Reason) + }) + + suite.Run("Success - Returns a DOSHUT service item model", func() { + returnedModel, verrs := MTOServiceItemModel(DOSHUTServiceItem) + + suite.NoVerrs(verrs) + suite.Equal(moveTaskOrderIDField.String(), returnedModel.MoveTaskOrderID.String()) + suite.Equal(mtoShipmentIDField.String(), returnedModel.MTOShipmentID.String()) + suite.Equal(models.ReServiceCodeDOSHUT, returnedModel.ReService.Code) + suite.Equal(DOSHUTServiceItem.Reason, returnedModel.Reason) + }) + + suite.Run("Success - Returns a IOSHUT service item model", func() { + returnedModel, verrs := MTOServiceItemModel(IOSHUTServiceItem) + + suite.NoVerrs(verrs) + suite.Equal(moveTaskOrderIDField.String(), returnedModel.MoveTaskOrderID.String()) + suite.Equal(mtoShipmentIDField.String(), returnedModel.MTOShipmentID.String()) + suite.Equal(models.ReServiceCodeIOSHUT, returnedModel.ReService.Code) + suite.Equal(IOSHUTServiceItem.Reason, returnedModel.Reason) + }) + + suite.Run("Success - Returns a IDSHUT service item model", func() { + returnedModel, verrs := MTOServiceItemModel(IDSHUTServiceItem) + + suite.NoVerrs(verrs) + suite.Equal(moveTaskOrderIDField.String(), returnedModel.MoveTaskOrderID.String()) + suite.Equal(mtoShipmentIDField.String(), returnedModel.MTOShipmentID.String()) + suite.Equal(models.ReServiceCodeIDSHUT, returnedModel.ReService.Code) + suite.Equal(IDSHUTServiceItem.Reason, returnedModel.Reason) + }) + suite.Run("Fail - Returns error for ICRT/IUCRT service item because of validation error", func() { // ICRT icrtCode := models.ReServiceCodeICRT.String() diff --git a/pkg/handlers/primeapiv3/api.go b/pkg/handlers/primeapiv3/api.go index 1662d151464..e46f49c8ad1 100644 --- a/pkg/handlers/primeapiv3/api.go +++ b/pkg/handlers/primeapiv3/api.go @@ -95,13 +95,26 @@ 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(), } return primeAPIV3 diff --git a/pkg/handlers/primeapiv3/mto_service_item.go b/pkg/handlers/primeapiv3/mto_service_item.go index ef7528e4ddf..f3c16b46e60 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.MTOServiceItemModelTypeMTOServiceItemInternationalShuttle: true, primev3messages.MTOServiceItemModelTypeMTOServiceItemDomesticCrating: true, primev3messages.MTOServiceItemModelTypeMTOServiceItemInternationalCrating: true, } diff --git a/pkg/handlers/primeapiv3/mto_service_item_test.go b/pkg/handlers/primeapiv3/mto_service_item_test.go index e5f36265a03..b0b02a71aca 100644 --- a/pkg/handlers/primeapiv3/mto_service_item_test.go +++ b/pkg/handlers/primeapiv3/mto_service_item_test.go @@ -109,7 +109,6 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemHandler() { mock.Anything, mock.Anything, false, - false, ).Return(400, nil) subtestData := makeSubtestData() moveRouter := moverouter.NewMoveRouter() @@ -171,7 +170,55 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemHandler() { 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(params.Body.Validate(strfmt.Default)) + + response := handler.Handle(params) + suite.IsType(&mtoserviceitemops.CreateMTOServiceItemOK{}, response) + okResponse := response.(*mtoserviceitemops.CreateMTOServiceItemOK) + + suite.NotZero(okResponse.Payload[0].ID()) + }) + + suite.Run("Successful POST for Creating International Shuttling without PrimeEstimatedWeight set - Integration Test", func() { + mto := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) + mtoShipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: mto, + LinkOnly: true, + }, + }, nil) + mtoShipment.PrimeEstimatedWeight = nil + factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeDOSHUT) + req := httptest.NewRequest("POST", "/mto-service-items", nil) + reason := "lorem ipsum" + + mtoServiceItem := models.MTOServiceItem{ + MoveTaskOrderID: mto.ID, + MTOShipmentID: &mtoShipment.ID, + ReService: models.ReService{Code: models.ReServiceCodeIOSHUT}, + Reason: &reason, + } + + params := mtoserviceitemops.CreateMTOServiceItemParams{ + HTTPRequest: req, + Body: payloads.MTOServiceItem(&mtoServiceItem), + } + + moveRouter := moverouter.NewMoveRouter() + planner := &routemocks.Planner{} + planner.On("ZipTransitDistance", + mock.AnythingOfType("*appcontext.appContext"), + mock.Anything, + mock.Anything, ).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{ @@ -311,7 +358,6 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemHandler() { 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{ @@ -357,7 +403,6 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemHandler() { 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{ @@ -572,7 +617,6 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemDomesticCratingHandler() { 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{ @@ -611,7 +655,6 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemDomesticCratingHandler() { 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{ @@ -736,7 +779,6 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemOriginSITHandler() { 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{ @@ -784,7 +826,6 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemOriginSITHandler() { 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{ @@ -855,7 +896,6 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemOriginSITHandler() { 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{ @@ -941,7 +981,6 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemOriginSITHandlerWithDOFSITNoA 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{ @@ -1051,7 +1090,6 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemOriginSITHandlerWithDOFSITWit 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{ @@ -1220,7 +1258,6 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemDestSITHandler() { 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{ @@ -1296,7 +1333,6 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemDestSITHandler() { 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{ @@ -1331,7 +1367,6 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemDestSITHandler() { 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{ @@ -1389,7 +1424,6 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemDestSITHandler() { 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{ @@ -1455,7 +1489,6 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemDestSITHandler() { 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{ @@ -1493,7 +1526,6 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemDestSITHandler() { 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{ diff --git a/pkg/handlers/primeapiv3/mto_shipment.go b/pkg/handlers/primeapiv3/mto_shipment.go index cddeeaab45b..20f3b4733e8 100644 --- a/pkg/handlers/primeapiv3/mto_shipment.go +++ b/pkg/handlers/primeapiv3/mto_shipment.go @@ -17,6 +17,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" ) @@ -169,6 +170,7 @@ func (h CreateMTOShipmentHandler) Handle(params mtoshipmentops.CreateMTOShipment type UpdateMTOShipmentHandler struct { handlers.HandlerConfig services.ShipmentUpdater + planner route.Planner } // Handle handler that updates a mto shipment @@ -223,7 +225,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 d2309f4a894..7e34a941234 100644 --- a/pkg/handlers/primeapiv3/mto_shipment_test.go +++ b/pkg/handlers/primeapiv3/mto_shipment_test.go @@ -60,7 +60,6 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { mock.Anything, mock.Anything, false, - false, ).Return(400, nil) setUpSignedCertificationCreatorMock := func(returnValue ...interface{}) services.SignedCertificationCreator { @@ -87,10 +86,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) @@ -113,7 +112,7 @@ 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) { @@ -543,6 +542,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"), @@ -552,6 +557,7 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { patchHandler := UpdateMTOShipmentHandler{ suite.HandlerConfig(), shipmentUpdater, + planner, } patchReq := httptest.NewRequest("PATCH", fmt.Sprintf("/mto-shipments/%s", createdPPM.ShipmentID.String()), nil) @@ -833,6 +839,7 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { patchHandler := UpdateMTOShipmentHandler{ suite.HandlerConfig(), shipmentUpdater, + planner, } patchReq := httptest.NewRequest("PATCH", fmt.Sprintf("/mto-shipments/%s", createdPPM.ShipmentID.String()), nil) @@ -1548,10 +1555,11 @@ 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, } now := time.Now() @@ -1623,10 +1631,11 @@ 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, } move := factory.BuildAvailableToPrimeMove(suite.DB(), []factory.Customization{}, nil) @@ -1671,10 +1680,11 @@ 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, } now := time.Now() diff --git a/pkg/handlers/primeapiv3/payloads/model_to_payload.go b/pkg/handlers/primeapiv3/payloads/model_to_payload.go index 37b1cbe6211..5ef6731190a 100644 --- a/pkg/handlers/primeapiv3/payloads/model_to_payload.go +++ b/pkg/handlers/primeapiv3/payloads/model_to_payload.go @@ -35,9 +35,11 @@ func MoveTaskOrder(appCtx appcontext.AppContext, moveTaskOrder *models.Move) *pr if err != nil { destGbloc = "" } - destZip, err = moveTaskOrder.GetDestinationPostalCode(db) + destinationAddress, err := moveTaskOrder.GetDestinationAddress(appCtx.DB()) if err != nil { destZip = "" + } else { + destZip = destinationAddress.PostalCode } payload := &primev3messages.MoveTaskOrder{ @@ -217,6 +219,10 @@ func Entitlement(entitlement *models.Entitlement) *primev3messages.Entitlements if entitlement.UBAllowance != nil { ubAllowance = int64(*entitlement.UBAllowance) } + var weightRestriction int64 + if entitlement.WeightRestriction != nil { + weightRestriction = int64(*entitlement.WeightRestriction) + } return &primev3messages.Entitlements{ ID: strfmt.UUID(entitlement.ID.String()), AuthorizedWeight: authorizedWeight, @@ -228,10 +234,11 @@ func Entitlement(entitlement *models.Entitlement) *primev3messages.Entitlements ProGearWeightSpouse: int64(entitlement.ProGearWeightSpouse), RequiredMedicalEquipmentWeight: int64(entitlement.RequiredMedicalEquipmentWeight), OrganizationalClothingAndIndividualEquipment: entitlement.OrganizationalClothingAndIndividualEquipment, - StorageInTransit: sit, - TotalDependents: totalDependents, - TotalWeight: totalWeight, - ETag: etag.GenerateEtag(entitlement.UpdatedAt), + StorageInTransit: sit, + TotalDependents: totalDependents, + TotalWeight: totalWeight, + WeightRestriction: &weightRestriction, + ETag: etag.GenerateEtag(entitlement.UpdatedAt), } } @@ -352,14 +359,12 @@ func MTOAgents(mtoAgents *models.MTOAgents) *primev3messages.MTOAgents { if mtoAgents == nil { return nil } - agents := make(primev3messages.MTOAgents, len(*mtoAgents)) for i, m := range *mtoAgents { copyOfM := m // Make copy to avoid implicit memory aliasing of items from a range statement. agents[i] = MTOAgent(©OfM) } - return &agents } @@ -882,6 +887,32 @@ func MTOServiceItem(mtoServiceItem *models.MTOServiceItem) primev3messages.MTOSe PortCode: portCode, } + case models.ReServiceCodeIDSHUT, models.ReServiceCodeIOSHUT: + shuttleSI := &primev3messages.MTOServiceItemInternationalShuttle{ + ReServiceCode: handlers.FmtString(string(mtoServiceItem.ReService.Code)), + Reason: mtoServiceItem.Reason, + RequestApprovalsRequestedStatus: mtoServiceItem.RequestedApprovalsRequestedStatus, + EstimatedWeight: handlers.FmtPoundPtr(mtoServiceItem.EstimatedWeight), + ActualWeight: handlers.FmtPoundPtr(mtoServiceItem.ActualWeight), + } + + if mtoServiceItem.ReService.Code == models.ReServiceCodeIOSHUT && mtoServiceItem.MTOShipment.PickupAddress != nil { + if *mtoServiceItem.MTOShipment.PickupAddress.IsOconus { + shuttleSI.Market = models.MarketOconus.FullString() + } else { + shuttleSI.Market = models.MarketConus.FullString() + } + } + + if mtoServiceItem.ReService.Code == models.ReServiceCodeIDSHUT && mtoServiceItem.MTOShipment.DestinationAddress != nil { + if *mtoServiceItem.MTOShipment.DestinationAddress.IsOconus { + shuttleSI.Market = models.MarketOconus.FullString() + } else { + shuttleSI.Market = models.MarketConus.FullString() + } + } + + payload = shuttleSI 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 0af6dddf6b1..475008105bb 100644 --- a/pkg/handlers/primeapiv3/payloads/model_to_payload_test.go +++ b/pkg/handlers/primeapiv3/payloads/model_to_payload_test.go @@ -545,6 +545,7 @@ func (suite *PayloadsSuite) TestEntitlement() { ProGearWeightSpouse: 0, CreatedAt: time.Now(), UpdatedAt: time.Now(), + WeightRestriction: models.IntPointer(1000), } payload := Entitlement(&entitlement) @@ -567,6 +568,7 @@ func (suite *PayloadsSuite) TestEntitlement() { suite.Equal(int64(0), payload.TotalDependents) suite.Equal(int64(0), payload.TotalWeight) suite.Equal(int64(0), *payload.UnaccompaniedBaggageAllowance) + suite.Equal(int64(1000), *payload.WeightRestriction) }) suite.Run("Success - Returns the entitlement payload with all optional fields populated", func() { @@ -1150,6 +1152,88 @@ func (suite *PayloadsSuite) TestMTOServiceItemDDSHUT() { suite.True(ok) } +func (suite *PayloadsSuite) TestMTOServiceItemIDSHUT() { + reServiceCode := models.ReServiceCodeIDSHUT + 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) + standaloneCrate := false + + mtoServiceItemIDSHUT := &models.MTOServiceItem{ + ID: uuid.Must(uuid.NewV4()), + ReService: models.ReService{Code: reServiceCode}, + Reason: &reason, + StandaloneCrate: &standaloneCrate, + 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, + }, + }, + } + + resultIDSHUT := MTOServiceItem(mtoServiceItemIDSHUT) + + suite.NotNil(resultIDSHUT) + + _, ok := resultIDSHUT.(*primev3messages.MTOServiceItemInternationalShuttle) + + suite.True(ok) +} + +func (suite *PayloadsSuite) TestMTOServiceItemIOSHUT() { + reServiceCode := models.ReServiceCodeIOSHUT + 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) + standaloneCrate := false + + mtoServiceItemIOSHUT := &models.MTOServiceItem{ + ID: uuid.Must(uuid.NewV4()), + ReService: models.ReService{Code: reServiceCode}, + Reason: &reason, + StandaloneCrate: &standaloneCrate, + 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, + }, + }, + } + + resultIOSHUT := MTOServiceItem(mtoServiceItemIOSHUT) + + suite.NotNil(resultIOSHUT) + + _, ok := resultIOSHUT.(*primev3messages.MTOServiceItemInternationalShuttle) + + suite.True(ok) +} + func (suite *PayloadsSuite) TestStorageFacilityPayload() { phone := "555" email := "email" diff --git a/pkg/handlers/primeapiv3/payloads/payload_to_model.go b/pkg/handlers/primeapiv3/payloads/payload_to_model.go index 47634cb343a..f33d8b2ff34 100644 --- a/pkg/handlers/primeapiv3/payloads/payload_to_model.go +++ b/pkg/handlers/primeapiv3/payloads/payload_to_model.go @@ -795,6 +795,14 @@ func MTOServiceItemModel(mtoServiceItem primev3messages.MTOServiceItem) (*models model.EstimatedWeight = handlers.PoundPtrFromInt64Ptr(shuttleService.EstimatedWeight) model.ActualWeight = handlers.PoundPtrFromInt64Ptr(shuttleService.ActualWeight) + case primev3messages.MTOServiceItemModelTypeMTOServiceItemInternationalShuttle: + shuttleService := mtoServiceItem.(*primev3messages.MTOServiceItemInternationalShuttle) + // 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.MTOServiceItemModelTypeMTOServiceItemDomesticCrating: domesticCrating := mtoServiceItem.(*primev3messages.MTOServiceItemDomesticCrating) @@ -980,6 +988,20 @@ func MTOServiceItemModelFromUpdate(mtoServiceItemID string, mtoServiceItem prime model.EstimatedWeight = handlers.PoundPtrFromInt64Ptr(shuttle.EstimatedWeight) model.ActualWeight = handlers.PoundPtrFromInt64Ptr(shuttle.ActualWeight) + if verrs != nil && verrs.HasAny() { + return nil, verrs + } + case primev3messages.UpdateMTOServiceItemModelTypeUpdateMTOServiceItemInternationalShuttle: + shuttle := mtoServiceItem.(*primev3messages.UpdateMTOServiceItemInternationalShuttle) + model.EstimatedWeight = handlers.PoundPtrFromInt64Ptr(shuttle.EstimatedWeight) + model.ActualWeight = handlers.PoundPtrFromInt64Ptr(shuttle.ActualWeight) + + if shuttle.RequestApprovalsRequestedStatus != nil { + pointerValue := *shuttle.RequestApprovalsRequestedStatus + model.RequestedApprovalsRequestedStatus = &pointerValue + model.Status = models.MTOServiceItemStatusSubmitted + } + if verrs != nil && verrs.HasAny() { return nil, verrs } diff --git a/pkg/handlers/primeapiv3/payloads/payload_to_model_test.go b/pkg/handlers/primeapiv3/payloads/payload_to_model_test.go index be2e2356114..fd9430379f0 100644 --- a/pkg/handlers/primeapiv3/payloads/payload_to_model_test.go +++ b/pkg/handlers/primeapiv3/payloads/payload_to_model_test.go @@ -29,7 +29,13 @@ func (suite *PayloadsSuite) TestMTOServiceItemModel() { // DCRT Service Item itemMeasurement := int32(1100) crateMeasurement := int32(1200) + estimatedWeight := int64(1000) + actualWeight := int64(1000) dcrtCode := models.ReServiceCodeDCRT.String() + ddshutCode := models.ReServiceCodeDDSHUT.String() + doshutCode := models.ReServiceCodeDOSHUT.String() + idshutCode := models.ReServiceCodeIDSHUT.String() + ioshutCode := models.ReServiceCodeIOSHUT.String() reason := "Reason" description := "Description" standaloneCrate := false @@ -58,6 +64,42 @@ func (suite *PayloadsSuite) TestMTOServiceItemModel() { DCRTServiceItem.SetMoveTaskOrderID(handlers.FmtUUID(moveTaskOrderIDField)) DCRTServiceItem.SetMtoShipmentID(*mtoShipmentIDString) + DDSHUTServiceItem := &primev3messages.MTOServiceItemShuttle{ + ReServiceCode: &ddshutCode, + Reason: &reason, + EstimatedWeight: &estimatedWeight, + ActualWeight: &actualWeight, + } + DDSHUTServiceItem.SetMoveTaskOrderID(handlers.FmtUUID(moveTaskOrderIDField)) + DDSHUTServiceItem.SetMtoShipmentID(*mtoShipmentIDString) + + DOSHUTServiceItem := &primev3messages.MTOServiceItemShuttle{ + ReServiceCode: &doshutCode, + Reason: &reason, + EstimatedWeight: &estimatedWeight, + ActualWeight: &actualWeight, + } + DOSHUTServiceItem.SetMoveTaskOrderID(handlers.FmtUUID(moveTaskOrderIDField)) + DOSHUTServiceItem.SetMtoShipmentID(*mtoShipmentIDString) + + IDSHUTServiceItem := &primev3messages.MTOServiceItemInternationalShuttle{ + ReServiceCode: &idshutCode, + Reason: &reason, + EstimatedWeight: &estimatedWeight, + ActualWeight: &actualWeight, + } + IDSHUTServiceItem.SetMoveTaskOrderID(handlers.FmtUUID(moveTaskOrderIDField)) + IDSHUTServiceItem.SetMtoShipmentID(*mtoShipmentIDString) + + IOSHUTServiceItem := &primev3messages.MTOServiceItemInternationalShuttle{ + ReServiceCode: &ioshutCode, + Reason: &reason, + EstimatedWeight: &estimatedWeight, + ActualWeight: &actualWeight, + } + IOSHUTServiceItem.SetMoveTaskOrderID(handlers.FmtUUID(moveTaskOrderIDField)) + IOSHUTServiceItem.SetMtoShipmentID(*mtoShipmentIDString) + destReason := "service member will pick up from storage at destination" destServiceCode := models.ReServiceCodeDDFSIT.String() destDate := strfmt.Date(time.Now()) @@ -146,6 +188,46 @@ func (suite *PayloadsSuite) TestMTOServiceItemModel() { }) + suite.Run("Success - Returns a DDSHUT service item model", func() { + returnedModel, verrs := MTOServiceItemModel(DDSHUTServiceItem) + + suite.NoVerrs(verrs) + suite.Equal(moveTaskOrderIDField.String(), returnedModel.MoveTaskOrderID.String()) + suite.Equal(mtoShipmentIDField.String(), returnedModel.MTOShipmentID.String()) + suite.Equal(models.ReServiceCodeDDSHUT, returnedModel.ReService.Code) + suite.Equal(DDSHUTServiceItem.Reason, returnedModel.Reason) + }) + + suite.Run("Success - Returns a DOSHUT service item model", func() { + returnedModel, verrs := MTOServiceItemModel(DOSHUTServiceItem) + + suite.NoVerrs(verrs) + suite.Equal(moveTaskOrderIDField.String(), returnedModel.MoveTaskOrderID.String()) + suite.Equal(mtoShipmentIDField.String(), returnedModel.MTOShipmentID.String()) + suite.Equal(models.ReServiceCodeDOSHUT, returnedModel.ReService.Code) + suite.Equal(DOSHUTServiceItem.Reason, returnedModel.Reason) + }) + + suite.Run("Success - Returns a IOSHUT service item model", func() { + returnedModel, verrs := MTOServiceItemModel(IOSHUTServiceItem) + + suite.NoVerrs(verrs) + suite.Equal(moveTaskOrderIDField.String(), returnedModel.MoveTaskOrderID.String()) + suite.Equal(mtoShipmentIDField.String(), returnedModel.MTOShipmentID.String()) + suite.Equal(models.ReServiceCodeIOSHUT, returnedModel.ReService.Code) + suite.Equal(IOSHUTServiceItem.Reason, returnedModel.Reason) + }) + + suite.Run("Success - Returns a IDSHUT service item model", func() { + returnedModel, verrs := MTOServiceItemModel(IDSHUTServiceItem) + + suite.NoVerrs(verrs) + suite.Equal(moveTaskOrderIDField.String(), returnedModel.MoveTaskOrderID.String()) + suite.Equal(mtoShipmentIDField.String(), returnedModel.MTOShipmentID.String()) + suite.Equal(models.ReServiceCodeIDSHUT, returnedModel.ReService.Code) + suite.Equal(IDSHUTServiceItem.Reason, returnedModel.Reason) + }) + suite.Run("Success - Returns a ICRT/IUCRT service item model", func() { // ICRT icrtCode := models.ReServiceCodeICRT.String() 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/move_task_order_test.go b/pkg/handlers/supportapi/move_task_order_test.go index f5663ae8794..74a616f22d4 100644 --- a/pkg/handlers/supportapi/move_task_order_test.go +++ b/pkg/handlers/supportapi/move_task_order_test.go @@ -176,7 +176,6 @@ func (suite *HandlerSuite) TestMakeMoveAvailableHandlerIntegrationSuccess() { mock.Anything, mock.Anything, false, - false, ).Return(400, nil) setUpSignedCertificationCreatorMock := func(returnValue ...interface{}) services.SignedCertificationCreator { @@ -394,7 +393,6 @@ func (suite *HandlerSuite) TestCreateMoveTaskOrderRequestHandler() { mock.Anything, mock.Anything, false, - false, ).Return(400, nil) setUpSignedCertificationCreatorMock := func(returnValue ...interface{}) services.SignedCertificationCreator { diff --git a/pkg/handlers/supportapi/mto_service_item_test.go b/pkg/handlers/supportapi/mto_service_item_test.go index 4c0596523a4..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" @@ -86,10 +87,9 @@ func (suite *HandlerSuite) TestUpdateMTOServiceItemStatusHandlerApproveSuccess() mock.Anything, mock.Anything, false, - 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 @@ -145,10 +145,9 @@ func (suite *HandlerSuite) TestUpdateMTOServiceItemStatusHandlerRejectSuccess() mock.Anything, mock.Anything, false, - 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,10 +203,9 @@ func (suite *HandlerSuite) TestUpdateMTOServiceItemStatusHandlerRejectionFailedN mock.Anything, mock.Anything, false, - 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/handlers/supportapi/mto_shipment_test.go b/pkg/handlers/supportapi/mto_shipment_test.go index 9a3a0c07a85..1a12aff8ced 100644 --- a/pkg/handlers/supportapi/mto_shipment_test.go +++ b/pkg/handlers/supportapi/mto_shipment_test.go @@ -98,7 +98,6 @@ func (suite *HandlerSuite) TestUpdateMTOShipmentStatusHandler() { mock.Anything, mock.Anything, false, - false, ).Return(400, nil) siCreator := mtoserviceitem.NewMTOServiceItemCreator(planner, queryBuilder, moveRouter, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticPackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticShorthaulPricer(), ghcrateengine.NewDomesticOriginPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) planner.On("Zip5TransitDistanceLineHaul", 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 d89a163c9aa..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 @@ -211,3 +219,17 @@ func EvaluateIsOconus(address Address) bool { return false } } + +// Fetches the GBLOC for a specific Address (for now this will be used for OCONUS) +func FetchAddressGbloc(db *pop.Connection, address Address, serviceMember ServiceMember) (*string, error) { + var gbloc *string + + err := db.RawQuery("SELECT * FROM get_address_gbloc($1, $2)", address.ID, serviceMember.Affiliation.String()). + First(&gbloc) + + if err != nil { + return nil, err + } + + return gbloc, nil +} diff --git a/pkg/models/address_test.go b/pkg/models/address_test.go index f2fbb5bf45c..de71c145f73 100644 --- a/pkg/models/address_test.go +++ b/pkg/models/address_test.go @@ -1,8 +1,13 @@ package models_test import ( + "fmt" + + "github.com/gofrs/uuid" + "github.com/transcom/mymove/pkg/factory" m "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/testdatagen" ) func (suite *ModelSuite) TestBasicAddressInstantiation() { @@ -100,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{ @@ -190,3 +168,251 @@ 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) + suite.NotNil(usprc) + suite.FatalNoError(err) + + address := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: m.Address{ + IsOconus: m.BoolPointer(true), + UsPostRegionCityID: &usprc.ID, + }, + }, + }, nil) + originDutyLocation := factory.BuildDutyLocation(suite.DB(), []factory.Customization{ + { + Model: address, + LinkOnly: true, + Type: &factory.Addresses.DutyLocationAddress, + }, + }, nil) + + contract := testdatagen.FetchOrMakeReContract(suite.DB(), testdatagen.Assertions{}) + + rateAreaCode := uuid.Must(uuid.NewV4()).String()[0:5] + rateArea := testdatagen.FetchOrMakeReRateArea(suite.DB(), testdatagen.Assertions{ + ReRateArea: m.ReRateArea{ + ContractID: contract.ID, + IsOconus: true, + Name: fmt.Sprintf("Alaska-%s", rateAreaCode), + Contract: contract, + }, + }) + suite.NotNil(rateArea) + suite.Nil(err) + + us_country, err := m.FetchCountryByCode(suite.DB(), "US") + suite.NotNil(us_country) + suite.Nil(err) + + oconusRateArea, err := m.FetchOconusRateAreaByCityId(suite.DB(), usprc.ID.String()) + suite.NotNil(oconusRateArea) + suite.Nil(err) + + return *oconusRateArea, *usprc, originDutyLocation + } + + suite.Run("fetches duty location GBLOC for AK address, Zone II AirForce", func() { + oconusRateArea, _, originDutyLocation := setupDataForOconusDutyLocation("99707") + + airForce := m.AffiliationAIRFORCE + serviceMember := factory.BuildServiceMember(suite.DB(), []factory.Customization{ + { + Model: m.ServiceMember{ + Affiliation: &airForce, + }, + }, + }, nil) + + jppsoRegion, err := m.FetchJppsoRegionByCode(suite.DB(), "MBFL") + suite.NotNil(jppsoRegion) + suite.Nil(err) + + gblocAors, err := m.FetchGblocAorsByJppsoCodeRateAreaDept(suite.DB(), jppsoRegion.ID, oconusRateArea.ID, m.DepartmentIndicatorAIRANDSPACEFORCE.String()) + suite.NotNil(gblocAors) + suite.Nil(err) + + gbloc, err := m.FetchAddressGbloc(suite.DB(), originDutyLocation.Address, serviceMember) + suite.NoError(err) + suite.NotNil(gbloc) + suite.Equal(string(*gbloc), "MBFL") + }) + + suite.Run("fetches duty location GBLOC for AK address, Zone II Army", func() { + oconusRateArea, _, originDutyLocation := setupDataForOconusDutyLocation("99707") + + army := m.AffiliationARMY + serviceMember := factory.BuildServiceMember(suite.DB(), []factory.Customization{ + { + Model: m.ServiceMember{ + Affiliation: &army, + }, + }, + }, nil) + + jppsoRegion, err := m.FetchJppsoRegionByCode(suite.DB(), "JEAT") + suite.NotNil(jppsoRegion) + suite.Nil(err) + + gblocAors, err := m.FetchGblocAorsByJppsoCodeRateAreaDept(suite.DB(), jppsoRegion.ID, oconusRateArea.ID, m.DepartmentIndicatorARMY.String()) + suite.NotNil(gblocAors) + suite.Nil(err) + + gbloc, err := m.FetchAddressGbloc(suite.DB(), originDutyLocation.Address, serviceMember) + suite.NoError(err) + suite.NotNil(gbloc) + suite.Equal(string(*gbloc), "JEAT") + }) + + suite.Run("fetches duty location GBLOC for AK Cordova address, Zone IV", func() { + usprc, err := m.FindByZipCodeAndCity(suite.AppContextForTest().DB(), "99574", "CORDOVA") + suite.NotNil(usprc) + suite.FatalNoError(err) + + address := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: m.Address{ + IsOconus: m.BoolPointer(true), + UsPostRegionCityID: &usprc.ID, + }, + }, + }, nil) + originDutyLocation := factory.BuildDutyLocation(suite.DB(), []factory.Customization{ + { + Model: address, + LinkOnly: true, + Type: &factory.Addresses.DutyLocationAddress, + }, + }, nil) + + contract := testdatagen.FetchOrMakeReContract(suite.DB(), testdatagen.Assertions{}) + + rateAreaCode := uuid.Must(uuid.NewV4()).String()[0:5] + rateArea := testdatagen.FetchOrMakeReRateArea(suite.DB(), testdatagen.Assertions{ + ReRateArea: m.ReRateArea{ + ContractID: contract.ID, + IsOconus: true, + Name: fmt.Sprintf("Alaska-%s", rateAreaCode), + Contract: contract, + }, + }) + suite.NotNil(rateArea) + + us_country, err := m.FetchCountryByCode(suite.DB(), "US") + suite.NotNil(us_country) + suite.Nil(err) + + oconusRateArea, err := m.FetchOconusRateAreaByCityId(suite.DB(), usprc.ID.String()) + suite.NotNil(oconusRateArea) + suite.Nil(err) + army := m.AffiliationARMY + serviceMember := factory.BuildServiceMember(suite.DB(), []factory.Customization{ + { + Model: m.ServiceMember{ + Affiliation: &army, + }, + }, + }, nil) + + jppsoRegion, err := m.FetchJppsoRegionByCode(suite.DB(), "MAPS") + suite.NotNil(jppsoRegion) + suite.Nil(err) + + gblocAors, err := m.FetchGblocAorsByJppsoCodeRateAreaDept(suite.DB(), jppsoRegion.ID, oconusRateArea.ID, m.DepartmentIndicatorARMY.String()) + suite.NotNil(gblocAors) + suite.Nil(err) + + gbloc, err := m.FetchAddressGbloc(suite.DB(), originDutyLocation.Address, serviceMember) + suite.NoError(err) + suite.NotNil(gbloc) + suite.Equal(string(*gbloc), "MAPS") + }) + + suite.Run("fetches duty location GBLOC for AK NOT Cordova address, Zone IV", func() { + oconusRateArea, _, originDutyLocation := setupDataForOconusDutyLocation("99803") + + army := m.AffiliationARMY + serviceMember := factory.BuildServiceMember(suite.DB(), []factory.Customization{ + { + Model: m.ServiceMember{ + Affiliation: &army, + }, + }, + }, nil) + + jppsoRegion, err := m.FetchJppsoRegionByCode(suite.DB(), "MAPK") + suite.NotNil(jppsoRegion) + suite.Nil(err) + + gblocAors, err := m.FetchGblocAorsByJppsoCodeRateAreaDept(suite.DB(), jppsoRegion.ID, oconusRateArea.ID, m.DepartmentIndicatorARMY.String()) + suite.NotNil(gblocAors) + suite.Nil(err) + + gbloc, err := m.FetchAddressGbloc(suite.DB(), originDutyLocation.Address, serviceMember) + suite.NoError(err) + suite.NotNil(gbloc) + 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/gbloc_aors.go b/pkg/models/gbloc_aors.go index e4fa1612355..132de00bbb1 100644 --- a/pkg/models/gbloc_aors.go +++ b/pkg/models/gbloc_aors.go @@ -3,6 +3,7 @@ package models import ( "time" + "github.com/gobuffalo/pop/v6" "github.com/gofrs/uuid" ) @@ -19,3 +20,17 @@ type GblocAors struct { func (c GblocAors) TableName() string { return "gbloc_aors" } + +func FetchGblocAorsByJppsoCodeRateAreaDept(db *pop.Connection, jppsoRegionId uuid.UUID, oconusRateAreaId uuid.UUID, deptInd string) (*GblocAors, error) { + var gblocAors GblocAors + err := db.Q(). + InnerJoin("jppso_regions jr", "gbloc_aors.jppso_regions_id = jr.id"). + Where("gbloc_aors.oconus_rate_area_id = ?", oconusRateAreaId). + Where("(gbloc_aors.department_indicator = ? or gbloc_aors.department_indicator is null)", deptInd). + Where("jr.id = ?", jppsoRegionId). + First(&gblocAors) + if err != nil { + return nil, err + } + return &gblocAors, nil +} diff --git a/pkg/models/gbloc_aors_test.go b/pkg/models/gbloc_aors_test.go new file mode 100644 index 00000000000..7e35492e58a --- /dev/null +++ b/pkg/models/gbloc_aors_test.go @@ -0,0 +1,22 @@ +package models_test + +import ( + "github.com/transcom/mymove/pkg/models" +) + +func (suite *ModelSuite) TestFetchGblocAorsByJppsoCodeRateAreaDept() { + usprc, err := models.FindByZipCode(suite.AppContextForTest().DB(), "99801") + suite.NotNil(usprc) + suite.FatalNoError(err) + oconusRateArea, err := models.FetchOconusRateAreaByCityId(suite.DB(), usprc.ID.String()) + suite.NotNil(oconusRateArea) + suite.NoError(err) + + jppsoRegion, err := models.FetchJppsoRegionByCode(suite.DB(), "MAPK") + suite.NotNil(jppsoRegion) + suite.NoError(err) + + gblocAors, err := models.FetchGblocAorsByJppsoCodeRateAreaDept(suite.DB(), jppsoRegion.ID, oconusRateArea.ID, models.DepartmentIndicatorARMY.String()) + suite.NotNil(gblocAors) + suite.NoError(err) +} diff --git a/pkg/models/jppso_regions.go b/pkg/models/jppso_regions.go index 0788c78abb2..aefb3ea106f 100644 --- a/pkg/models/jppso_regions.go +++ b/pkg/models/jppso_regions.go @@ -3,6 +3,7 @@ package models import ( "time" + "github.com/gobuffalo/pop/v6" "github.com/gofrs/uuid" ) @@ -18,3 +19,14 @@ type JppsoRegions struct { func (c JppsoRegions) TableName() string { return "jppso_regions" } + +func FetchJppsoRegionByCode(db *pop.Connection, code string) (*JppsoRegions, error) { + var jppsoRegions JppsoRegions + err := db.Q(). + Where("jppso_regions.code = ?", code). + First(&jppsoRegions) + if err != nil { + return nil, err + } + return &jppsoRegions, nil +} diff --git a/pkg/models/jppso_regions_test.go b/pkg/models/jppso_regions_test.go new file mode 100644 index 00000000000..21da54ea898 --- /dev/null +++ b/pkg/models/jppso_regions_test.go @@ -0,0 +1,12 @@ +package models_test + +import ( + "github.com/transcom/mymove/pkg/models" +) + +func (suite *ModelSuite) TestFetchJppsoRegionByCode() { + jppsoRegion, err := models.FetchJppsoRegionByCode(suite.DB(), "MAPK") + suite.NotNil(jppsoRegion) + suite.NoError(err) + suite.Equal("USCG Base Ketchikan", jppsoRegion.Name) +} diff --git a/pkg/models/move.go b/pkg/models/move.go index f0b3ac49269..e3b1b889bf8 100644 --- a/pkg/models/move.go +++ b/pkg/models/move.go @@ -203,48 +203,63 @@ func FetchMove(db *pop.Connection, session *auth.Session, id uuid.UUID) (*Move, return &move, nil } -// GetDestinationPostalCode returns the postal code for the move. This ensures that business logic is centralized. -func (m Move) GetDestinationPostalCode(db *pop.Connection) (string, error) { +// GetDestinationGBLOC returns the GBLOC for the move. This ensures that business logic is centralized. +func (m Move) GetDestinationGBLOC(db *pop.Connection) (string, error) { // Since this requires looking up the move in the DB, the move must have an ID. This means, the move has to have been created first. if uuid.UUID.IsNil(m.ID) { - return "", errors.WithMessage(ErrInvalidOrderID, "You must created the move in the DB before getting the destination Postal Code.") + return "", errors.WithMessage(ErrInvalidOrderID, "You must created the move in the DB before getting the destination GBLOC.") } - err := db.Load(&m, "Orders") + destinationAddress, err := m.GetDestinationAddress(db) if err != nil { - if err.Error() == RecordNotFoundErrorString { - return "", errors.WithMessage(err, "No Orders found in the DB associated with moveID "+m.ID.String()) - } return "", err } - var gblocsMap map[uuid.UUID]string - gblocsMap, err = m.Orders.GetDestinationPostalCodeForAssociatedMoves(db) - if err != nil { - return "", err + var newGBLOC string + if *destinationAddress.IsOconus { + err := db.Load(&m.Orders, "ServiceMember") + if err != nil { + if err.Error() == RecordNotFoundErrorString { + return "", errors.WithMessage(err, "No Service Member found in the DB associated with moveID "+m.ID.String()) + } + return "", err + } + newGBLOCOconus, err := FetchAddressGbloc(db, *destinationAddress, m.Orders.ServiceMember) + if err != nil { + return "", err + } + newGBLOC = *newGBLOCOconus + } else { + newGBLOCConus, err := FetchGBLOCForPostalCode(db, destinationAddress.PostalCode) + if err != nil { + return "", err + } + newGBLOC = newGBLOCConus.GBLOC } - return gblocsMap[m.ID], nil + + return newGBLOC, err } -// GetDestinationGBLOC returns the GBLOC for the move. This ensures that business logic is centralized. -func (m Move) GetDestinationGBLOC(db *pop.Connection) (string, error) { +// GetDestinationAddress returns the address for the move. This ensures that business logic is centralized. +func (m Move) GetDestinationAddress(db *pop.Connection) (*Address, error) { // Since this requires looking up the move in the DB, the move must have an ID. This means, the move has to have been created first. if uuid.UUID.IsNil(m.ID) { - return "", errors.WithMessage(ErrInvalidOrderID, "You must created the move in the DB before getting the destination GBLOC.") + return nil, errors.WithMessage(ErrInvalidOrderID, "You must created the move in the DB before getting the destination Postal Code.") } - postalCode, err := m.GetDestinationPostalCode(db) + err := db.Load(&m, "Orders") if err != nil { - return "", err + if err.Error() == RecordNotFoundErrorString { + return nil, errors.WithMessage(err, "No Orders found in the DB associated with moveID "+m.ID.String()) + } + return nil, err } - var gblocResult PostalCodeToGBLOC - gblocResult, err = FetchGBLOCForPostalCode(db, postalCode) + destinationAddress, err := m.Orders.GetDestinationAddressForAssociatedMoves(db) if err != nil { - return "", err + return nil, err } - - return gblocResult.GBLOC, err + return destinationAddress, nil } // CreateSignedCertification creates a new SignedCertification associated with this move diff --git a/pkg/models/mto_service_items.go b/pkg/models/mto_service_items.go index 446c822c1ee..814febf0e70 100644 --- a/pkg/models/mto_service_items.go +++ b/pkg/models/mto_service_items.go @@ -1,6 +1,8 @@ package models import ( + "database/sql/driver" + "fmt" "time" "github.com/gobuffalo/pop/v6" @@ -94,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"` @@ -250,3 +254,189 @@ func (m MTOServiceItem) GetMTOServiceItemTypeFromServiceItem() MTOServiceItemTyp PODLocationID: m.PODLocationID, } } + +func (m MTOServiceItem) Value() (driver.Value, error) { + var id string + var moveTaskOrderID string + var mtoShipmentID string + var reason string + var pickupPostalCode string + var description string + var rejectionReason string + var approvedAt string + var rejectedAt string + var sitPostalCode string + var sitRequestedDelivery string + var requestedApprovalsRequestedStatus bool + var serviceLocation string + var sitEntryDate string + var sitDepartureDate string + var sitCustomerContacted string + var poeLocationID string + var podLocationID string + var sitDestinationFinalAddressID string + var sitOriginHHGOriginalAddressID string + var sitOriginHHGActualAddressID string + var sitDestinationOriginalAddressID string + var standaloneCrate bool + var lockedPriceCents int64 + var sitDeliveryMiles int + var customerExpenseReason string + var estimatedWeight int64 + var actualWeight int64 + var pricingEstimate int64 + + if m.ID != uuid.Nil { + id = m.ID.String() + } + + if m.MoveTaskOrderID != uuid.Nil { + moveTaskOrderID = m.MoveTaskOrderID.String() + } + + if *m.MTOShipmentID != uuid.Nil { + mtoShipmentID = m.MTOShipmentID.String() + } + + if m.Reason != nil { + reason = *m.Reason + } + + if m.PickupPostalCode != nil { + pickupPostalCode = *m.PickupPostalCode + } + + if m.Description != nil { + description = *m.Description + } + + if m.RejectionReason != nil { + rejectionReason = *m.RejectionReason + } + + if m.ApprovedAt != nil { + approvedAt = m.ApprovedAt.Format("2006-01-02 15:04:05") + } + + if m.RejectedAt != nil { + rejectedAt = m.RejectedAt.Format("2006-01-02 15:04:05") + } + + if m.SITPostalCode != nil { + sitPostalCode = *m.SITPostalCode + } + + if m.SITRequestedDelivery != nil { + sitRequestedDelivery = m.SITRequestedDelivery.Format("2006-01-02 15:04:05") + } + + if m.RequestedApprovalsRequestedStatus != nil { + requestedApprovalsRequestedStatus = *m.RequestedApprovalsRequestedStatus + } + + if m.ServiceLocation != nil { + serviceLocation = string(*m.ServiceLocation) + } + + if m.SITEntryDate != nil { + sitEntryDate = m.SITEntryDate.Format("2006-01-02 15:04:05") + } + + if m.SITDepartureDate != nil { + sitDepartureDate = m.SITDepartureDate.Format("2006-01-02 15:04:05") + } + + if m.SITCustomerContacted != nil { + sitCustomerContacted = m.SITCustomerContacted.Format("2006-01-02 15:04:05") + } + + if m.POELocationID != nil { + poeLocationID = m.POELocationID.String() + } + + if m.PODLocationID != nil { + podLocationID = m.PODLocationID.String() + } + + if m.SITDestinationFinalAddressID != nil { + sitDestinationFinalAddressID = m.SITDestinationFinalAddressID.String() + } + + if m.SITOriginHHGActualAddressID != nil { + sitOriginHHGActualAddressID = m.SITOriginHHGActualAddressID.String() + } + + if m.SITDestinationOriginalAddressID != nil { + sitDestinationOriginalAddressID = m.SITDestinationOriginalAddressID.String() + } + + if m.SITOriginHHGOriginalAddressID != nil { + sitOriginHHGOriginalAddressID = m.SITOriginHHGOriginalAddressID.String() + } + + if m.StandaloneCrate != nil { + standaloneCrate = *m.StandaloneCrate + } + + if m.LockedPriceCents != nil { + lockedPriceCents = m.LockedPriceCents.Int64() + } + + if m.SITDeliveryMiles != nil { + sitDeliveryMiles = *m.SITDeliveryMiles + } + + if m.CustomerExpenseReason != nil { + customerExpenseReason = *m.CustomerExpenseReason + } + + if m.EstimatedWeight != nil { + estimatedWeight = m.EstimatedWeight.Int64() + } + + if m.ActualWeight != nil { + actualWeight = m.ActualWeight.Int64() + } + + if m.PricingEstimate != nil { + 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)", + id, + moveTaskOrderID, + mtoShipmentID, + m.CreatedAt.Format("2006-01-02 15:04:05"), + m.UpdatedAt.Format("2006-01-02 15:04:05"), + reason, + pickupPostalCode, + description, + m.Status, + rejectionReason, + approvedAt, + rejectedAt, + sitPostalCode, + sitEntryDate, + sitDepartureDate, + sitDestinationFinalAddressID, + sitOriginHHGOriginalAddressID, + sitOriginHHGActualAddressID, + estimatedWeight, + actualWeight, + sitDestinationOriginalAddressID, + sitCustomerContacted, + sitRequestedDelivery, + requestedApprovalsRequestedStatus, + m.CustomerExpense, + customerExpenseReason, + sitDeliveryMiles, + pricingEstimate, + standaloneCrate, + lockedPriceCents, + serviceLocation, + poeLocationID, + podLocationID, + m.ReService.Code.String(), + ) + return []byte(s), nil +} diff --git a/pkg/models/mto_service_items_test.go b/pkg/models/mto_service_items_test.go index 720ef5c9226..8d55e1c4bea 100644 --- a/pkg/models/mto_service_items_test.go +++ b/pkg/models/mto_service_items_test.go @@ -110,3 +110,57 @@ func (suite *ModelSuite) TestFetchRelatedDestinationSITServiceItems() { suite.Len(relatedServiceItems, 0, "There should be zero related destination service items") }) } + +func (suite *ModelSuite) TestValue() { + suite.Run("value returns an array", 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) + + byte, err := msServiceItem.Value() + + suite.NotNil(byte) + suite.Nil(err) + }) +} + +func (suite *ModelSuite) TestGetMTOServiceItemTypeFromServiceItem() { + suite.Run("returns 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) + + returnedShipment := msServiceItem.GetMTOServiceItemTypeFromServiceItem() + suite.NotNil(returnedShipment) + }) +} diff --git a/pkg/models/mto_shipments.go b/pkg/models/mto_shipments.go index 917e75b7978..b3c69cb52bb 100644 --- a/pkg/models/mto_shipments.go +++ b/pkg/models/mto_shipments.go @@ -3,14 +3,17 @@ package models import ( "database/sql" "fmt" + "slices" "time" "github.com/gobuffalo/pop/v6" "github.com/gobuffalo/validate/v3" "github.com/gobuffalo/validate/v3/validators" "github.com/gofrs/uuid" + "github.com/lib/pq" "github.com/pkg/errors" + "github.com/transcom/mymove/pkg/apperror" "github.com/transcom/mymove/pkg/unit" ) @@ -33,6 +36,20 @@ const ( MarketCodeInternational MarketCode = "i" // international ) +// Add to this list as international service items are implemented +var internationalAccessorialServiceItems = []ReServiceCode{ + ReServiceCodeICRT, + ReServiceCodeIUCRT, + ReServiceCodeIOASIT, + ReServiceCodeIDASIT, + ReServiceCodeIOFSIT, + ReServiceCodeIDFSIT, + ReServiceCodeIOPSIT, + ReServiceCodeIDDSIT, + ReServiceCodeIDSHUT, + ReServiceCodeIOSHUT, +} + const ( // MTOShipmentTypeHHG is an HHG Shipment Type default MTOShipmentTypeHHG MTOShipmentType = "HHG" @@ -277,33 +294,6 @@ func GetCustomerFromShipment(db *pop.Connection, shipmentID uuid.UUID) (*Service return &serviceMember, nil } -func (m *MTOShipment) UpdateOrdersDestinationGBLOC(db *pop.Connection) error { - // Since this requires looking up the order in the DB, the order must have an ID. This means, the order has to have been created first. - if uuid.UUID.IsNil(m.ID) { - return fmt.Errorf("error updating orders destination GBLOC for shipment due to no shipment ID provided") - } - - var err error - var order Order - - err = db.Load(&m, "MoveTaskOrder.OrdersID") - if err != nil { - return fmt.Errorf("error loading orders for shipment ID: %s with error %w", m.ID, err) - } - - order, err = FetchOrder(db, m.MoveTaskOrder.OrdersID) - if err != nil { - return fmt.Errorf("error fetching order for shipment ID: %s with error %w", m.ID, err) - } - - err = order.UpdateDestinationGBLOC(db) - if err != nil { - return fmt.Errorf("error fetching GBLOC for postal code with error %w", err) - } - - return nil -} - // Helper function to check that an MTO Shipment contains a PPM Shipment func (m MTOShipment) ContainsAPPMShipment() bool { return m.PPMShipment != nil @@ -459,6 +449,28 @@ func CreateApprovedServiceItemsForShipment(db *pop.Connection, shipment *MTOShip return nil } +func CreateInternationalAccessorialServiceItemsForShipment(db *pop.Connection, shipmentId uuid.UUID, mtoServiceItems MTOServiceItems) ([]string, error) { + if len(mtoServiceItems) == 0 { + err := fmt.Errorf("must request service items to create: %s", shipmentId) + return nil, apperror.NewInvalidInputError(shipmentId, err, nil, err.Error()) + } + + for _, serviceItem := range mtoServiceItems { + if !slices.Contains(internationalAccessorialServiceItems, serviceItem.ReService.Code) { + err := fmt.Errorf("cannot create domestic service items for international shipment: %s", shipmentId) + return nil, apperror.NewInvalidInputError(shipmentId, err, nil, err.Error()) + } + } + + createdServiceItemIDs := []string{} + err := db.RawQuery("CALL create_accessorial_service_items_for_shipment($1, $2, $3)", shipmentId, pq.Array(mtoServiceItems), pq.StringArray(createdServiceItemIDs)).All(&createdServiceItemIDs) + if err != nil { + return nil, apperror.NewInvalidInputError(shipmentId, err, nil, err.Error()) + } + + return createdServiceItemIDs, nil +} + // a db stored proc that will handle updating the pricing_estimate columns of basic service items for shipment types: // iHHG // iUB diff --git a/pkg/models/mto_shipments_test.go b/pkg/models/mto_shipments_test.go index 558e80525bd..62957b6222b 100644 --- a/pkg/models/mto_shipments_test.go +++ b/pkg/models/mto_shipments_test.go @@ -325,6 +325,61 @@ func (suite *ModelSuite) TestCreateApprovedServiceItemsForShipment() { }) } +func (suite *ModelSuite) TestCreateInternationalAccessorialServiceItemsForShipment() { + suite.Run("test creating accessorial service items for shipment", func() { + + move := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) + + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + Status: models.MTOShipmentStatusApproved, + }, + }, + { + Model: move, + LinkOnly: true, + }, + }, nil) + + serviceItem := factory.BuildMTOServiceItemBasic(suite.DB(), []factory.Customization{ + { + Model: models.MTOServiceItem{ + RejectionReason: models.StringPointer("not applicable"), + MTOShipmentID: &shipment.ID, + Reason: models.StringPointer("this is a special item"), + EstimatedWeight: models.PoundPointer(400), + ActualWeight: models.PoundPointer(500), + }, + }, + { + Model: move, + LinkOnly: true, + }, + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.ReService{ + Code: models.ReServiceCodeIDSHUT, + }, + }, + }, nil) + + serviceItem.MTOShipment = shipment + serviceItemIds, err := models.CreateInternationalAccessorialServiceItemsForShipment(suite.DB(), shipment.ID, models.MTOServiceItems{serviceItem}) + suite.NoError(err) + suite.NotNil(serviceItemIds) + }) + + suite.Run("test error handling for invalid shipment", func() { + serviceItemIds, err := models.CreateInternationalAccessorialServiceItemsForShipment(suite.DB(), uuid.Nil, models.MTOServiceItems{}) + suite.Error(err) + suite.Nil(serviceItemIds) + }) +} + func (suite *ModelSuite) TestFindShipmentByID() { suite.Run("success - test find", func() { shipment := factory.BuildMTOShipmentMinimal(suite.DB(), nil, nil) @@ -485,3 +540,25 @@ func (suite *ModelSuite) TestGetDestinationGblocForShipment() { suite.Equal(*gbloc, "USMC") }) } + +func (suite *ModelSuite) TestIsPPMShipment() { + suite.Run("true - shipment is a ppm", func() { + ppmShipment := factory.BuildPPMShipment(suite.DB(), nil, nil) + mtoShipment := factory.BuildMTOShipmentMinimal(suite.DB(), nil, nil) + + mtoShipment.PPMShipment = &ppmShipment + mtoShipment.ShipmentType = models.MTOShipmentTypePPM + + isPPM := mtoShipment.IsPPMShipment() + suite.NotNil(isPPM) + suite.Equal(isPPM, true) + }) + + suite.Run("false - shipment is not a ppm", func() { + nonPPMshipment := factory.BuildMTOShipmentMinimal(suite.DB(), nil, nil) + + isPPM := nonPPMshipment.IsPPMShipment() + suite.NotNil(isPPM) + suite.Equal(isPPM, false) + }) +} diff --git a/pkg/models/office_user_test.go b/pkg/models/office_user_test.go index 7759e148874..e0b25879d98 100644 --- a/pkg/models/office_user_test.go +++ b/pkg/models/office_user_test.go @@ -97,25 +97,3 @@ func (suite *ModelSuite) TestFetchOfficeUserByEmailCaseSensitivity() { suite.NotNil(user) suite.Equal(user.Email, userEmail) } - -func (suite *ModelSuite) TestFetchOfficeUserByID() { - fakeUUID, _ := uuid.FromString("99999999-8888-7777-8b57-e39519f42dc1") - - _, err := m.FetchOfficeUserByID(suite.DB(), fakeUUID) - suite.NotNil(err) - - office := CreateTestShippingOffice(suite) - newUser := m.OfficeUser{ - LastName: "Tester", - FirstName: "Sally", - Email: "test@test.com", - Telephone: "(907) 555-1212", - TransportationOfficeID: office.ID, - } - suite.MustSave(&newUser) - - user, err := m.FetchOfficeUserByID(suite.DB(), newUser.ID) - suite.NoError(err) - suite.NotNil(user) - suite.Equal(newUser.ID, user.ID) -} diff --git a/pkg/models/order.go b/pkg/models/order.go index 476b96b1d43..2b0601f17c7 100644 --- a/pkg/models/order.go +++ b/pkg/models/order.go @@ -482,45 +482,16 @@ func (o Order) FetchAllShipmentsExcludingRejected(db *pop.Connection) (map[uuid. } /* - * GetDestinationGBLOC returns a map of destination GBLOCs for the first shipments from all of - * the moves that are associated with an order. If there are no shipments returned on a particular move, - * it will return the GBLOC of the new duty station address for that move. - */ -func (o Order) GetDestinationGBLOC(db *pop.Connection) (map[uuid.UUID]string, error) { - // Since this requires looking up the order in the DB, the order must have an ID. This means, the order has to have been created first. - if uuid.UUID.IsNil(o.ID) { - return nil, errors.WithMessage(ErrInvalidOrderID, "You must created the order in the DB before getting the destination GBLOC.") - } - - destinationPostalCodesMap, err := o.GetDestinationPostalCodeForAssociatedMoves(db) - if err != nil { - return nil, err - } - - destinationGBLOCsMap := make(map[uuid.UUID]string) - for k, v := range destinationPostalCodesMap { - var gblocResult PostalCodeToGBLOC - gblocResult, err = FetchGBLOCForPostalCode(db, v) - if err != nil { - return nil, errors.WithMessage(err, "Could not get GBLOC for postal code "+v+" for move ID "+k.String()) - } - destinationGBLOCsMap[k] = gblocResult.GBLOC - } - - return destinationGBLOCsMap, nil -} - -/* -* GetDestinationPostalCodeForAssociatedMove returns a map of Postal Codes of the destination address for the first shipments from each of +* GetDestinationAddressForAssociatedMoves returns the destination Address for the first shipments from each of * the moves that are associated with an order. If there are no shipments returned, it will return the -* Postal Code of the new duty station addresses. +* Address of the new duty station addresses. */ -func (o Order) GetDestinationPostalCodeForAssociatedMoves(db *pop.Connection) (map[uuid.UUID]string, error) { +func (o Order) GetDestinationAddressForAssociatedMoves(db *pop.Connection) (*Address, error) { if uuid.UUID.IsNil(o.ID) { - return nil, errors.WithMessage(ErrInvalidOrderID, "You must created the order in the DB before getting the destination Postal Code.") + return nil, errors.WithMessage(ErrInvalidOrderID, "You must create the order in the DB before getting the destination Address.") } - err := db.Load(&o, "Moves", "NewDutyLocation.Address.PostalCode") + err := db.Load(&o, "Moves", "NewDutyLocation.Address") if err != nil { if err.Error() == RecordNotFoundErrorString { return nil, errors.WithMessage(err, "No Moves were found for the order ID "+o.ID.String()) @@ -528,8 +499,8 @@ func (o Order) GetDestinationPostalCodeForAssociatedMoves(db *pop.Connection) (m return nil, err } - // zipsMap is a map of key, value pairs where the key is the move id and the value is the destination postal code - zipsMap := make(map[uuid.UUID]string) + // addrMap is a map of key, value pairs where the key is the move id and the value is the destination address + var destinationAddress Address for i, m := range o.Moves { err = db.Load(&o.Moves[i], "MTOShipments") if err != nil { @@ -563,71 +534,23 @@ func (o Order) GetDestinationPostalCodeForAssociatedMoves(db *pop.Connection) (m return shipments[i].CreatedAt.Before(shipments[j].CreatedAt) }) - var addressResult *Address - addressResult, err = shipments[0].GetDestinationAddress(db) + addressResult, err := shipments[0].GetDestinationAddress(db) if err != nil { - if err == ErrMissingDestinationAddress || err == ErrUnsupportedShipmentType { - zipsMap[o.Moves[i].ID] = o.NewDutyLocation.Address.PostalCode - } return nil, err } if addressResult != nil { - zipsMap[o.Moves[i].ID] = addressResult.PostalCode + destinationAddress = *addressResult } else { return nil, errors.WithMessage(ErrMissingDestinationAddress, "No destination address was able to be found for the order ID "+o.ID.String()) } } else { // No valid shipments, use new duty location - zipsMap[o.Moves[i].ID] = o.NewDutyLocation.Address.PostalCode + destinationAddress = o.NewDutyLocation.Address } } - if len(zipsMap) == 0 { - return nil, errors.New("No destination postal codes were found for the order ID " + o.ID.String()) - } - - return zipsMap, nil -} - -// UpdateDestinationGBLOC updates the destination GBLOC for the associated Order in the DB -func (o Order) UpdateDestinationGBLOC(db *pop.Connection) error { - // Since this requires looking up the order in the DB, the order must have an ID. This means, the order has to have been created first. - if uuid.UUID.IsNil(o.ID) { - return errors.WithMessage(ErrInvalidOrderID, "You must created the order in the DB before updating the destination GBLOC.") - } - - var dbOrder Order - err := db.Find(&dbOrder, o.ID) - if err != nil { - if err.Error() == RecordNotFoundErrorString { - return errors.WithMessage(err, "No Order was found for the order ID "+o.ID.String()) - } - return err - } - - err = db.Load(&o, "NewDutyLocation.Address.PostalCode") - if err != nil { - if err.Error() == RecordNotFoundErrorString { - return errors.WithMessage(err, "No New Duty Location Address Postal Code was found for the order ID "+o.ID.String()) - } - return err - } - - var gblocResult PostalCodeToGBLOC - gblocResult, err = FetchGBLOCForPostalCode(db, o.NewDutyLocation.Address.PostalCode) - if err != nil { - return errors.WithMessage(err, "Could not get GBLOC for postal code "+o.NewDutyLocation.Address.PostalCode) - } - - dbOrder.DestinationGBLOC = &gblocResult.GBLOC - - err = db.Save(&dbOrder) - if err != nil { - return errors.WithMessage(err, "Could not save the updated destination GBLOC for order ID "+o.ID.String()) - } - - return nil + return &destinationAddress, nil } // IsCompleteForGBL checks if orders have all fields necessary to generate a GBL diff --git a/pkg/models/payment_request.go b/pkg/models/payment_request.go index 120f7a1d4fb..8a57e05e0ae 100644 --- a/pkg/models/payment_request.go +++ b/pkg/models/payment_request.go @@ -89,6 +89,11 @@ type PaymentRequest struct { TPPSPaidInvoiceReports TPPSPaidInvoiceReportEntrys `has_many:"tpps_paid_invoice_reports" fk_id:"payment_request_number"` } +type PaymentRequestWithEarliestRequestedDate struct { + ID uuid.UUID `json:"id" db:"id"` + EarliestRequestedDate time.Time `db:"requested_at"` +} + // TableName overrides the table name used by Pop. func (p PaymentRequest) TableName() string { return "payment_requests" diff --git a/pkg/models/port_location.go b/pkg/models/port_location.go index 4d514a2a545..414006d9c82 100644 --- a/pkg/models/port_location.go +++ b/pkg/models/port_location.go @@ -3,7 +3,10 @@ package models import ( "time" + "github.com/gobuffalo/pop/v6" "github.com/gofrs/uuid" + + "github.com/transcom/mymove/pkg/apperror" ) type PortLocation struct { @@ -24,3 +27,12 @@ type PortLocation struct { func (l PortLocation) TableName() string { return "port_locations" } + +func FetchPortLocationByCode(db *pop.Connection, portCode string) (*PortLocation, error) { + portLocation := PortLocation{} + err := db.Eager("Port", "UsPostRegionCity").Where("is_active = TRUE").InnerJoin("ports p", "port_id = p.id").Where("p.port_code = $1", portCode).First(&portLocation) + if err != nil { + return nil, apperror.NewQueryError("PortLocation", err, "") + } + return &portLocation, err +} diff --git a/pkg/models/port_location_test.go b/pkg/models/port_location_test.go index c63a4e34e29..ae50c68880f 100644 --- a/pkg/models/port_location_test.go +++ b/pkg/models/port_location_test.go @@ -2,6 +2,7 @@ package models_test import ( "github.com/transcom/mymove/pkg/factory" + "github.com/transcom/mymove/pkg/models" ) func (suite *ModelSuite) TestPortLocation() { @@ -24,3 +25,28 @@ func (suite *ModelSuite) TestPortLocation() { suite.Equal("port_locations", portLocation.TableName()) }) } + +func (suite *ModelSuite) TestFetchPortLocationByCode() { + suite.Run("Port location can be fetched when it exists", func() { + + portLocation := factory.FetchPortLocation(suite.DB(), []factory.Customization{ + { + Model: models.Port{ + PortCode: "SEA", + }, + }, + }, nil) + suite.NotNil(portLocation) + + result, err := models.FetchPortLocationByCode(suite.AppContextForTest().DB(), "SEA") + suite.NotNil(result) + suite.NoError(err) + suite.Equal(portLocation.ID, result.ID) + }) + + suite.Run("Sends back an error when it does not exist", func() { + result, err := models.FetchPortLocationByCode(suite.AppContextForTest().DB(), "123") + suite.Nil(result) + suite.Error(err) + }) +} diff --git a/pkg/models/ppm_shipment.go b/pkg/models/ppm_shipment.go index 0737417207c..d53bdb5b3ac 100644 --- a/pkg/models/ppm_shipment.go +++ b/pkg/models/ppm_shipment.go @@ -1,6 +1,7 @@ package models import ( + "fmt" "time" "github.com/gobuffalo/pop/v6" @@ -34,11 +35,14 @@ type PPMCloseout struct { RemainingIncentive *unit.Cents HaulPrice *unit.Cents HaulFSC *unit.Cents - HaulType HaulType + HaulType *HaulType DOP *unit.Cents DDP *unit.Cents PackPrice *unit.Cents UnpackPrice *unit.Cents + IntlPackPrice *unit.Cents + IntlUnpackPrice *unit.Cents + IntlLinehaulPrice *unit.Cents SITReimbursement *unit.Cents } @@ -319,3 +323,44 @@ func FetchPPMShipmentByPPMShipmentID(db *pop.Connection, ppmShipmentID uuid.UUID } return &ppmShipment, nil } + +type PPMIncentiveOCONUS struct { + TotalIncentive int `db:"total_incentive"` + PriceISLH int `db:"price_islh"` + PriceIHPK int `db:"price_ihpk"` + PriceIHUPK int `db:"price_ihupk"` + PriceFSC int `db:"price_fsc"` +} + +// a db function that will handle updating the estimated_incentive value +// this simulates pricing of a basic iHHG shipment with ISLH, IHPK, IHUPK, and the CONUS portion for a FSC +func CalculatePPMIncentive(db *pop.Connection, ppmID uuid.UUID, pickupAddressID uuid.UUID, destAddressID uuid.UUID, moveDate time.Time, mileage int, weight int, isEstimated bool, isActual bool, isMax bool) (*PPMIncentiveOCONUS, error) { + var incentive PPMIncentiveOCONUS + + err := db.RawQuery("SELECT * FROM calculate_ppm_incentive($1, $2, $3, $4, $5, $6, $7, $8, $9)", ppmID, pickupAddressID, destAddressID, moveDate, mileage, weight, isEstimated, isActual, isMax). + First(&incentive) + if err != nil { + return nil, fmt.Errorf("error calculating PPM incentive for PPM ID %s: %w", ppmID, err) + } + + return &incentive, nil +} + +type PPMSITCosts struct { + TotalSITCost int `db:"total_cost"` + PriceFirstDaySIT int `db:"price_first_day"` + PriceAddlDaySIT int `db:"price_addl_day"` +} + +// a db function that will handle calculating and returning the SIT costs related to a PPM shipment +func CalculatePPMSITCost(db *pop.Connection, ppmID uuid.UUID, addressID uuid.UUID, isOrigin bool, moveDate time.Time, weight int, sitDays int) (*PPMSITCosts, error) { + var costs PPMSITCosts + + err := db.RawQuery("SELECT * FROM calculate_ppm_SIT_cost($1, $2, $3, $4, $5, $6)", ppmID, addressID, isOrigin, moveDate, weight, sitDays). + First(&costs) + if err != nil { + return nil, fmt.Errorf("error calculating PPM SIT costs for PPM ID %s: %w", ppmID, err) + } + + return &costs, nil +} diff --git a/pkg/models/ppm_shipment_test.go b/pkg/models/ppm_shipment_test.go index 4def2567968..7dbeb27f545 100644 --- a/pkg/models/ppm_shipment_test.go +++ b/pkg/models/ppm_shipment_test.go @@ -7,6 +7,7 @@ import ( "github.com/gofrs/uuid" + "github.com/transcom/mymove/pkg/factory" "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/testdatagen" "github.com/transcom/mymove/pkg/unit" @@ -125,3 +126,161 @@ func (suite *ModelSuite) TestPPMShipmentValidation() { }) } } + +func (suite *ModelSuite) TestCalculatePPMIncentive() { + suite.Run("success - receive PPM incentive when all values exist", func() { + ppmShipment := factory.BuildPPMShipment(suite.DB(), nil, nil) + pickupUSPRC, err := models.FindByZipCode(suite.AppContextForTest().DB(), "74135") + suite.FatalNoError(err) + pickupAddress := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "Tester Address", + City: "Tulsa", + State: "OK", + PostalCode: "74133", + IsOconus: models.BoolPointer(false), + UsPostRegionCityID: &pickupUSPRC.ID, + }, + }, + }, nil) + + destUSPRC, err := models.FindByZipCode(suite.AppContextForTest().DB(), "99505") + suite.FatalNoError(err) + destAddress := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "JBER", + City: "JBER", + State: "AK", + PostalCode: "99505", + IsOconus: models.BoolPointer(true), + UsPostRegionCityID: &destUSPRC.ID, + }, + }, + }, nil) + + testdatagen.FetchOrMakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + StartDate: time.Now().Add(-24 * time.Hour), + EndDate: time.Now().Add(24 * time.Hour), + }, + }) + moveDate := time.Now() + mileage := 1000 + weight := 2000 + + incentives, err := models.CalculatePPMIncentive(suite.DB(), ppmShipment.ID, pickupAddress.ID, destAddress.ID, moveDate, mileage, weight, true, false, false) + suite.NoError(err) + suite.NotNil(incentives) + suite.NotNil(incentives.PriceFSC) + suite.NotNil(incentives.PriceIHPK) + suite.NotNil(incentives.PriceIHUPK) + suite.NotNil(incentives.PriceISLH) + suite.NotNil(incentives.TotalIncentive) + }) + + suite.Run("failure - contract doesn't exist", func() { + ppmShipment := factory.BuildPPMShipment(suite.DB(), nil, nil) + pickupUSPRC, err := models.FindByZipCode(suite.AppContextForTest().DB(), "74135") + suite.FatalNoError(err) + pickupAddress := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "Tester Address", + City: "Tulsa", + State: "OK", + PostalCode: "74133", + IsOconus: models.BoolPointer(false), + UsPostRegionCityID: &pickupUSPRC.ID, + }, + }, + }, nil) + + destUSPRC, err := models.FindByZipCode(suite.AppContextForTest().DB(), "99505") + suite.FatalNoError(err) + destAddress := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "JBER", + City: "JBER", + State: "AK", + PostalCode: "99505", + IsOconus: models.BoolPointer(true), + UsPostRegionCityID: &destUSPRC.ID, + }, + }, + }, nil) + + moveDate := time.Now() + mileage := 1000 + weight := 2000 + + incentives, err := models.CalculatePPMIncentive(suite.DB(), ppmShipment.ID, pickupAddress.ID, destAddress.ID, moveDate, mileage, weight, true, false, false) + suite.Error(err) + suite.Nil(incentives) + }) +} + +func (suite *ModelSuite) TestCalculatePPMSITCost() { + suite.Run("success - receive PPM SIT costs when all values exist", func() { + ppmShipment := factory.BuildPPMShipment(suite.DB(), nil, nil) + destUSPRC, err := models.FindByZipCode(suite.AppContextForTest().DB(), "99505") + suite.FatalNoError(err) + address := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "JBER", + City: "JBER", + State: "AK", + PostalCode: "99505", + IsOconus: models.BoolPointer(true), + UsPostRegionCityID: &destUSPRC.ID, + }, + }, + }, nil) + + testdatagen.FetchOrMakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + StartDate: time.Now().Add(-24 * time.Hour), + EndDate: time.Now().Add(24 * time.Hour), + }, + }) + moveDate := time.Now() + sitDays := 7 + weight := 2000 + + sitCost, err := models.CalculatePPMSITCost(suite.DB(), ppmShipment.ID, address.ID, false, moveDate, weight, sitDays) + suite.NoError(err) + suite.NotNil(sitCost) + suite.NotNil(sitCost.PriceAddlDaySIT) + suite.NotNil(sitCost.PriceFirstDaySIT) + suite.NotNil(sitCost.TotalSITCost) + }) + + suite.Run("failure - contract doesn't exist", func() { + ppmShipment := factory.BuildPPMShipment(suite.DB(), nil, nil) + destUSPRC, err := models.FindByZipCode(suite.AppContextForTest().DB(), "99505") + suite.FatalNoError(err) + address := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "JBER", + City: "JBER", + State: "AK", + PostalCode: "99505", + IsOconus: models.BoolPointer(true), + UsPostRegionCityID: &destUSPRC.ID, + }, + }, + }, nil) + + moveDate := time.Now() + sitDays := 7 + weight := 2000 + + sitCost, err := models.CalculatePPMSITCost(suite.DB(), ppmShipment.ID, address.ID, false, moveDate, weight, sitDays) + suite.Error(err) + suite.Nil(sitCost) + }) +} diff --git a/pkg/models/re_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_other_price.go b/pkg/models/re_intl_other_price.go index b8dce673214..1d95b5fcdd8 100644 --- a/pkg/models/re_intl_other_price.go +++ b/pkg/models/re_intl_other_price.go @@ -1,6 +1,7 @@ package models import ( + "fmt" "time" "github.com/gobuffalo/pop/v6" @@ -45,3 +46,36 @@ func (r *ReIntlOtherPrice) Validate(_ *pop.Connection) (*validate.Errors, error) &validators.IntIsGreaterThan{Field: r.PerUnitCents.Int(), Name: "PerUnitCents", Compared: -1}, ), nil } + +// fetches a row from re_intl_other_prices using passed in parameters +// gets the rate_area_id & is_peak_period based on values provided +func FetchReIntlOtherPrice(db *pop.Connection, addressID uuid.UUID, serviceID uuid.UUID, contractID uuid.UUID, referenceDate *time.Time) (*ReIntlOtherPrice, error) { + if addressID != uuid.Nil && serviceID != uuid.Nil && contractID != uuid.Nil && referenceDate != nil { + // need to get the rate area first + rateAreaID, err := FetchRateAreaID(db, addressID, &serviceID, contractID) + if err != nil { + return nil, fmt.Errorf("error fetching rate area id for shipment ID: %s, service ID %s, and contract ID: %s: %s", addressID, serviceID, contractID, err) + } + + var isPeakPeriod bool + err = db.RawQuery("SELECT is_peak_period($1)", referenceDate).First(&isPeakPeriod) + if err != nil { + return nil, fmt.Errorf("error checking if date is peak period with date: %s: %s", contractID, err) + } + + var reIntlOtherPrice ReIntlOtherPrice + err = db.Q(). + Where("contract_id = ?", contractID). + Where("service_id = ?", serviceID). + Where("is_peak_period = ?", isPeakPeriod). + Where("rate_area_id = ?", rateAreaID). + First(&reIntlOtherPrice) + if err != nil { + return nil, fmt.Errorf("error fetching row from re_int_other_prices using rateAreaID %s, service ID %s, and contract ID: %s: %s", rateAreaID, serviceID, contractID, err) + } + + return &reIntlOtherPrice, nil + } + + return nil, fmt.Errorf("error value from re_intl_other_prices - required parameters not provided") +} diff --git a/pkg/models/re_intl_other_price_test.go b/pkg/models/re_intl_other_price_test.go index 69e9770c47a..8cde04c61d4 100644 --- a/pkg/models/re_intl_other_price_test.go +++ b/pkg/models/re_intl_other_price_test.go @@ -1,9 +1,13 @@ package models_test import ( + "time" + "github.com/gofrs/uuid" + "github.com/transcom/mymove/pkg/factory" "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/testdatagen" ) func (suite *ModelSuite) TestReIntlOtherPriceValidation() { @@ -43,3 +47,76 @@ func (suite *ModelSuite) TestReIntlOtherPriceValidation() { suite.verifyValidationErrors(&intlOtherPrice, expErrors) }) } + +func (suite *ModelSuite) TestFetchReIntlOtherPrice() { + suite.Run("success - receive ReIntlOtherPrice when all values exist and are found", func() { + address := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "JBER", + City: "JBER", + State: "AK", + PostalCode: "99505", + IsOconus: models.BoolPointer(true), + }, + }, + }, nil) + + reService, err := models.FetchReServiceByCode(suite.DB(), models.ReServiceCodeIHPK) + suite.NoError(err) + suite.NotNil(reService) + + contract := testdatagen.FetchOrMakeReContract(suite.DB(), testdatagen.Assertions{}) + moveDate := time.Now() + + reIntlOtherPrice, err := models.FetchReIntlOtherPrice(suite.DB(), address.ID, reService.ID, contract.ID, &moveDate) + suite.NoError(err) + suite.NotNil(reIntlOtherPrice) + suite.NotNil(reIntlOtherPrice.PerUnitCents) + }) + + suite.Run("failure - receive error when values aren't provided", func() { + address := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "JBER", + City: "JBER", + State: "AK", + PostalCode: "99505", + IsOconus: models.BoolPointer(true), + }, + }, + }, nil) + + reService, err := models.FetchReServiceByCode(suite.DB(), models.ReServiceCodeIHPK) + suite.NoError(err) + suite.NotNil(reService) + + contract := testdatagen.FetchOrMakeReContractYear(suite.DB(), testdatagen.Assertions{}) + moveDate := time.Now() + + // no address + reIntlOtherPrice, err := models.FetchReIntlOtherPrice(suite.DB(), uuid.Nil, reService.ID, contract.ID, &moveDate) + suite.Error(err) + suite.Nil(reIntlOtherPrice) + suite.Contains(err.Error(), "error value from re_intl_other_prices - required parameters not provided") + + // no service ID + reIntlOtherPrice, err = models.FetchReIntlOtherPrice(suite.DB(), address.ID, uuid.Nil, contract.ID, &moveDate) + suite.Error(err) + suite.Nil(reIntlOtherPrice) + suite.Contains(err.Error(), "error value from re_intl_other_prices - required parameters not provided") + + // no contract ID + reIntlOtherPrice, err = models.FetchReIntlOtherPrice(suite.DB(), address.ID, reService.ID, uuid.Nil, &moveDate) + suite.Error(err) + suite.Nil(reIntlOtherPrice) + suite.Contains(err.Error(), "error value from re_intl_other_prices - required parameters not provided") + + // no move date + reIntlOtherPrice, err = models.FetchReIntlOtherPrice(suite.DB(), address.ID, reService.ID, contract.ID, nil) + suite.Error(err) + suite.Nil(reIntlOtherPrice) + suite.Contains(err.Error(), "error value from re_intl_other_prices - required parameters not provided") + }) +} diff --git a/pkg/models/re_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_oconus_rate_areas.go b/pkg/models/re_oconus_rate_areas.go index 84def705f95..7003219ba9b 100644 --- a/pkg/models/re_oconus_rate_areas.go +++ b/pkg/models/re_oconus_rate_areas.go @@ -33,3 +33,14 @@ func FetchOconusRateArea(db *pop.Connection, zip string) (*OconusRateArea, error } return &reOconusRateArea, nil } + +func FetchOconusRateAreaByCityId(db *pop.Connection, usprc string) (*OconusRateArea, error) { + var reOconusRateArea OconusRateArea + err := db.Q(). + Where("re_oconus_rate_areas.us_post_region_cities_id = ?", usprc). + First(&reOconusRateArea) + if err != nil { + return nil, err + } + return &reOconusRateArea, nil +} diff --git a/pkg/models/re_oconus_rate_areas_test.go b/pkg/models/re_oconus_rate_areas_test.go new file mode 100644 index 00000000000..366bcff7daf --- /dev/null +++ b/pkg/models/re_oconus_rate_areas_test.go @@ -0,0 +1,14 @@ +package models_test + +import ( + "github.com/transcom/mymove/pkg/models" +) + +func (suite *ModelSuite) TestFetchOconusRateAreaByCityId() { + usprc, err := models.FindByZipCode(suite.AppContextForTest().DB(), "99801") + suite.NotNil(usprc) + suite.FatalNoError(err) + oconusRateArea, err := models.FetchOconusRateAreaByCityId(suite.DB(), usprc.ID.String()) + suite.NotNil(oconusRateArea) + suite.NoError(err) +} 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 bab371ac5d4..85136a4705a 100644 --- a/pkg/models/re_service.go +++ b/pkg/models/re_service.go @@ -1,12 +1,15 @@ package models import ( + "fmt" "time" "github.com/gobuffalo/pop/v6" "github.com/gobuffalo/validate/v3" "github.com/gobuffalo/validate/v3/validators" "github.com/gofrs/uuid" + + "github.com/transcom/mymove/pkg/apperror" ) // ReServiceCode is the code of service @@ -224,3 +227,16 @@ func (r *ReService) Validate(_ *pop.Connection) (*validate.Errors, error) { &validators.StringIsPresent{Field: r.Name, Name: "Name"}, ), nil } + +func FetchReServiceByCode(db *pop.Connection, code ReServiceCode) (*ReService, error) { + var reServiceCode ReServiceCode + if code != reServiceCode { + reService := ReService{} + err := db.Where("code = ?", code).First(&reService) + if err != nil { + return nil, apperror.NewQueryError("ReService", err, "") + } + return &reService, err + } + return nil, fmt.Errorf("error fetching from re_services - required code not provided") +} diff --git a/pkg/models/re_service_test.go b/pkg/models/re_service_test.go index 929677faab7..41e60c55d73 100644 --- a/pkg/models/re_service_test.go +++ b/pkg/models/re_service_test.go @@ -23,3 +23,19 @@ func (suite *ModelSuite) TestReServiceValidation() { suite.verifyValidationErrors(&emptyReService, expErrors) }) } + +func (suite *ModelSuite) TestFetchReServiceBycode() { + suite.Run("success - receive ReService when code is provided", func() { + reService, err := models.FetchReServiceByCode(suite.DB(), models.ReServiceCodeIHPK) + suite.NoError(err) + suite.NotNil(reService) + }) + + suite.Run("failure - receive error when code is not provided", func() { + var blankReServiceCode models.ReServiceCode + reService, err := models.FetchReServiceByCode(suite.DB(), blankReServiceCode) + suite.Error(err) + suite.Nil(reService) + suite.Contains(err.Error(), "error fetching from re_services - required code not provided") + }) +} diff --git a/pkg/models/roles/roles.go b/pkg/models/roles/roles.go index 10fedc99b6e..dd326a3f895 100644 --- a/pkg/models/roles/roles.go +++ b/pkg/models/roles/roles.go @@ -94,3 +94,25 @@ func FetchRolesForUser(db *pop.Connection, userID uuid.UUID) (Roles, error) { All(&roles) return roles, err } + +// Fetch like roles based on the search parameter +func FindRoles(db *pop.Connection, search string) (Roles, error) { + var rolesList Roles + + // The % operator filters out strings that are below this similarity threshold + err := db.Q().RawQuery("SET pg_trgm.similarity_threshold = 0.03").Exec() + if err != nil { + return rolesList, err + } + + sqlQuery := `select * from roles where role_name % $1` + + query := db.Q().RawQuery(sqlQuery, search) + if err := query.All(&rolesList); err != nil { + if err != nil { + return rolesList, err + } + } + + return rolesList, nil +} diff --git a/pkg/models/roles/roles_test.go b/pkg/models/roles/roles_test.go index 9ec5a0e7d86..6bcf0c07704 100644 --- a/pkg/models/roles/roles_test.go +++ b/pkg/models/roles/roles_test.go @@ -3,10 +3,12 @@ package roles_test import ( "testing" + "github.com/gofrs/uuid" "github.com/stretchr/testify/suite" "github.com/transcom/mymove/pkg/factory" "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/models/roles" m "github.com/transcom/mymove/pkg/models/roles" "github.com/transcom/mymove/pkg/testingsuite" ) @@ -66,3 +68,36 @@ func (suite *RolesSuite) TestFetchRolesForUser() { suite.NoError(err) suite.Equal(1, len(userRoles), userRoles) } + +func (suite *RolesSuite) TestFindRoles() { + id1, _ := uuid.NewV4() + role1 := roles.Role{ + ID: id1, + RoleName: "Task Invoicing Officer", + RoleType: "role1", + } + + id2, _ := uuid.NewV4() + role2 := roles.Role{ + ID: id2, + RoleName: "Task Ordering Officer", + RoleType: "role2", + } + + id3, _ := uuid.NewV4() + role3 := roles.Role{ + ID: id3, + RoleName: "Contracting Officer", + RoleType: "role3", + } + + // Create roles + rs := roles.Roles{role1, role2, role3} + err := suite.DB().Create(rs) + suite.NoError(err) + + userRoles, err := m.FindRoles(suite.DB(), "Ta") + + suite.NoError(err) + suite.GreaterOrEqual(len(userRoles), 2) +} diff --git a/pkg/models/us_post_region_city.go b/pkg/models/us_post_region_city.go index 84e9a01941c..7267134f044 100644 --- a/pkg/models/us_post_region_city.go +++ b/pkg/models/us_post_region_city.go @@ -76,3 +76,19 @@ func FindByZipCode(db *pop.Connection, zipCode string) (*UsPostRegionCity, error } return &usprc, nil } + +func FindByZipCodeAndCity(db *pop.Connection, zipCode string, city string) (*UsPostRegionCity, error) { + var usprc UsPostRegionCity + err := db.Where("uspr_zip_id = ?", zipCode). + Where("u_s_post_region_city_nm = ?", city). + First(&usprc) + if err != nil { + switch err { + case sql.ErrNoRows: + return nil, apperror.NewEventError("No UsPostRegionCity found for provided zip code "+zipCode+" and city "+city+".", err) + default: + return nil, err + } + } + return &usprc, nil +} diff --git a/pkg/models/us_post_region_city_test.go b/pkg/models/us_post_region_city_test.go index 1654d1a817e..abadd9cdfe6 100644 --- a/pkg/models/us_post_region_city_test.go +++ b/pkg/models/us_post_region_city_test.go @@ -26,3 +26,12 @@ func (suite *ModelSuite) TestFindByZipCode() { suite.NoError(err) suite.Equal("LOS ANGELES", usPostRegionCity.UsprcCountyNm) } + +func (suite *ModelSuite) TestFindByZipCodeAndCity() { + + // Attempt to gather 99677's County from the 99677 zip code and CORDOVA city + usPostRegionCity, err := models.FindByZipCodeAndCity(suite.DB(), "99677", "CORDOVA") + suite.NotNil(usPostRegionCity) + suite.NoError(err) + suite.Equal("CHUGACH", usPostRegionCity.UsprcCountyNm) +} diff --git a/pkg/models/user_test.go b/pkg/models/user_test.go index 3a6988eb161..e4dc8563ba0 100644 --- a/pkg/models/user_test.go +++ b/pkg/models/user_test.go @@ -104,23 +104,16 @@ func (suite *ModelSuite) TestFetchUserIdentity() { suite.Equal(systemAdmin.User.OktaEmail, identity.Email) suite.Nil(identity.ServiceMemberID) suite.Nil(identity.OfficeUserID) - rs := []roles.Role{{ - ID: uuid.FromStringOrNil("ed2d2cd7-d427-412a-98bb-a9b391d98d32"), + customerRole := roles.Role{ RoleType: roles.RoleTypeCustomer, - }, { - ID: uuid.FromStringOrNil("9dc423b6-33b8-493a-a59b-6a823660cb07"), + } + tooRole := roles.Role{ RoleType: roles.RoleTypeTOO, - }, } - suite.NoError(suite.DB().Create(&rs)) - customerRole := rs[0] - patOktaID := factory.MakeRandomString(20) - pat := factory.BuildUser(suite.DB(), []factory.Customization{ + pat := factory.BuildUserAndUsersRoles(suite.DB(), []factory.Customization{ { - Model: m.User{ - OktaID: patOktaID, - Active: true, - Roles: []roles.Role{customerRole}, + Model: models.User{ + Roles: []roles.Role{customerRole}, }, }, }, nil) @@ -129,15 +122,10 @@ func (suite *ModelSuite) TestFetchUserIdentity() { suite.Nil(err, "loading pat's identity") suite.NotNil(identity) suite.Equal(len(identity.Roles), 1) - - tooRole := rs[1] - billyUUID := uuid.Must(uuid.NewV4()) - billy := factory.BuildUser(suite.DB(), []factory.Customization{ + billy := factory.BuildUserAndUsersRoles(suite.DB(), []factory.Customization{ { - Model: m.User{ - OktaID: billyUUID.String(), - Active: true, - Roles: []roles.Role{tooRole}, + Model: models.User{ + Roles: []roles.Role{tooRole}, }, }, }, nil) diff --git a/pkg/notifications/move_counseled_test.go b/pkg/notifications/move_counseled_test.go index a66370c27af..90611f197e3 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/payment_request/service_param_value_lookups/distance_zip_lookup.go b/pkg/payment_request/service_param_value_lookups/distance_zip_lookup.go index 8992f0087a2..acc87eb2715 100644 --- a/pkg/payment_request/service_param_value_lookups/distance_zip_lookup.go +++ b/pkg/payment_request/service_param_value_lookups/distance_zip_lookup.go @@ -48,6 +48,8 @@ func (r DistanceZipLookup) lookup(appCtx appcontext.AppContext, keyData *Service "Distance", "PickupAddress", "DestinationAddress", + "PPMShipment.PickupAddress", + "PPMShipment.DestinationAddress", ).Find(&mtoShipment, mtoShipment.ID) if err != nil { return "", err @@ -61,20 +63,47 @@ func (r DistanceZipLookup) lookup(appCtx appcontext.AppContext, keyData *Service // if the shipment is international, we need to change the respective ZIP to use the port ZIP and not the address ZIP if isInternationalShipment { - portZip, portType, err := models.GetPortLocationInfoForShipment(appCtx.DB(), *mtoShipmentID) - if err != nil { - return "", err - } - if portZip != nil && portType != nil { - // if the port type is POEFSC this means the shipment is CONUS -> OCONUS (pickup -> port) - // if the port type is PODFSC this means the shipment is OCONUS -> CONUS (port -> destination) - if *portType == models.ReServiceCodePOEFSC.String() { - destinationZip = *portZip - } else if *portType == models.ReServiceCodePODFSC.String() { - pickupZip = *portZip + if mtoShipment.ShipmentType != models.MTOShipmentTypePPM { + portZip, portType, err := models.GetPortLocationInfoForShipment(appCtx.DB(), *mtoShipmentID) + if err != nil { + return "", err + } + if portZip != nil && portType != nil { + // if the port type is POEFSC this means the shipment is CONUS -> OCONUS (pickup -> port) + // if the port type is PODFSC this means the shipment is OCONUS -> CONUS (port -> destination) + if *portType == models.ReServiceCodePOEFSC.String() { + destinationZip = *portZip + } else if *portType == models.ReServiceCodePODFSC.String() { + pickupZip = *portZip + } + } else { + return "", apperror.NewNotFoundError(*mtoShipmentID, "looking for port ZIP for shipment") } } else { - return "", apperror.NewNotFoundError(*mtoShipmentID, "looking for port ZIP for shipment") + // PPMs get reimbursed for their travel from CONUS <-> Port ZIPs, but only for the Tacoma Port + portLocation, err := models.FetchPortLocationByCode(appCtx.DB(), "4E1") // Tacoma port code + if err != nil { + return "", fmt.Errorf("unable to find port zip with code %s", "4E1") + } + if mtoShipment.PPMShipment != nil && mtoShipment.PPMShipment.PickupAddress != nil && mtoShipment.PPMShipment.DestinationAddress != nil { + // need to figure out if we are going to go Port -> CONUS or CONUS -> Port + pickupOconus := *mtoShipment.PPMShipment.PickupAddress.IsOconus + destOconus := *mtoShipment.PPMShipment.DestinationAddress.IsOconus + if pickupOconus && !destOconus { + // Port ZIP -> CONUS ZIP + pickupZip = portLocation.UsPostRegionCity.UsprZipID + destinationZip = mtoShipment.PPMShipment.DestinationAddress.PostalCode + } else if !pickupOconus && destOconus { + // CONUS ZIP -> Port ZIP + pickupZip = mtoShipment.PPMShipment.PickupAddress.PostalCode + destinationZip = portLocation.UsPostRegionCity.UsprZipID + } else { + // OCONUS -> OCONUS mileage they don't get reimbursed for this + return strconv.Itoa(0), nil + } + } else { + return "", fmt.Errorf("missing required PPM & address information for shipment with id %s", mtoShipmentID) + } } } errorMsgForPickupZip := fmt.Sprintf("Shipment must have valid pickup zipcode. Received: %s", pickupZip) @@ -106,17 +135,17 @@ func (r DistanceZipLookup) lookup(appCtx appcontext.AppContext, keyData *Service totalDistanceMiles = distanceMiles } else if hasApprovedDestinationSIT { // from pickup zip to delivery zip - totalDistanceMiles, err = planner.ZipTransitDistance(appCtx, mtoShipment.PickupAddress.PostalCode, mtoShipment.DestinationAddress.PostalCode, false, isInternationalShipment) + totalDistanceMiles, err = planner.ZipTransitDistance(appCtx, mtoShipment.PickupAddress.PostalCode, mtoShipment.DestinationAddress.PostalCode, isInternationalShipment) if err != nil { return "", err } // from pickup zip to Destination SIT zip - distanceMiles, err = planner.ZipTransitDistance(appCtx, pickupZip, destinationZip, false, isInternationalShipment) + distanceMiles, err = planner.ZipTransitDistance(appCtx, pickupZip, destinationZip, isInternationalShipment) if err != nil { return "", err } } else { - distanceMiles, err = planner.ZipTransitDistance(appCtx, pickupZip, destinationZip, false, isInternationalShipment) + distanceMiles, err = planner.ZipTransitDistance(appCtx, pickupZip, destinationZip, isInternationalShipment) if err != nil { return "", err } diff --git a/pkg/payment_request/service_param_value_lookups/distance_zip_lookup_test.go b/pkg/payment_request/service_param_value_lookups/distance_zip_lookup_test.go index eeb37166850..8b8d866b313 100644 --- a/pkg/payment_request/service_param_value_lookups/distance_zip_lookup_test.go +++ b/pkg/payment_request/service_param_value_lookups/distance_zip_lookup_test.go @@ -125,6 +125,56 @@ func (suite *ServiceParamValueLookupsSuite) TestDistanceLookup() { suite.Equal(unit.Miles(defaultInternationalZipDistance), *mtoShipment.Distance) }) + suite.Run("Call ZipTransitDistance on international PPMs with CONUS -> Tacoma Port ZIP", func() { + miles := unit.Miles(defaultZipDistance) + + ppmShipment := factory.BuildPPMShipment(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + Distance: &miles, + ShipmentType: models.MTOShipmentTypePPM, + MarketCode: models.MarketCodeInternational, + }, + }, + { + Model: models.Address{ + StreetAddress1: "Tester Address", + City: "Tulsa", + State: "OK", + PostalCode: "74133", + }, + Type: &factory.Addresses.PickupAddress, + }, + { + Model: models.Address{ + StreetAddress1: "JBER", + City: "JBER", + State: "AK", + PostalCode: "99505", + IsOconus: models.BoolPointer(true), + }, + Type: &factory.Addresses.DeliveryAddress, + }, + }, nil) + + distanceZipLookup := DistanceZipLookup{ + PickupAddress: models.Address{PostalCode: ppmShipment.PickupAddress.PostalCode}, + DestinationAddress: models.Address{PostalCode: ppmShipment.DestinationAddress.PostalCode}, + } + + appContext := suite.AppContextForTest() + distance, err := distanceZipLookup.lookup(appContext, &ServiceItemParamKeyData{ + planner: suite.planner, + mtoShipmentID: &ppmShipment.ShipmentID, + }) + suite.NoError(err) + suite.NotNil(distance) + + planner := suite.planner.(*mocks.Planner) + // should be called with the 98421 ZIP of the Tacoma port and NOT 99505 + planner.AssertCalled(suite.T(), "ZipTransitDistance", appContext, ppmShipment.PickupAddress.PostalCode, "98421", true) + }) + suite.Run("Calculate transit zip distance with an approved Destination SIT service item", func() { testdatagen.MakeReContractYear(suite.DB(), testdatagen.Assertions{ ReContractYear: models.ReContractYear{ @@ -223,7 +273,7 @@ func (suite *ServiceParamValueLookupsSuite) TestDistanceLookup() { suite.NoError(err) planner := suite.planner.(*mocks.Planner) - planner.AssertCalled(suite.T(), "ZipTransitDistance", appContext, ppmShipment.PickupAddress.PostalCode, ppmShipment.DestinationAddress.PostalCode, false, false) + planner.AssertCalled(suite.T(), "ZipTransitDistance", appContext, ppmShipment.PickupAddress.PostalCode, ppmShipment.DestinationAddress.PostalCode, false) err = suite.DB().Reload(&ppmShipment.Shipment) suite.NoError(err) @@ -254,7 +304,7 @@ func (suite *ServiceParamValueLookupsSuite) TestDistanceLookup() { suite.NoError(err) planner := suite.planner.(*mocks.Planner) - planner.AssertCalled(suite.T(), "ZipTransitDistance", appContext, ppmShipment.PickupAddress.PostalCode, ppmShipment.DestinationAddress.PostalCode, false, false) + planner.AssertCalled(suite.T(), "ZipTransitDistance", appContext, ppmShipment.PickupAddress.PostalCode, ppmShipment.DestinationAddress.PostalCode, false) err = suite.DB().Reload(&ppmShipment.Shipment) suite.NoError(err) diff --git a/pkg/payment_request/service_param_value_lookups/distance_zip_sit_dest_lookup.go b/pkg/payment_request/service_param_value_lookups/distance_zip_sit_dest_lookup.go index 7ddc8651946..bc3e612c811 100644 --- a/pkg/payment_request/service_param_value_lookups/distance_zip_sit_dest_lookup.go +++ b/pkg/payment_request/service_param_value_lookups/distance_zip_sit_dest_lookup.go @@ -33,7 +33,7 @@ func (r DistanceZipSITDestLookup) lookup(appCtx appcontext.AppContext, keyData * if destZip == finalDestZip { distanceMiles = 1 } else { - distanceMiles, distanceErr = planner.ZipTransitDistance(appCtx, destZip, finalDestZip, false, false) + distanceMiles, distanceErr = planner.ZipTransitDistance(appCtx, destZip, finalDestZip, false) } if distanceErr != nil { diff --git a/pkg/payment_request/service_param_value_lookups/distance_zip_sit_dest_lookup_test.go b/pkg/payment_request/service_param_value_lookups/distance_zip_sit_dest_lookup_test.go index 997fcb409e7..c0e29027758 100644 --- a/pkg/payment_request/service_param_value_lookups/distance_zip_sit_dest_lookup_test.go +++ b/pkg/payment_request/service_param_value_lookups/distance_zip_sit_dest_lookup_test.go @@ -212,7 +212,6 @@ func (suite *ServiceParamValueLookupsSuite) TestDistanceZipSITDestLookup() { mock.Anything, mock.Anything, false, - false, ).Return(0, errors.New("error with ZipTransitDistance")) paramLookup, err := ServiceParamLookupInitialize(suite.AppContextForTest(), errorPlanner, mtoServiceItemSameZip3, paymentRequest.ID, paymentRequest.MoveTaskOrderID, nil) diff --git a/pkg/payment_request/service_param_value_lookups/distance_zip_sit_origin_lookup.go b/pkg/payment_request/service_param_value_lookups/distance_zip_sit_origin_lookup.go index 52178be8d65..945462236dd 100644 --- a/pkg/payment_request/service_param_value_lookups/distance_zip_sit_origin_lookup.go +++ b/pkg/payment_request/service_param_value_lookups/distance_zip_sit_origin_lookup.go @@ -39,7 +39,7 @@ func (r DistanceZipSITOriginLookup) lookup(appCtx appcontext.AppContext, keyData if originZip == actualOriginZip { distanceMiles = 1 } else { - distanceMiles, distanceErr = planner.ZipTransitDistance(appCtx, originZip, actualOriginZip, false, false) + distanceMiles, distanceErr = planner.ZipTransitDistance(appCtx, originZip, actualOriginZip, false) } if distanceErr != nil { return "", distanceErr diff --git a/pkg/payment_request/service_param_value_lookups/distance_zip_sit_origin_lookup_test.go b/pkg/payment_request/service_param_value_lookups/distance_zip_sit_origin_lookup_test.go index 6c055f7fd6b..eabf098155f 100644 --- a/pkg/payment_request/service_param_value_lookups/distance_zip_sit_origin_lookup_test.go +++ b/pkg/payment_request/service_param_value_lookups/distance_zip_sit_origin_lookup_test.go @@ -185,7 +185,6 @@ func (suite *ServiceParamValueLookupsSuite) TestDistanceZipSITOriginLookup() { mock.Anything, mock.Anything, false, - false, ).Return(0, errors.New("error with ZipTransitDistance")) paramLookup, err := ServiceParamLookupInitialize(suite.AppContextForTest(), errorPlanner, mtoServiceItemSameZip3, paymentRequest.ID, paymentRequest.MoveTaskOrderID, nil) diff --git a/pkg/payment_request/service_param_value_lookups/market_destination_lookup.go b/pkg/payment_request/service_param_value_lookups/market_destination_lookup.go new file mode 100644 index 00000000000..61c3234ed41 --- /dev/null +++ b/pkg/payment_request/service_param_value_lookups/market_destination_lookup.go @@ -0,0 +1,20 @@ +package serviceparamvaluelookups + +import ( + "github.com/transcom/mymove/pkg/appcontext" + "github.com/transcom/mymove/pkg/handlers" + "github.com/transcom/mymove/pkg/models" +) + +type MarketDestinationLookup struct { + Address models.Address +} + +func (r MarketDestinationLookup) lookup(_ appcontext.AppContext, _ *ServiceItemParamKeyData) (string, error) { + international := r.Address.IsOconus + value := handlers.FmtString(models.MarketConus.String()) + if *international { + value = handlers.FmtString(models.MarketOconus.String()) + } + return *value, nil +} diff --git a/pkg/payment_request/service_param_value_lookups/market_destination_lookup_test.go b/pkg/payment_request/service_param_value_lookups/market_destination_lookup_test.go new file mode 100644 index 00000000000..f4195887f5f --- /dev/null +++ b/pkg/payment_request/service_param_value_lookups/market_destination_lookup_test.go @@ -0,0 +1,49 @@ +package serviceparamvaluelookups + +import ( + "github.com/transcom/mymove/pkg/models" +) + +func (suite *ServiceParamValueLookupsSuite) TestMarketDestinationLookup() { + suite.Run("test conus market destination lookup", func() { + falseBool := false + conusAddress := models.Address{ + StreetAddress1: "987 Other Avenue", + StreetAddress2: models.StringPointer("P.O. Box 1234"), + StreetAddress3: models.StringPointer("c/o Another Person"), + City: "Des Moines", + State: "IA", + PostalCode: "50309", + IsOconus: &falseBool, + } + + conusLookup := MarketDestinationLookup{ + Address: conusAddress, + } + + value, err := conusLookup.lookup(nil, nil) + suite.FatalNoError(err) + suite.Equal(models.MarketConus.String(), value) + }) + + suite.Run("test oconus market destination lookup", func() { + trueBool := true + oconusAddress := models.Address{ + StreetAddress1: "987 Other Avenue", + StreetAddress2: models.StringPointer("P.O. Box 1234"), + StreetAddress3: models.StringPointer("c/o Another Person"), + City: "Des Moines", + State: "AK", + PostalCode: "99720", + IsOconus: &trueBool, + } + + oconusLookup := MarketDestinationLookup{ + Address: oconusAddress, + } + + value, err := oconusLookup.lookup(nil, nil) + suite.FatalNoError(err) + suite.Equal(models.MarketOconus.String(), value) + }) +} diff --git a/pkg/payment_request/service_param_value_lookups/market_origin_lookup.go b/pkg/payment_request/service_param_value_lookups/market_origin_lookup.go new file mode 100644 index 00000000000..cf857ebfe2b --- /dev/null +++ b/pkg/payment_request/service_param_value_lookups/market_origin_lookup.go @@ -0,0 +1,20 @@ +package serviceparamvaluelookups + +import ( + "github.com/transcom/mymove/pkg/appcontext" + "github.com/transcom/mymove/pkg/handlers" + "github.com/transcom/mymove/pkg/models" +) + +type MarketOriginLookup struct { + Address models.Address +} + +func (r MarketOriginLookup) lookup(_ appcontext.AppContext, _ *ServiceItemParamKeyData) (string, error) { + international := r.Address.IsOconus + value := handlers.FmtString(models.MarketConus.String()) + if *international { + value = handlers.FmtString(models.MarketOconus.String()) + } + return *value, nil +} diff --git a/pkg/payment_request/service_param_value_lookups/market_origin_lookup_test.go b/pkg/payment_request/service_param_value_lookups/market_origin_lookup_test.go new file mode 100644 index 00000000000..514d40f2f0f --- /dev/null +++ b/pkg/payment_request/service_param_value_lookups/market_origin_lookup_test.go @@ -0,0 +1,49 @@ +package serviceparamvaluelookups + +import ( + "github.com/transcom/mymove/pkg/models" +) + +func (suite *ServiceParamValueLookupsSuite) TestMarketOriginLookup() { + falseBool := false + suite.Run("test conus market origin lookup", func() { + conusAddress := models.Address{ + StreetAddress1: "987 Other Avenue", + StreetAddress2: models.StringPointer("P.O. Box 1234"), + StreetAddress3: models.StringPointer("c/o Another Person"), + City: "Des Moines", + State: "IA", + PostalCode: "50309", + IsOconus: &falseBool, + } + + conusLookup := MarketOriginLookup{ + Address: conusAddress, + } + + value, err := conusLookup.lookup(nil, nil) + suite.FatalNoError(err) + suite.Equal(value, models.MarketConus.String()) + }) + + suite.Run("test oconus market origin lookup", func() { + trueBool := true + oconusAddress := models.Address{ + StreetAddress1: "987 Other Avenue", + StreetAddress2: models.StringPointer("P.O. Box 1234"), + StreetAddress3: models.StringPointer("c/o Another Person"), + City: "Des Moines", + State: "AK", + PostalCode: "99720", + IsOconus: &trueBool, + } + + oconusLookup := MarketOriginLookup{ + Address: oconusAddress, + } + + value, err := oconusLookup.lookup(nil, nil) + suite.FatalNoError(err) + suite.Equal(value, models.MarketOconus.String()) + }) +} diff --git a/pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup.go b/pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup.go index f8efda9d451..37032a5ccf9 100644 --- a/pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup.go +++ b/pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup.go @@ -2,6 +2,9 @@ package serviceparamvaluelookups import ( "fmt" + "time" + + "github.com/gofrs/uuid" "github.com/transcom/mymove/pkg/appcontext" "github.com/transcom/mymove/pkg/models" @@ -16,19 +19,67 @@ type PerUnitCentsLookup struct { func (p PerUnitCentsLookup) lookup(appCtx appcontext.AppContext, s *ServiceItemParamKeyData) (string, error) { serviceID := p.ServiceItem.ReServiceID + if serviceID == uuid.Nil { + reService, err := models.FetchReServiceByCode(appCtx.DB(), p.ServiceItem.ReService.Code) + if err != nil { + return "", fmt.Errorf("error fetching ReService Code %s: %w", p.ServiceItem.ReService.Code, err) + } + serviceID = reService.ID + } contractID := s.ContractID - if p.MTOShipment.RequestedPickupDate == nil { - return "", fmt.Errorf("requested pickup date is required for shipment with id: %s", p.MTOShipment.ID) + var shipmentID uuid.UUID + var pickupAddressID uuid.UUID + var destinationAddressID uuid.UUID + var moveDate time.Time + // HHG shipment + if p.MTOShipment.ShipmentType != models.MTOShipmentTypePPM { + shipmentID = p.MTOShipment.ID + if p.MTOShipment.RequestedPickupDate != nil { + moveDate = *p.MTOShipment.RequestedPickupDate + } else { + return "", fmt.Errorf("requested pickup date is required for shipment with id: %s", shipmentID) + } + if p.MTOShipment.PickupAddressID != nil { + pickupAddressID = *p.MTOShipment.PickupAddressID + } else { + return "", fmt.Errorf("pickup address is required for shipment with id: %s", shipmentID) + } + if p.MTOShipment.DestinationAddressID != nil { + destinationAddressID = *p.MTOShipment.DestinationAddressID + } else { + return "", fmt.Errorf("destination address is required for shipment with id: %s", shipmentID) + } + } else { // PPM shipment + shipmentID = p.MTOShipment.PPMShipment.ID + if p.MTOShipment.ActualPickupDate != nil { + moveDate = *p.MTOShipment.ActualPickupDate + } else if p.MTOShipment.RequestedPickupDate != nil { + moveDate = *p.MTOShipment.RequestedPickupDate + } else { + return "", fmt.Errorf("actual move date is required for PPM shipment with id: %s", shipmentID) + } + + if p.MTOShipment.PPMShipment.PickupAddressID != nil { + pickupAddressID = *p.MTOShipment.PPMShipment.PickupAddressID + } else { + return "", fmt.Errorf("pickup address is required for PPM shipment with id: %s", shipmentID) + } + + if p.MTOShipment.PPMShipment.DestinationAddressID != nil { + destinationAddressID = *p.MTOShipment.PPMShipment.DestinationAddressID + } else { + return "", fmt.Errorf("destination address is required for PPM shipment with id: %s", shipmentID) + } } switch p.ServiceItem.ReService.Code { case models.ReServiceCodeIHPK: // IHPK: Need rate area ID for the pickup address - rateAreaID, err := models.FetchRateAreaID(appCtx.DB(), *p.MTOShipment.PickupAddressID, &serviceID, contractID) + rateAreaID, err := models.FetchRateAreaID(appCtx.DB(), pickupAddressID, &serviceID, contractID) if err != nil { - return "", fmt.Errorf("error fetching rate area id for shipment ID: %s and service ID %s: %s", p.MTOShipment.ID, serviceID, err) + return "", fmt.Errorf("error fetching rate area id for shipment ID: %s and service ID %s: %s", shipmentID, serviceID, err) } - isPeakPeriod := ghcrateengine.IsPeakPeriod(*p.MTOShipment.RequestedPickupDate) + isPeakPeriod := ghcrateengine.IsPeakPeriod(moveDate) var reIntlOtherPrice models.ReIntlOtherPrice err = appCtx.DB().Q(). Where("contract_id = ?", contractID). @@ -43,11 +94,11 @@ func (p PerUnitCentsLookup) lookup(appCtx appcontext.AppContext, s *ServiceItemP case models.ReServiceCodeIHUPK: // IHUPK: Need rate area ID for the destination address - rateAreaID, err := models.FetchRateAreaID(appCtx.DB(), *p.MTOShipment.DestinationAddressID, &serviceID, contractID) + rateAreaID, err := models.FetchRateAreaID(appCtx.DB(), destinationAddressID, &serviceID, contractID) if err != nil { - return "", fmt.Errorf("error fetching rate area id for shipment ID: %s and service ID %s: %s", p.MTOShipment.ID, serviceID, err) + return "", fmt.Errorf("error fetching rate area id for shipment ID: %s and service ID %s: %s", shipmentID, serviceID, err) } - isPeakPeriod := ghcrateengine.IsPeakPeriod(*p.MTOShipment.RequestedPickupDate) + isPeakPeriod := ghcrateengine.IsPeakPeriod(moveDate) var reIntlOtherPrice models.ReIntlOtherPrice err = appCtx.DB().Q(). Where("contract_id = ?", contractID). @@ -62,15 +113,15 @@ func (p PerUnitCentsLookup) lookup(appCtx appcontext.AppContext, s *ServiceItemP case models.ReServiceCodeISLH: // ISLH: Need rate area IDs for origin and destination - originRateAreaID, err := models.FetchRateAreaID(appCtx.DB(), *p.MTOShipment.PickupAddressID, &serviceID, contractID) + originRateAreaID, err := models.FetchRateAreaID(appCtx.DB(), pickupAddressID, &serviceID, contractID) if err != nil { - return "", fmt.Errorf("error fetching rate area id for origin address for shipment ID: %s and service ID %s: %s", p.MTOShipment.ID, serviceID, err) + return "", fmt.Errorf("error fetching rate area id for origin address for shipment ID: %s and service ID %s: %s", shipmentID, serviceID, err) } - destRateAreaID, err := models.FetchRateAreaID(appCtx.DB(), *p.MTOShipment.DestinationAddressID, &serviceID, contractID) + destRateAreaID, err := models.FetchRateAreaID(appCtx.DB(), destinationAddressID, &serviceID, contractID) if err != nil { - return "", fmt.Errorf("error fetching rate area id for destination address for shipment ID: %s and service ID %s: %s", p.MTOShipment.ID, serviceID, err) + return "", fmt.Errorf("error fetching rate area id for destination address for shipment ID: %s and service ID %s: %s", shipmentID, serviceID, err) } - isPeakPeriod := ghcrateengine.IsPeakPeriod(*p.MTOShipment.RequestedPickupDate) + isPeakPeriod := ghcrateengine.IsPeakPeriod(moveDate) var reIntlPrice models.ReIntlPrice err = appCtx.DB().Q(). Where("contract_id = ?", contractID). @@ -84,6 +135,82 @@ func (p PerUnitCentsLookup) lookup(appCtx appcontext.AppContext, s *ServiceItemP } return reIntlPrice.PerUnitCents.ToMillicents().ToCents().String(), nil + case models.ReServiceCodeIOFSIT: + // IOFSIT: Need rate area ID for origin + originRateAreaID, err := models.FetchRateAreaID(appCtx.DB(), pickupAddressID, &serviceID, contractID) + if err != nil { + return "", fmt.Errorf("error fetching rate area id for origin address for shipment ID: %s and service ID %s: %s", shipmentID, serviceID, err) + } + isPeakPeriod := ghcrateengine.IsPeakPeriod(moveDate) + var reIntlOtherPrice models.ReIntlOtherPrice + err = appCtx.DB().Q(). + Where("contract_id = ?", contractID). + Where("service_id = ?", serviceID). + Where("is_peak_period = ?", isPeakPeriod). + Where("rate_area_id = ?", originRateAreaID). + First(&reIntlOtherPrice) + if err != nil { + return "", fmt.Errorf("error fetching IOFSIT per unit cents for contractID: %s, serviceID %s, isPeakPeriod: %t, originRateAreaID: %s: %s", contractID, serviceID, isPeakPeriod, originRateAreaID, err) + } + return reIntlOtherPrice.PerUnitCents.ToMillicents().ToCents().String(), nil + + case models.ReServiceCodeIOASIT: + // IOASIT: Need rate area ID for origin + originRateAreaID, err := models.FetchRateAreaID(appCtx.DB(), pickupAddressID, &serviceID, contractID) + if err != nil { + return "", fmt.Errorf("error fetching rate area id for origin address for shipment ID: %s, service ID %s: %s", shipmentID, serviceID, err) + } + isPeakPeriod := ghcrateengine.IsPeakPeriod(moveDate) + var reIntlOtherPrice models.ReIntlOtherPrice + err = appCtx.DB().Q(). + Where("contract_id = ?", contractID). + Where("service_id = ?", serviceID). + Where("is_peak_period = ?", isPeakPeriod). + Where("rate_area_id = ?", originRateAreaID). + First(&reIntlOtherPrice) + if err != nil { + return "", fmt.Errorf("error fetching IOASIT per unit cents for contractID: %s, serviceID %s, isPeakPeriod: %t, originRateAreaID: %s: %s", contractID, serviceID, isPeakPeriod, originRateAreaID, err) + } + return reIntlOtherPrice.PerUnitCents.ToMillicents().ToCents().String(), nil + + case models.ReServiceCodeIDFSIT: + // IDFSIT: Need rate area ID for destination + destRateAreaID, err := models.FetchRateAreaID(appCtx.DB(), destinationAddressID, &serviceID, contractID) + if err != nil { + return "", fmt.Errorf("error fetching rate area id for destination address for shipment ID: %s, service ID %s: %s", shipmentID, serviceID, err) + } + isPeakPeriod := ghcrateengine.IsPeakPeriod(moveDate) + var reIntlOtherPrice models.ReIntlOtherPrice + err = appCtx.DB().Q(). + Where("contract_id = ?", contractID). + Where("service_id = ?", serviceID). + Where("is_peak_period = ?", isPeakPeriod). + Where("rate_area_id = ?", destRateAreaID). + First(&reIntlOtherPrice) + if err != nil { + return "", fmt.Errorf("error fetching IDFSIT per unit cents for contractID: %s, serviceID %s, isPeakPeriod: %t, destRateAreaID: %s: %s", contractID, serviceID, isPeakPeriod, destRateAreaID, err) + } + return reIntlOtherPrice.PerUnitCents.ToMillicents().ToCents().String(), nil + + case models.ReServiceCodeIDASIT: + // IDASIT: Need rate area ID for destination + destRateAreaID, err := models.FetchRateAreaID(appCtx.DB(), destinationAddressID, &serviceID, contractID) + if err != nil { + return "", fmt.Errorf("error fetching rate area id for destination address for shipment ID: %s and service ID %s: %s", shipmentID, serviceID, err) + } + isPeakPeriod := ghcrateengine.IsPeakPeriod(moveDate) + var reIntlOtherPrice models.ReIntlOtherPrice + err = appCtx.DB().Q(). + Where("contract_id = ?", contractID). + Where("service_id = ?", serviceID). + Where("is_peak_period = ?", isPeakPeriod). + Where("rate_area_id = ?", destRateAreaID). + First(&reIntlOtherPrice) + if err != nil { + return "", fmt.Errorf("error fetching IDASIT per unit cents for contractID: %s, serviceID %s, isPeakPeriod: %t, destRateAreaID: %s: %s", contractID, serviceID, isPeakPeriod, destRateAreaID, err) + } + return reIntlOtherPrice.PerUnitCents.ToMillicents().ToCents().String(), nil + default: return "", fmt.Errorf("unsupported service code to retrieve service item param PerUnitCents") } diff --git a/pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup_test.go b/pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup_test.go index 9937f86217b..dc69b69f888 100644 --- a/pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup_test.go +++ b/pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup_test.go @@ -30,6 +30,105 @@ func (suite *ServiceParamValueLookupsSuite) TestPerUnitCentsLookup() { } + setupTestDataPickupOCONUS := func(serviceCode models.ReServiceCode) models.Move { + testdatagen.MakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + StartDate: time.Now().Add(-24 * time.Hour), + EndDate: time.Now().Add(24 * time.Hour), + }, + }) + move := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) + address := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "JBER", + City: "Anchorage", + State: "AK", + PostalCode: "99505", + IsOconus: models.BoolPointer(true), + }, + }, + }, nil) + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + PickupAddressID: &address.ID, + MarketCode: models.MarketCodeInternational, + }, + }, + { + Model: move, + LinkOnly: true, + }, + }, nil) + mtoServiceItem = factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.ReService{ + Code: serviceCode, + }, + }, + }, nil) + + return move + } + + setupTestDataDestOCONUS := func(serviceCode models.ReServiceCode) models.Move { + testdatagen.MakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + StartDate: time.Now().Add(-24 * time.Hour), + EndDate: time.Now().Add(24 * time.Hour), + }, + }) + move := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) + address := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "JBER", + City: "Anchorage", + State: "AK", + PostalCode: "99505", + IsOconus: models.BoolPointer(true), + }, + }, + }, nil) + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + DestinationAddressID: &address.ID, + MarketCode: models.MarketCodeInternational, + }, + }, + { + Model: move, + LinkOnly: true, + }, + }, nil) + mtoServiceItem = factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.ReService{ + Code: serviceCode, + }, + }, + }, nil) + return move + } + suite.Run("success - returns perUnitCent value for IHPK", func() { setupTestData(models.ReServiceCodeIHPK) @@ -119,6 +218,115 @@ func (suite *ServiceParamValueLookupsSuite) TestPerUnitCentsLookup() { suite.Equal(perUnitCents, "1605") }) + suite.Run("success - returns perUnitCent value for IOFSIT", func() { + move := setupTestDataPickupOCONUS(models.ReServiceCodeIOFSIT) + + paramLookup, err := ServiceParamLookupInitialize(suite.AppContextForTest(), suite.planner, mtoServiceItem, uuid.Must(uuid.NewV4()), move.ID, nil) + suite.FatalNoError(err) + + perUnitCents, err := paramLookup.ServiceParamValue(suite.AppContextForTest(), key) + suite.FatalNoError(err) + suite.Equal(perUnitCents, "607") + }) + + suite.Run("success - returns perUnitCent value for IOASIT", func() { + move := setupTestDataPickupOCONUS(models.ReServiceCodeIOASIT) + + paramLookup, err := ServiceParamLookupInitialize(suite.AppContextForTest(), suite.planner, mtoServiceItem, uuid.Must(uuid.NewV4()), move.ID, nil) + suite.FatalNoError(err) + + perUnitCents, err := paramLookup.ServiceParamValue(suite.AppContextForTest(), key) + suite.FatalNoError(err) + suite.Equal(perUnitCents, "14") + }) + + suite.Run("success - returns perUnitCent value for IDFSIT", func() { + move := setupTestDataDestOCONUS(models.ReServiceCodeIDFSIT) + + paramLookup, err := ServiceParamLookupInitialize(suite.AppContextForTest(), suite.planner, mtoServiceItem, uuid.Must(uuid.NewV4()), move.ID, nil) + suite.FatalNoError(err) + + perUnitCents, err := paramLookup.ServiceParamValue(suite.AppContextForTest(), key) + suite.FatalNoError(err) + suite.Equal(perUnitCents, "607") + }) + + suite.Run("success - returns perUnitCent value for IDASIT", func() { + move := setupTestDataDestOCONUS(models.ReServiceCodeIDASIT) + + paramLookup, err := ServiceParamLookupInitialize(suite.AppContextForTest(), suite.planner, mtoServiceItem, uuid.Must(uuid.NewV4()), move.ID, nil) + suite.FatalNoError(err) + + perUnitCents, err := paramLookup.ServiceParamValue(suite.AppContextForTest(), key) + suite.FatalNoError(err) + suite.Equal(perUnitCents, "14") + }) + + suite.Run("success - returns perUnitCent value for IDASIT for a PPM", func() { + contractYear := testdatagen.MakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + StartDate: time.Now().Add(-24 * time.Hour), + EndDate: time.Now().Add(24 * time.Hour), + }, + }) + move := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) + ppm := factory.BuildPPMShipment(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + ActualPickupDate: models.TimePointer(time.Now()), + }, + }, + { + Model: models.Address{ + StreetAddress1: "Tester Address", + City: "Tulsa", + State: "OK", + PostalCode: "74133", + }, + Type: &factory.Addresses.PickupAddress, + }, + { + Model: models.Address{ + StreetAddress1: "JBER", + City: "JBER", + State: "AK", + PostalCode: "99505", + IsOconus: models.BoolPointer(true), + }, + Type: &factory.Addresses.DeliveryAddress, + }, + }, nil) + + mtoServiceItem = factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: models.ReService{ + Code: models.ReServiceCodeIDASIT, + }, + }, + { + Model: move, + LinkOnly: true, + }, + }, nil) + + _, err := ServiceParamLookupInitialize(suite.AppContextForTest(), suite.planner, mtoServiceItem, uuid.Must(uuid.NewV4()), mtoServiceItem.MoveTaskOrderID, nil) + suite.FatalNoError(err) + + perUnitCentsLookup := PerUnitCentsLookup{ + ServiceItem: mtoServiceItem, + MTOShipment: ppm.Shipment, + } + + appContext := suite.AppContextForTest() + perUnitCents, err := perUnitCentsLookup.lookup(appContext, &ServiceItemParamKeyData{ + planner: suite.planner, + mtoShipmentID: &ppm.ShipmentID, + ContractID: contractYear.ContractID, + }) + suite.NoError(err) + suite.Equal(perUnitCents, "14") + }) + suite.Run("failure - unauthorized service code", func() { setupTestData(models.ReServiceCodeDUPK) diff --git a/pkg/payment_request/service_param_value_lookups/port_zip_lookup.go b/pkg/payment_request/service_param_value_lookups/port_zip_lookup.go index 3ea8be94315..9621c56cd8a 100644 --- a/pkg/payment_request/service_param_value_lookups/port_zip_lookup.go +++ b/pkg/payment_request/service_param_value_lookups/port_zip_lookup.go @@ -15,14 +15,27 @@ type PortZipLookup struct { ServiceItem models.MTOServiceItem } -func (p PortZipLookup) lookup(appCtx appcontext.AppContext, _ *ServiceItemParamKeyData) (string, error) { +func (p PortZipLookup) lookup(appCtx appcontext.AppContext, keyData *ServiceItemParamKeyData) (string, error) { var portLocationID *uuid.UUID if p.ServiceItem.PODLocationID != nil { portLocationID = p.ServiceItem.PODLocationID } else if p.ServiceItem.POELocationID != nil { portLocationID = p.ServiceItem.POELocationID } else { - return "", fmt.Errorf("unable to find port zip for service item id: %s", p.ServiceItem.ID) + // for PPMs we need to send back the ZIP for the Tacoma Port, they are reimbursed for their CONUS <-> Port travel + shipment, err := models.FetchShipmentByID(appCtx.DB(), *keyData.mtoShipmentID) + if err != nil { + return "", fmt.Errorf("unable to find shipment with id %s", keyData.mtoShipmentID) + } + if shipment.ShipmentType == models.MTOShipmentTypePPM && shipment.MarketCode == models.MarketCodeInternational { + portLocation, err := models.FetchPortLocationByCode(appCtx.DB(), "4E1") + if err != nil { + return "", fmt.Errorf("unable to find port zip with code %s", "4E1") + } + return portLocation.UsPostRegionCity.UsprZipID, nil + } else { + return "", nil + } } var portLocation models.PortLocation err := appCtx.DB().Q(). diff --git a/pkg/payment_request/service_param_value_lookups/port_zip_lookup_test.go b/pkg/payment_request/service_param_value_lookups/port_zip_lookup_test.go index 4410ba8e198..b06533a4e1f 100644 --- a/pkg/payment_request/service_param_value_lookups/port_zip_lookup_test.go +++ b/pkg/payment_request/service_param_value_lookups/port_zip_lookup_test.go @@ -85,7 +85,76 @@ func (suite *ServiceParamValueLookupsSuite) TestPortZipLookup() { suite.Equal(portZip, port.UsPostRegionCity.UsprZipID) }) - suite.Run("failure - no port zip on service item", func() { + suite.Run("success - returns PortZip value for Port Code 4E1 for PPMs", func() { + port := factory.FetchPortLocation(suite.DB(), []factory.Customization{ + { + Model: models.Port{ + PortCode: "4E1", + }, + }, + }, nil) + + contractYear := testdatagen.MakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + StartDate: time.Now().Add(-24 * time.Hour), + EndDate: time.Now().Add(24 * time.Hour), + }, + }) + + move := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) + ppm := factory.BuildPPMShipment(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + ActualPickupDate: models.TimePointer(time.Now()), + MarketCode: models.MarketCodeInternational, + }, + }, + { + Model: models.Address{ + StreetAddress1: "Tester Address", + City: "Tulsa", + State: "OK", + PostalCode: "74133", + }, + Type: &factory.Addresses.PickupAddress, + }, + { + Model: models.Address{ + StreetAddress1: "JBER", + City: "JBER", + State: "AK", + PostalCode: "99505", + IsOconus: models.BoolPointer(true), + }, + Type: &factory.Addresses.DeliveryAddress, + }, + }, nil) + + mtoServiceItem = factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + }, nil) + + _, err := ServiceParamLookupInitialize(suite.AppContextForTest(), suite.planner, mtoServiceItem, uuid.Must(uuid.NewV4()), mtoServiceItem.MoveTaskOrderID, nil) + suite.FatalNoError(err) + + portZipLookup := PortZipLookup{ + ServiceItem: mtoServiceItem, + } + + appContext := suite.AppContextForTest() + portZip, err := portZipLookup.lookup(appContext, &ServiceItemParamKeyData{ + planner: suite.planner, + mtoShipmentID: &ppm.ShipmentID, + ContractID: contractYear.ContractID, + }) + suite.NoError(err) + suite.Equal(portZip, port.UsPostRegionCity.UsprZipID) + }) + + suite.Run("returns nothing if shipment is HHG and service item does not have port info", func() { testdatagen.MakeReContractYear(suite.DB(), testdatagen.Assertions{ ReContractYear: models.ReContractYear{ StartDate: time.Now().Add(-24 * time.Hour), @@ -108,7 +177,8 @@ func (suite *ServiceParamValueLookupsSuite) TestPortZipLookup() { paramLookup, err := ServiceParamLookupInitialize(suite.AppContextForTest(), suite.planner, mtoServiceItem, uuid.Must(uuid.NewV4()), mtoServiceItem.MoveTaskOrderID, nil) suite.FatalNoError(err) - _, err = paramLookup.ServiceParamValue(suite.AppContextForTest(), key) - suite.Error(err) + portZip, err := paramLookup.ServiceParamValue(suite.AppContextForTest(), key) + suite.NoError(err) + suite.Equal(portZip, "") }) } diff --git a/pkg/payment_request/service_param_value_lookups/service_param_value_lookups.go b/pkg/payment_request/service_param_value_lookups/service_param_value_lookups.go index d5f73e1ff2d..6815d250f28 100644 --- a/pkg/payment_request/service_param_value_lookups/service_param_value_lookups.go +++ b/pkg/payment_request/service_param_value_lookups/service_param_value_lookups.go @@ -27,7 +27,7 @@ type ServiceItemParamKeyData struct { paramCache *ServiceParamsCache } -func NewServiceItemParamKeyData(planner route.Planner, lookups map[models.ServiceItemParamName]ServiceItemParamKeyLookup, mtoServiceItem models.MTOServiceItem, mtoShipment models.MTOShipment, contractCode string) ServiceItemParamKeyData { +func NewServiceItemParamKeyData(planner route.Planner, lookups map[models.ServiceItemParamName]ServiceItemParamKeyLookup, mtoServiceItem models.MTOServiceItem, mtoShipment models.MTOShipment, contractCode string, contractID uuid.UUID) ServiceItemParamKeyData { return ServiceItemParamKeyData{ planner: planner, lookups: lookups, @@ -36,6 +36,7 @@ func NewServiceItemParamKeyData(planner route.Planner, lookups map[models.Servic mtoShipmentID: &mtoShipment.ID, MoveTaskOrderID: mtoShipment.MoveTaskOrderID, ContractCode: contractCode, + ContractID: contractID, } } @@ -88,6 +89,8 @@ var ServiceItemParamsWithLookups = []models.ServiceItemParamName{ models.ServiceItemParamNameLockedPriceCents, models.ServiceItemParamNamePerUnitCents, models.ServiceItemParamNamePortZip, + models.ServiceItemParamNameMarketDest, + models.ServiceItemParamNameMarketOrigin, } // ServiceParamLookupInitialize initializes service parameter lookup @@ -211,8 +214,11 @@ func ServiceParamLookupInitialize( mtoShipment.DestinationAddress = &destinationAddress switch mtoServiceItem.ReService.Code { - case models.ReServiceCodeDDASIT, models.ReServiceCodeDDDSIT, models.ReServiceCodeDDFSIT, models.ReServiceCodeDDSFSC, models.ReServiceCodeDOASIT, models.ReServiceCodeDOPSIT, models.ReServiceCodeDOFSIT, models.ReServiceCodeDOSFSC: - err := appCtx.DB().Load(&mtoShipment, "SITDurationUpdates") + case models.ReServiceCodeDDASIT, models.ReServiceCodeDDDSIT, models.ReServiceCodeDDFSIT, + models.ReServiceCodeDDSFSC, models.ReServiceCodeDOASIT, models.ReServiceCodeDOPSIT, + models.ReServiceCodeDOFSIT, models.ReServiceCodeDOSFSC, models.ReServiceCodeIOFSIT, + models.ReServiceCodeIOASIT, models.ReServiceCodeIDFSIT, models.ReServiceCodeIDASIT: + err := appCtx.DB().Load(&mtoShipment, "SITDurationUpdates", "PPMShipment.PickupAddress", "PPMShipment.DestinationAddress") if err != nil { return nil, err } @@ -441,6 +447,14 @@ func InitializeLookups(appCtx appcontext.AppContext, shipment models.MTOShipment ServiceItem: serviceItem, } + lookups[models.ServiceItemParamNameMarketOrigin] = MarketOriginLookup{ + Address: *shipment.PickupAddress, + } + + lookups[models.ServiceItemParamNameMarketDest] = MarketDestinationLookup{ + Address: *shipment.DestinationAddress, + } + return lookups } diff --git a/pkg/payment_request/service_param_value_lookups/service_param_value_lookups_test.go b/pkg/payment_request/service_param_value_lookups/service_param_value_lookups_test.go index 4021db685d2..34d4a025d2b 100644 --- a/pkg/payment_request/service_param_value_lookups/service_param_value_lookups_test.go +++ b/pkg/payment_request/service_param_value_lookups/service_param_value_lookups_test.go @@ -48,13 +48,11 @@ func TestServiceParamValueLookupsSuite(t *testing.T) { mock.Anything, mock.Anything, false, - false, ).Return(defaultZipDistance, nil) planner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), mock.Anything, mock.Anything, - false, true, ).Return(defaultInternationalZipDistance, nil) @@ -174,7 +172,7 @@ func (suite *ServiceParamValueLookupsSuite) setupTestMTOServiceItemWithEstimated // i don't think this function gets called for PPMs, but need to verify //paramLookup, err := ServiceParamLookupInitialize(suite.AppContextForTest(), suite.planner, mtoServiceItem, paymentRequest.ID, paymentRequest.MoveTaskOrderID, nil) //suite.FatalNoError(err) - paramLookup := NewServiceItemParamKeyData(suite.planner, serviceItemLookups, mtoServiceItem, mtoShipment, testdatagen.DefaultContractCode) + paramLookup := NewServiceItemParamKeyData(suite.planner, serviceItemLookups, mtoServiceItem, mtoShipment, testdatagen.DefaultContractCode, uuid.Nil) return mtoServiceItem, paymentRequest, ¶mLookup } diff --git a/pkg/payment_request/service_param_value_lookups/weight_billed_lookup.go b/pkg/payment_request/service_param_value_lookups/weight_billed_lookup.go index bca7555ea28..2a3e2f8bb62 100644 --- a/pkg/payment_request/service_param_value_lookups/weight_billed_lookup.go +++ b/pkg/payment_request/service_param_value_lookups/weight_billed_lookup.go @@ -200,8 +200,13 @@ func applyMinimum(code models.ReServiceCode, shipmentType models.MTOShipmentType switch shipmentType { case models.MTOShipmentTypeUnaccompaniedBaggage: switch code { - case models.ReServiceCodeIOSHUT, - models.ReServiceCodeIDSHUT: + case models.ReServiceCodeUBP, + models.ReServiceCodeIUBPK, + models.ReServiceCodeIUBUPK, + models.ReServiceCodeIOSHUT, + models.ReServiceCodeIDSHUT, + models.ReServiceCodePODFSC, + models.ReServiceCodePOEFSC: if weight < 300 { result = 300 } @@ -238,16 +243,12 @@ func applyMinimum(code models.ReServiceCode, shipmentType models.MTOShipmentType models.ReServiceCodeIDDSIT, models.ReServiceCodeIOSHUT, models.ReServiceCodeIDSHUT, - models.ReServiceCodeFSC: + models.ReServiceCodeFSC, + models.ReServiceCodePODFSC, + models.ReServiceCodePOEFSC: if weight < 500 { result = 500 } - case models.ReServiceCodeUBP, - models.ReServiceCodeIUBPK, - models.ReServiceCodeIUBUPK: - if weight < 300 { - result = 300 - } } } return fmt.Sprintf("%d", result) diff --git a/pkg/payment_request/service_param_value_lookups/weight_billed_lookup_test.go b/pkg/payment_request/service_param_value_lookups/weight_billed_lookup_test.go index 78072940ae1..74ffcd88754 100644 --- a/pkg/payment_request/service_param_value_lookups/weight_billed_lookup_test.go +++ b/pkg/payment_request/service_param_value_lookups/weight_billed_lookup_test.go @@ -95,15 +95,15 @@ func (suite *ServiceParamValueLookupsSuite) TestWeightBilledLookup() { {models.ReServiceCodeDDDSIT, unit.Pound(450), "500", models.MTOShipmentTypeHHG}, // International {models.ReServiceCodeISLH, unit.Pound(450), "500", models.MTOShipmentTypeHHG}, - {models.ReServiceCodeUBP, unit.Pound(250), "300", models.MTOShipmentTypeHHG}, - {models.ReServiceCodeISLH, unit.Pound(450), "500", models.MTOShipmentTypeHHG}, - {models.ReServiceCodeUBP, unit.Pound(250), "300", models.MTOShipmentTypeHHG}, - {models.ReServiceCodeISLH, unit.Pound(450), "500", models.MTOShipmentTypeHHG}, - {models.ReServiceCodeUBP, unit.Pound(250), "300", models.MTOShipmentTypeHHG}, {models.ReServiceCodeIHPK, unit.Pound(450), "500", models.MTOShipmentTypeHHG}, {models.ReServiceCodeIHUPK, unit.Pound(450), "500", models.MTOShipmentTypeHHG}, - {models.ReServiceCodeIUBPK, unit.Pound(250), "300", models.MTOShipmentTypeHHG}, - {models.ReServiceCodeIUBUPK, unit.Pound(250), "300", models.MTOShipmentTypeHHG}, + {models.ReServiceCodePOEFSC, unit.Pound(450), "500", models.MTOShipmentTypeHHG}, + {models.ReServiceCodePODFSC, unit.Pound(450), "500", models.MTOShipmentTypeHHG}, + {models.ReServiceCodeUBP, unit.Pound(250), "300", models.MTOShipmentTypeUnaccompaniedBaggage}, + {models.ReServiceCodeIUBPK, unit.Pound(250), "300", models.MTOShipmentTypeUnaccompaniedBaggage}, + {models.ReServiceCodeIUBUPK, unit.Pound(250), "300", models.MTOShipmentTypeUnaccompaniedBaggage}, + {models.ReServiceCodePOEFSC, unit.Pound(250), "300", models.MTOShipmentTypeUnaccompaniedBaggage}, + {models.ReServiceCodePODFSC, unit.Pound(250), "300", models.MTOShipmentTypeUnaccompaniedBaggage}, // International SIT {models.ReServiceCodeIOFSIT, unit.Pound(450), "500", models.MTOShipmentTypeHHG}, {models.ReServiceCodeIDFSIT, unit.Pound(450), "500", models.MTOShipmentTypeHHG}, diff --git a/pkg/rateengine/linehaul.go b/pkg/rateengine/linehaul.go deleted file mode 100644 index 914509499bc..00000000000 --- a/pkg/rateengine/linehaul.go +++ /dev/null @@ -1,17 +0,0 @@ -package rateengine - -import ( - "github.com/transcom/mymove/pkg/unit" -) - -// LinehaulCostComputation represents the results of a computation. -// Deprecated: This is part of the old pre-GHC rate engine. -type LinehaulCostComputation struct { - BaseLinehaul unit.Cents - OriginLinehaulFactor unit.Cents - DestinationLinehaulFactor unit.Cents - ShorthaulCharge unit.Cents - LinehaulChargeTotal unit.Cents - Mileage int - FuelSurcharge FeeAndRate -} diff --git a/pkg/rateengine/nonlinehaul.go b/pkg/rateengine/nonlinehaul.go deleted file mode 100644 index 7e3dacf59a1..00000000000 --- a/pkg/rateengine/nonlinehaul.go +++ /dev/null @@ -1,21 +0,0 @@ -package rateengine - -import ( - "github.com/transcom/mymove/pkg/unit" -) - -// FeeAndRate holds the rate lookup and calculated fee (non-discounted) -// Deprecated: This is part of the old pre-GHC rate engine. -type FeeAndRate struct { - Fee unit.Cents - Rate unit.Millicents -} - -// NonLinehaulCostComputation represents the results of a computation. -// Deprecated: This is part of the old pre-GHC rate engine. -type NonLinehaulCostComputation struct { - OriginService FeeAndRate - DestinationService FeeAndRate - Pack FeeAndRate - Unpack FeeAndRate -} diff --git a/pkg/rateengine/rateengine.go b/pkg/rateengine/rateengine.go deleted file mode 100644 index 5b0b45d7013..00000000000 --- a/pkg/rateengine/rateengine.go +++ /dev/null @@ -1,75 +0,0 @@ -package rateengine - -import ( - "time" - - "github.com/pkg/errors" - "go.uber.org/zap" - - "github.com/transcom/mymove/pkg/appcontext" - "github.com/transcom/mymove/pkg/models" - "github.com/transcom/mymove/pkg/unit" -) - -// RateEngine encapsulates the TSP rate engine process -// Deprecated: This is part of the old pre-GHC rate engine. -type RateEngine struct { - move models.Move -} - -// CostComputation represents the results of a computation. -// Deprecated: This is part of the old pre-GHC rate engine. -type CostComputation struct { - LinehaulCostComputation - NonLinehaulCostComputation - - SITFee unit.Cents - SITMax unit.Cents - GCC unit.Cents - LHDiscount unit.DiscountRate - SITDiscount unit.DiscountRate - Weight unit.Pound -} - -// CostDetail holds the costComputation and a bool that signifies if the calculation is the winning (lowest cost) computation -// Deprecated: This is part of the old pre-GHC rate engine. -type CostDetail struct { - Cost CostComputation - IsWinning bool -} - -// CostDetails is a map of CostDetail -// Deprecated: This is part of the old pre-GHC rate engine. -type CostDetails map[string]*CostDetail - -// ComputePPMMoveCosts uses zip codes to make two calculations for the price of a PPM move - once with the pickup zip and once with the current duty location zip - and returns both calcs. -// Deprecated: This is part of the old pre-GHC rate engine. -func (re *RateEngine) ComputePPMMoveCosts(appCtx appcontext.AppContext, _ unit.Pound, _ string, _ string, _ string, _ int, _ int, _ time.Time, _ int) (costDetails CostDetails, err error) { - errDeprecated := errors.New("ComputePPMMoveCosts function is deprecated") - appCtx.Logger().Error("Invoking deprecated function", zap.Error(errDeprecated)) - return nil, errDeprecated -} - -// GetWinningCostMove returns a costComputation of the winning calculation -// Deprecated: This is part of the old pre-GHC rate engine. -func GetWinningCostMove(costDetails CostDetails) CostComputation { - if costDetails["pickupLocation"].IsWinning { - return costDetails["pickupLocation"].Cost - } - return costDetails["originDutyLocation"].Cost -} - -// GetNonWinningCostMove returns a costComputation of the non-winning calculation -// Deprecated: This is part of the old pre-GHC rate engine. -func GetNonWinningCostMove(costDetails CostDetails) CostComputation { - if costDetails["pickupLocation"].IsWinning { - return costDetails["originDutyLocation"].Cost - } - return costDetails["pickupLocation"].Cost -} - -// NewRateEngine creates a new RateEngine -// Deprecated: This is part of the old pre-GHC rate engine. -func NewRateEngine(move models.Move) *RateEngine { - return &RateEngine{move: move} -} diff --git a/pkg/route/dtod_planner.go b/pkg/route/dtod_planner.go index 61cf7addb26..af426b41b69 100644 --- a/pkg/route/dtod_planner.go +++ b/pkg/route/dtod_planner.go @@ -44,7 +44,7 @@ func (p *dtodPlanner) Zip3TransitDistance(_ appcontext.AppContext, _ string, _ s } // ZipTransitDistance calculates the distance between two valid Zips -func (p *dtodPlanner) ZipTransitDistance(appCtx appcontext.AppContext, source string, destination string, useDTOD bool, isInternationalShipment bool) (int, error) { +func (p *dtodPlanner) ZipTransitDistance(appCtx appcontext.AppContext, source string, destination string, isInternationalShipment bool) (int, error) { if len(source) < 5 { source = fmt.Sprintf("%05s", source) } diff --git a/pkg/route/dtod_planner_test.go b/pkg/route/dtod_planner_test.go index 1315f531fc6..02e811e31d4 100644 --- a/pkg/route/dtod_planner_test.go +++ b/pkg/route/dtod_planner_test.go @@ -100,7 +100,7 @@ func (suite *GHCTestSuite) TestDTODZipTransitDistance() { plannerMileage := NewDTODZip5Distance(fakeUsername, fakePassword, testSoapClient, false) planner := NewDTODPlanner(plannerMileage) - distance, err := planner.ZipTransitDistance(suite.AppContextForTest(), "30907", "30301", false, false) + distance, err := planner.ZipTransitDistance(suite.AppContextForTest(), "30907", "30301", false) suite.NoError(err) suite.Equal(150, distance) }) @@ -114,7 +114,7 @@ func (suite *GHCTestSuite) TestDTODZipTransitDistance() { plannerMileage := NewDTODZip5Distance(fakeUsername, fakePassword, testSoapClient, false) planner := NewDTODPlanner(plannerMileage) - distance, err := planner.ZipTransitDistance(suite.AppContextForTest(), "30907", "30901", false, false) + distance, err := planner.ZipTransitDistance(suite.AppContextForTest(), "30907", "30901", false) suite.Error(err) suite.Equal(0, distance) }) diff --git a/pkg/route/here_planner.go b/pkg/route/here_planner.go index e7114dd1bf2..aea2b861639 100644 --- a/pkg/route/here_planner.go +++ b/pkg/route/here_planner.go @@ -219,7 +219,7 @@ func (p *herePlanner) Zip5TransitDistance(appCtx appcontext.AppContext, source s } // ZipTransitDistance calculates the distance between two valid Zip5s; it is used by the PPM flow -func (p *herePlanner) ZipTransitDistance(_ appcontext.AppContext, _ string, _ string, _ bool, _ bool) (int, error) { +func (p *herePlanner) ZipTransitDistance(_ appcontext.AppContext, _ string, _ string, _ bool) (int, error) { // This might get retired after we transition over fully to GHC. panic("implement me") diff --git a/pkg/route/hhg_planner.go b/pkg/route/hhg_planner.go index c6cf68c3007..f69cc2c0591 100644 --- a/pkg/route/hhg_planner.go +++ b/pkg/route/hhg_planner.go @@ -46,7 +46,7 @@ func (p *hhgPlanner) Zip3TransitDistance(_ appcontext.AppContext, _ string, _ st } // ZipTransitDistance calculates the distance between two valid Zips -func (p *hhgPlanner) ZipTransitDistance(appCtx appcontext.AppContext, source string, destination string, useDTOD bool, isInternationalShipment bool) (int, error) { +func (p *hhgPlanner) ZipTransitDistance(appCtx appcontext.AppContext, source string, destination string, isInternationalShipment bool) (int, error) { sourceZip5 := source if len(source) < 5 { sourceZip5 = fmt.Sprintf("%05s", source) @@ -58,18 +58,15 @@ func (p *hhgPlanner) ZipTransitDistance(appCtx appcontext.AppContext, source str sourceZip3 := sourceZip5[0:3] destZip3 := destZip5[0:3] - // we want to use DTOD for international shipments & same ZIPs for accuracy - if sourceZip3 == destZip3 || useDTOD || isInternationalShipment { + if sourceZip3 == destZip3 || isInternationalShipment { if sourceZip5 == destZip5 { return 1, nil } return p.dtodPlannerMileage.DTODZip5Distance(appCtx, source, destination) - } - - // Get reZip3s for origin and destination to compare base point cities. - // Dont throw/return errors from this. If we dont find them, we'll just use randMcNallyZip3Distance - // this only applies to domestic shipments - if !isInternationalShipment { + } else { + // Get reZip3s for origin and destination to compare base point cities. + // Dont throw/return errors from this. If we dont find them, we'll just use randMcNallyZip3Distance + // this only applies to domestic shipments sourceReZip3, sErr := models.FetchReZip3Item(appCtx.DB(), sourceZip3) if sErr != nil { appCtx.Logger().Error("Failed to fetch the reZip3 item for sourceZip3", zap.Error(sErr)) diff --git a/pkg/route/hhg_planner_test.go b/pkg/route/hhg_planner_test.go index 15c4fb03d14..a2725ef1c3a 100644 --- a/pkg/route/hhg_planner_test.go +++ b/pkg/route/hhg_planner_test.go @@ -78,7 +78,7 @@ func (suite *GHCTestSuite) TestHHGZipTransitDistance() { plannerMileage := NewDTODZip5Distance(fakeUsername, fakePassword, testSoapClient, false) planner := NewHHGPlanner(plannerMileage) - distance, err := planner.ZipTransitDistance(suite.AppContextForTest(), "30907", "30301", false, false) + distance, err := planner.ZipTransitDistance(suite.AppContextForTest(), "30907", "30301", false) suite.NoError(err) suite.Equal(149, distance) }) @@ -88,7 +88,7 @@ func (suite *GHCTestSuite) TestHHGZipTransitDistance() { plannerMileage := NewDTODZip5Distance(fakeUsername, fakePassword, testSoapClient, false) planner := NewHHGPlanner(plannerMileage) - distance, err := planner.ZipTransitDistance(suite.AppContextForTest(), "11201", "11201", false, false) + distance, err := planner.ZipTransitDistance(suite.AppContextForTest(), "11201", "11201", false) suite.NoError(err) suite.Equal(1, distance) }) @@ -105,7 +105,7 @@ func (suite *GHCTestSuite) TestHHGZipTransitDistance() { planner := NewHHGPlanner(plannerMileage) // Get distance between two zips in the same base point city - distance, err := planner.ZipTransitDistance(suite.AppContextForTest(), "33169", "33040", false, false) + distance, err := planner.ZipTransitDistance(suite.AppContextForTest(), "33169", "33040", false) suite.NoError(err) // Ensure DTOD was used for distance @@ -121,7 +121,7 @@ func (suite *GHCTestSuite) TestHHGZipTransitDistance() { plannerMileage := NewDTODZip5Distance(fakeUsername, fakePassword, testSoapClient, false) planner := NewHHGPlanner(plannerMileage) - distance, err := planner.ZipTransitDistance(suite.AppContextForTest(), "30907", "30901", false, false) + distance, err := planner.ZipTransitDistance(suite.AppContextForTest(), "30907", "30901", false) suite.Error(err) suite.Equal(0, distance) }) diff --git a/pkg/route/mocks/Planner.go b/pkg/route/mocks/Planner.go index f6ce16a0e45..ac16906e51c 100644 --- a/pkg/route/mocks/Planner.go +++ b/pkg/route/mocks/Planner.go @@ -156,9 +156,9 @@ func (_m *Planner) Zip5TransitDistanceLineHaul(appCtx appcontext.AppContext, sou return r0, r1 } -// ZipTransitDistance provides a mock function with given fields: appCtx, source, destination, useDTOD, isInternationalShipment -func (_m *Planner) ZipTransitDistance(appCtx appcontext.AppContext, source string, destination string, useDTOD bool, isInternationalShipment bool) (int, error) { - ret := _m.Called(appCtx, source, destination, useDTOD, isInternationalShipment) +// ZipTransitDistance provides a mock function with given fields: appCtx, source, destination, isInternationalShipment +func (_m *Planner) ZipTransitDistance(appCtx appcontext.AppContext, source string, destination string, isInternationalShipment bool) (int, error) { + ret := _m.Called(appCtx, source, destination, isInternationalShipment) if len(ret) == 0 { panic("no return value specified for ZipTransitDistance") @@ -166,17 +166,17 @@ func (_m *Planner) ZipTransitDistance(appCtx appcontext.AppContext, source strin var r0 int var r1 error - if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, string, bool, bool) (int, error)); ok { - return rf(appCtx, source, destination, useDTOD, isInternationalShipment) + if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, string, bool) (int, error)); ok { + return rf(appCtx, source, destination, isInternationalShipment) } - if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, string, bool, bool) int); ok { - r0 = rf(appCtx, source, destination, useDTOD, isInternationalShipment) + if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, string, bool) int); ok { + r0 = rf(appCtx, source, destination, isInternationalShipment) } else { r0 = ret.Get(0).(int) } - if rf, ok := ret.Get(1).(func(appcontext.AppContext, string, string, bool, bool) error); ok { - r1 = rf(appCtx, source, destination, useDTOD, isInternationalShipment) + if rf, ok := ret.Get(1).(func(appcontext.AppContext, string, string, bool) error); ok { + r1 = rf(appCtx, source, destination, isInternationalShipment) } else { r1 = ret.Error(1) } diff --git a/pkg/route/planner.go b/pkg/route/planner.go index 591dcec643b..860be9caddf 100644 --- a/pkg/route/planner.go +++ b/pkg/route/planner.go @@ -120,7 +120,7 @@ type Planner interface { // Zip5TransitDistanceLineHaul is used by PPM flow and checks for minimum distance restriction as PPM doesn't allow short hauls // New code should probably make the minimum checks after calling Zip5TransitDistance over using this method Zip5TransitDistanceLineHaul(appCtx appcontext.AppContext, source string, destination string) (int, error) - ZipTransitDistance(appCtx appcontext.AppContext, source string, destination string, useDTOD bool, isInternationalShipment bool) (int, error) + ZipTransitDistance(appCtx appcontext.AppContext, source string, destination string, isInternationalShipment bool) (int, error) Zip3TransitDistance(appCtx appcontext.AppContext, source string, destination string) (int, error) Zip5TransitDistance(appCtx appcontext.AppContext, source string, destination string) (int, error) } diff --git a/pkg/route/planner_test.go b/pkg/route/planner_test.go index 4a0d968c1f9..cfbd39dd69f 100644 --- a/pkg/route/planner_test.go +++ b/pkg/route/planner_test.go @@ -109,7 +109,7 @@ func (suite *PlannerFullSuite) TestZipDistance() { {zip1: "902101234", zip2: caZip, distanceMin: 30, distanceMax: 49}, } for _, ts := range tests { - distance, err := suite.planner.ZipTransitDistance(suite.AppContextForTest(), ts.zip1, ts.zip2, false, false) + distance, err := suite.planner.ZipTransitDistance(suite.AppContextForTest(), ts.zip1, ts.zip2, false) if len(ts.zip1) > 5 { suite.Error(err) suite.Equal(distance, 0) 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/clientcert/client_cert_creator_test.go b/pkg/services/clientcert/client_cert_creator_test.go index cba5e9851db..688368e599f 100644 --- a/pkg/services/clientcert/client_cert_creator_test.go +++ b/pkg/services/clientcert/client_cert_creator_test.go @@ -42,14 +42,6 @@ func (suite *ClientCertServiceSuite) TestCreateClientCert() { mockSender := setUpMockNotificationSender() user := factory.BuildUser(suite.DB(), nil, nil) - // make sure the prime role exists - factory.BuildRole(suite.DB(), []factory.Customization{ - { - Model: roles.Role{ - RoleType: roles.RoleTypePrime, - }, - }, - }, nil) clientCertInfo := models.ClientCert{ Subject: "existingUser", @@ -79,15 +71,6 @@ func (suite *ClientCertServiceSuite) TestCreateClientCert() { associator := usersroles.NewUsersRolesCreator() mockSender := setUpMockNotificationSender() - // make sure the prime role exists - factory.BuildRole(suite.DB(), []factory.Customization{ - { - Model: roles.Role{ - RoleType: roles.RoleTypePrime, - }, - }, - }, nil) - clientCertInfo := models.ClientCert{ Subject: "newUser", Sha256Digest: digest, diff --git a/pkg/services/clientcert/client_cert_role_updater_test.go b/pkg/services/clientcert/client_cert_role_updater_test.go index 88f9cc1fef7..90cef6450cd 100644 --- a/pkg/services/clientcert/client_cert_role_updater_test.go +++ b/pkg/services/clientcert/client_cert_role_updater_test.go @@ -13,14 +13,6 @@ func (suite *ClientCertServiceSuite) TestClientCertRoleUpdater() { associator := usersroles.NewUsersRolesCreator() suite.Run("Cert with allow prime, user without prime role", func() { - // make sure the prime role exists - factory.BuildRole(suite.DB(), []factory.Customization{ - { - Model: roles.Role{ - RoleType: roles.RoleTypePrime, - }, - }, - }, nil) clientCert := factory.BuildClientCert(suite.DB(), []factory.Customization{ { @@ -44,14 +36,6 @@ func (suite *ClientCertServiceSuite) TestClientCertRoleUpdater() { }) suite.Run("Cert without allow prime, user without prime role", func() { - // make sure the prime role exists - factory.BuildRole(suite.DB(), []factory.Customization{ - { - Model: roles.Role{ - RoleType: roles.RoleTypePrime, - }, - }, - }, nil) clientCert := factory.BuildClientCert(suite.DB(), []factory.Customization{ { @@ -74,14 +58,6 @@ func (suite *ClientCertServiceSuite) TestClientCertRoleUpdater() { }) suite.Run("Cert removed, user without prime role", func() { - // make sure the prime role exists - factory.BuildRole(suite.DB(), []factory.Customization{ - { - Model: roles.Role{ - RoleType: roles.RoleTypePrime, - }, - }, - }, nil) user := factory.BuildUser(suite.DB(), nil, nil) @@ -98,14 +74,6 @@ func (suite *ClientCertServiceSuite) TestClientCertRoleUpdater() { }) suite.Run("Cert removed, user with prime role and another cert", func() { - // make sure the prime role exists - factory.BuildRole(suite.DB(), []factory.Customization{ - { - Model: roles.Role{ - RoleType: roles.RoleTypePrime, - }, - }, - }, nil) clientCert := factory.BuildClientCert(suite.DB(), []factory.Customization{ { @@ -140,14 +108,6 @@ func (suite *ClientCertServiceSuite) TestClientCertRoleUpdater() { }) suite.Run("Cert removed, user with prime role and another cert without prime", func() { - // make sure the prime role exists - factory.BuildRole(suite.DB(), []factory.Customization{ - { - Model: roles.Role{ - RoleType: roles.RoleTypePrime, - }, - }, - }, nil) clientCert := factory.BuildClientCert(suite.DB(), []factory.Customization{ { diff --git a/pkg/services/ghc_rate_engine.go b/pkg/services/ghc_rate_engine.go index 2247e3d7426..d1c7b923118 100644 --- a/pkg/services/ghc_rate_engine.go +++ b/pkg/services/ghc_rate_engine.go @@ -85,7 +85,7 @@ type DomesticOriginShuttlingPricer interface { ParamsPricer } -// DomesticDestinationShuttlingPricer prices the domestic origin shuttling service for a GHC Move +// DomesticDestinationShuttlingPricer prices the domestic destination shuttling service for a GHC Move // //go:generate mockery --name DomesticDestinationShuttlingPricer type DomesticDestinationShuttlingPricer interface { @@ -93,6 +93,22 @@ type DomesticDestinationShuttlingPricer interface { ParamsPricer } +// InternationalDestinationShuttlingPricer prices the international destination shuttling service for a GHC Move +// +//go:generate mockery --name InternationalDestinationShuttlingPricer +type InternationalDestinationShuttlingPricer interface { + Price(appCtx appcontext.AppContext, contractCode string, requestedPickupDate time.Time, weight unit.Pound, market models.Market) (unit.Cents, PricingDisplayParams, error) + ParamsPricer +} + +// InternationalOriginShuttlingPricer prices the international origin shuttling service for a GHC Move +// +//go:generate mockery --name InternationalOriginShuttlingPricer +type InternationalOriginShuttlingPricer interface { + Price(appCtx appcontext.AppContext, contractCode string, requestedPickupDate time.Time, weight unit.Pound, market models.Market) (unit.Cents, PricingDisplayParams, error) + ParamsPricer +} + // DomesticCratingPricer prices the domestic crating service for a GHC Move // //go:generate mockery --name DomesticCratingPricer @@ -264,3 +280,35 @@ type IntlPortFuelSurchargePricer interface { Price(appCtx appcontext.AppContext, actualPickupDate time.Time, distance unit.Miles, weight unit.Pound, fscWeightBasedDistanceMultiplier float64, eiaFuelPrice unit.Millicents) (unit.Cents, PricingDisplayParams, error) ParamsPricer } + +// IntlOriginFirstDaySITPricer prices international origin first day SIT +// +//go:generate mockery --name IntlOriginFirstDaySITPricer +type IntlOriginFirstDaySITPricer interface { + Price(appCtx appcontext.AppContext, contractCode string, requestedPickupDate time.Time, weight unit.Pound, perUnitCents int) (unit.Cents, PricingDisplayParams, error) + ParamsPricer +} + +// IntlOriginAdditionalDaySITPricer prices international origin additional days of SIT +// +//go:generate mockery --name IntlOriginAdditionalDaySITPricer +type IntlOriginAdditionalDaySITPricer interface { + Price(appCtx appcontext.AppContext, contractCode string, requestedPickupDate time.Time, numberOfDaysInSIT int, weight unit.Pound, perUnitCents int) (unit.Cents, PricingDisplayParams, error) + ParamsPricer +} + +// IntlDestinationFirstDaySITPricer prices international destination first day SIT +// +//go:generate mockery --name IntlDestinationFirstDaySITPricer +type IntlDestinationFirstDaySITPricer interface { + Price(appCtx appcontext.AppContext, contractCode string, requestedPickupDate time.Time, weight unit.Pound, perUnitCents int) (unit.Cents, PricingDisplayParams, error) + ParamsPricer +} + +// IntlDestinationAdditionalDaySITPricer prices international destination additional days of SIT +// +//go:generate mockery --name IntlDestinationAdditionalDaySITPricer +type IntlDestinationAdditionalDaySITPricer interface { + Price(appCtx appcontext.AppContext, contractCode string, requestedPickupDate time.Time, numberOfDaysInSIT int, weight unit.Pound, perUnitCents int) (unit.Cents, PricingDisplayParams, error) + ParamsPricer +} diff --git a/pkg/services/ghcrateengine/ghc_rate_engine_service_test.go b/pkg/services/ghcrateengine/ghc_rate_engine_service_test.go index 8fcd52d9d34..c5bcbebac4c 100644 --- a/pkg/services/ghcrateengine/ghc_rate_engine_service_test.go +++ b/pkg/services/ghcrateengine/ghc_rate_engine_service_test.go @@ -84,6 +84,27 @@ func (suite *GHCRateEngineServiceSuite) setupDomesticAccessorialPrice(code model suite.MustSave(&accessorialPrice) } +func (suite *GHCRateEngineServiceSuite) setupInternationalAccessorialPrice(code models.ReServiceCode, market models.Market, perUnitCents unit.Cents, contractYearName string, escalationCompounded float64) { + contractYear := testdatagen.MakeReContractYear(suite.DB(), + testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + Name: contractYearName, + EscalationCompounded: escalationCompounded, + }, + }) + + service := factory.FetchReServiceByCode(suite.DB(), code) + + accessorialPrice := models.ReIntlAccessorialPrice{ + ContractID: contractYear.Contract.ID, + ServiceID: service.ID, + PerUnitCents: perUnitCents, + Market: market, + } + + suite.MustSave(&accessorialPrice) +} + func (suite *GHCRateEngineServiceSuite) setupDomesticServiceAreaPrice(code models.ReServiceCode, serviceAreaCode string, isPeakPeriod bool, priceCents unit.Cents, contractYearName string, escalationCompounded float64) { contractYear := testdatagen.FetchOrMakeReContractYear(suite.DB(), testdatagen.Assertions{ diff --git a/pkg/services/ghcrateengine/international_destination_shuttling_pricer.go b/pkg/services/ghcrateengine/international_destination_shuttling_pricer.go new file mode 100644 index 00000000000..226672230d2 --- /dev/null +++ b/pkg/services/ghcrateengine/international_destination_shuttling_pricer.go @@ -0,0 +1,48 @@ +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 internationalDestinationShuttlingPricer struct { +} + +// NewInternationalDestinationShuttlingPricer creates a new pricer for international destination shuttle +func NewInternationalDestinationShuttlingPricer() services.InternationalDestinationShuttlingPricer { + return &internationalDestinationShuttlingPricer{} +} + +// Price determines the price for international destination shuttle +func (p internationalDestinationShuttlingPricer) Price(appCtx appcontext.AppContext, contractCode string, referenceDate time.Time, weight unit.Pound, market models.Market) (unit.Cents, services.PricingDisplayParams, error) { + return priceInternationalShuttling(appCtx, models.ReServiceCodeIDSHUT, contractCode, referenceDate, weight, market) +} + +// PriceUsingParams determines the price for international destination shuttle given PaymentServiceItemParams +func (p internationalDestinationShuttlingPricer) PriceUsingParams(appCtx appcontext.AppContext, params models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error) { + contractCode, err := getParamString(params, models.ServiceItemParamNameContractCode) + if err != nil { + return unit.Cents(0), nil, err + } + + referenceDate, err := getParamTime(params, models.ServiceItemParamNameReferenceDate) + if err != nil { + return unit.Cents(0), nil, err + } + + market, err := getParamMarket(params, models.ServiceItemParamNameMarketDest) + if err != nil { + return unit.Cents(0), nil, err + } + + weightBilled, err := getParamInt(params, models.ServiceItemParamNameWeightBilled) + if err != nil { + return unit.Cents(0), nil, err + } + + return p.Price(appCtx, contractCode, referenceDate, unit.Pound(weightBilled), market) +} diff --git a/pkg/services/ghcrateengine/international_destination_shuttling_pricer_test.go b/pkg/services/ghcrateengine/international_destination_shuttling_pricer_test.go new file mode 100644 index 00000000000..cae0ce84a3f --- /dev/null +++ b/pkg/services/ghcrateengine/international_destination_shuttling_pricer_test.go @@ -0,0 +1,109 @@ +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 ( + idshutTestMarket = models.Market("O") + idshutTestBasePriceCents = unit.Cents(353) + idshutTestEscalationCompounded = 1.125 + idshutTestWeight = unit.Pound(4000) + idshutTestPriceCents = unit.Cents(15880) +) + +var idshutTestRequestedPickupDate = time.Date(testdatagen.TestYear, time.June, 5, 7, 33, 11, 456, time.UTC) + +func (suite *GHCRateEngineServiceSuite) TestInternationalDestinationShuttlingPricer() { + pricer := NewInternationalDestinationShuttlingPricer() + + suite.Run("success using PaymentServiceItemParams", func() { + suite.setupInternationalAccessorialPrice(models.ReServiceCodeIDSHUT, ioshutTestMarket, idshutTestBasePriceCents, testdatagen.DefaultContractCode, idshutTestEscalationCompounded) + + paymentServiceItem := suite.setupInternationalDestinationShuttlingServiceItem() + priceCents, displayParams, err := pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.NoError(err) + suite.Equal(idshutTestPriceCents, priceCents) + + expectedParams := services.PricingDisplayParams{ + {Key: models.ServiceItemParamNameContractYearName, Value: testdatagen.DefaultContractCode}, + {Key: models.ServiceItemParamNameEscalationCompounded, Value: FormatEscalation(idshutTestEscalationCompounded)}, + {Key: models.ServiceItemParamNamePriceRateOrFactor, Value: FormatCents(idshutTestBasePriceCents)}, + } + suite.validatePricerCreatedParams(expectedParams, displayParams) + }) + + suite.Run("success without PaymentServiceItemParams", func() { + suite.setupInternationalAccessorialPrice(models.ReServiceCodeIDSHUT, ioshutTestMarket, idshutTestBasePriceCents, testdatagen.DefaultContractCode, idshutTestEscalationCompounded) + + priceCents, _, err := pricer.Price(suite.AppContextForTest(), testdatagen.DefaultContractCode, idshutTestRequestedPickupDate, idshutTestWeight, idshutTestMarket) + suite.NoError(err) + suite.Equal(idshutTestPriceCents, priceCents) + }) + + suite.Run("PriceUsingParams but sending empty params", func() { + suite.setupInternationalAccessorialPrice(models.ReServiceCodeIDSHUT, ioshutTestMarket, idshutTestBasePriceCents, testdatagen.DefaultContractCode, idshutTestEscalationCompounded) + _, _, err := pricer.PriceUsingParams(suite.AppContextForTest(), models.PaymentServiceItemParams{}) + suite.Error(err) + }) + + suite.Run("invalid weight", func() { + suite.setupInternationalAccessorialPrice(models.ReServiceCodeIDSHUT, ioshutTestMarket, idshutTestBasePriceCents, testdatagen.DefaultContractCode, idshutTestEscalationCompounded) + badWeight := unit.Pound(250) + _, _, err := pricer.Price(suite.AppContextForTest(), testdatagen.DefaultContractCode, idshutTestRequestedPickupDate, badWeight, idshutTestMarket) + suite.Error(err) + suite.Contains(err.Error(), "Weight must be a minimum of 500") + }) + + suite.Run("not finding a rate record", func() { + suite.setupInternationalAccessorialPrice(models.ReServiceCodeIDSHUT, ioshutTestMarket, idshutTestBasePriceCents, testdatagen.DefaultContractCode, idshutTestEscalationCompounded) + _, _, err := pricer.Price(suite.AppContextForTest(), "BOGUS", idshutTestRequestedPickupDate, idshutTestWeight, idshutTestMarket) + 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.ReServiceCodeIDSHUT, ioshutTestMarket, idshutTestBasePriceCents, testdatagen.DefaultContractCode, idshutTestEscalationCompounded) + twoYearsLaterPickupDate := idshutTestRequestedPickupDate.AddDate(2, 0, 0) + _, _, err := pricer.Price(suite.AppContextForTest(), testdatagen.DefaultContractCode, twoYearsLaterPickupDate, idshutTestWeight, idshutTestMarket) + suite.Error(err) + + suite.Contains(err.Error(), "could not calculate escalated price") + }) +} + +func (suite *GHCRateEngineServiceSuite) setupInternationalDestinationShuttlingServiceItem() models.PaymentServiceItem { + return factory.BuildPaymentServiceItemWithParams( + suite.DB(), + models.ReServiceCodeIDSHUT, + []factory.CreatePaymentServiceItemParams{ + { + Key: models.ServiceItemParamNameContractCode, + KeyType: models.ServiceItemParamTypeString, + Value: factory.DefaultContractCode, + }, + { + Key: models.ServiceItemParamNameReferenceDate, + KeyType: models.ServiceItemParamTypeDate, + Value: idshutTestRequestedPickupDate.Format(DateParamFormat), + }, + { + Key: models.ServiceItemParamNameMarketDest, + KeyType: models.ServiceItemParamTypeString, + Value: idshutTestMarket.String(), + }, + { + Key: models.ServiceItemParamNameWeightBilled, + KeyType: models.ServiceItemParamTypeInteger, + Value: fmt.Sprintf("%d", int(idshutTestWeight)), + }, + }, nil, nil, + ) +} diff --git a/pkg/services/ghcrateengine/international_origin_shuttling_pricer.go b/pkg/services/ghcrateengine/international_origin_shuttling_pricer.go new file mode 100644 index 00000000000..6c499d3a41b --- /dev/null +++ b/pkg/services/ghcrateengine/international_origin_shuttling_pricer.go @@ -0,0 +1,48 @@ +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 internationalOriginShuttlingPricer struct { +} + +// NewInternationalOriginShuttlingPricer creates a new pricer for international origin shuttle +func NewInternationalOriginShuttlingPricer() services.InternationalOriginShuttlingPricer { + return &internationalOriginShuttlingPricer{} +} + +// Price determines the price for international origin shuttle +func (p internationalOriginShuttlingPricer) Price(appCtx appcontext.AppContext, contractCode string, referenceDate time.Time, weight unit.Pound, market models.Market) (unit.Cents, services.PricingDisplayParams, error) { + return priceInternationalShuttling(appCtx, models.ReServiceCodeIOSHUT, contractCode, referenceDate, weight, market) +} + +// PriceUsingParams determines the price for international origin shuttle given PaymentServiceItemParams +func (p internationalOriginShuttlingPricer) PriceUsingParams(appCtx appcontext.AppContext, params models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error) { + contractCode, err := getParamString(params, models.ServiceItemParamNameContractCode) + if err != nil { + return unit.Cents(0), nil, err + } + + referenceDate, err := getParamTime(params, models.ServiceItemParamNameReferenceDate) + if err != nil { + return unit.Cents(0), nil, err + } + + market, err := getParamMarket(params, models.ServiceItemParamNameMarketOrigin) + if err != nil { + return unit.Cents(0), nil, err + } + + weightBilled, err := getParamInt(params, models.ServiceItemParamNameWeightBilled) + if err != nil { + return unit.Cents(0), nil, err + } + + return p.Price(appCtx, contractCode, referenceDate, unit.Pound(weightBilled), market) +} diff --git a/pkg/services/ghcrateengine/international_origin_shuttling_pricer_test.go b/pkg/services/ghcrateengine/international_origin_shuttling_pricer_test.go new file mode 100644 index 00000000000..0f18a1ed70a --- /dev/null +++ b/pkg/services/ghcrateengine/international_origin_shuttling_pricer_test.go @@ -0,0 +1,110 @@ +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 ( + ioshutTestMarket = "O" + ioshutTestBasePriceCents = unit.Cents(353) + ioshutTestEscalationCompounded = 1.125 + ioshutTestWeight = unit.Pound(4000) + ioshutTestPriceCents = unit.Cents(15880) +) + +var ioshutTestRequestedPickupDate = time.Date(testdatagen.TestYear, time.June, 5, 7, 33, 11, 456, time.UTC) + +func (suite *GHCRateEngineServiceSuite) TestInternationalOriginShuttlingPricer() { + pricer := NewInternationalOriginShuttlingPricer() + + suite.Run("success using PaymentServiceItemParams", func() { + suite.setupInternationalAccessorialPrice(models.ReServiceCodeIOSHUT, ioshutTestMarket, ioshutTestBasePriceCents, testdatagen.DefaultContractCode, ioshutTestEscalationCompounded) + + paymentServiceItem := suite.setupInternationalOriginShuttlingServiceItem() + priceCents, displayParams, err := pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.NoError(err) + suite.Equal(ioshutTestPriceCents, priceCents) + + expectedParams := services.PricingDisplayParams{ + {Key: models.ServiceItemParamNameContractYearName, Value: testdatagen.DefaultContractCode}, + {Key: models.ServiceItemParamNameEscalationCompounded, Value: FormatEscalation(ioshutTestEscalationCompounded)}, + {Key: models.ServiceItemParamNamePriceRateOrFactor, Value: FormatCents(ioshutTestBasePriceCents)}, + } + suite.validatePricerCreatedParams(expectedParams, displayParams) + }) + + suite.Run("success without PaymentServiceItemParams", func() { + suite.setupInternationalAccessorialPrice(models.ReServiceCodeIOSHUT, ioshutTestMarket, ioshutTestBasePriceCents, testdatagen.DefaultContractCode, ioshutTestEscalationCompounded) + + priceCents, _, err := pricer.Price(suite.AppContextForTest(), testdatagen.DefaultContractCode, ioshutTestRequestedPickupDate, ioshutTestWeight, ioshutTestMarket) + suite.NoError(err) + suite.Equal(ioshutTestPriceCents, priceCents) + }) + + suite.Run("PriceUsingParams but sending empty params", func() { + suite.setupInternationalAccessorialPrice(models.ReServiceCodeIOSHUT, ioshutTestMarket, ioshutTestBasePriceCents, testdatagen.DefaultContractCode, ioshutTestEscalationCompounded) + _, _, err := pricer.PriceUsingParams(suite.AppContextForTest(), models.PaymentServiceItemParams{}) + suite.Error(err) + }) + + suite.Run("invalid weight", func() { + suite.setupInternationalAccessorialPrice(models.ReServiceCodeIOSHUT, ioshutTestMarket, ioshutTestBasePriceCents, testdatagen.DefaultContractCode, ioshutTestEscalationCompounded) + badWeight := unit.Pound(250) + _, _, err := pricer.Price(suite.AppContextForTest(), testdatagen.DefaultContractCode, ioshutTestRequestedPickupDate, badWeight, ioshutTestMarket) + suite.Error(err) + suite.Contains(err.Error(), "Weight must be a minimum of 500") + }) + + suite.Run("not finding a rate record", func() { + suite.setupInternationalAccessorialPrice(models.ReServiceCodeIOSHUT, ioshutTestMarket, ioshutTestBasePriceCents, testdatagen.DefaultContractCode, ioshutTestEscalationCompounded) + _, _, err := pricer.Price(suite.AppContextForTest(), "BOGUS", ioshutTestRequestedPickupDate, ioshutTestWeight, ioshutTestMarket) + 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.ReServiceCodeIOSHUT, ioshutTestMarket, ioshutTestBasePriceCents, testdatagen.DefaultContractCode, ioshutTestEscalationCompounded) + twoYearsLaterPickupDate := ioshutTestRequestedPickupDate.AddDate(2, 0, 0) + _, _, err := pricer.Price(suite.AppContextForTest(), testdatagen.DefaultContractCode, twoYearsLaterPickupDate, ioshutTestWeight, ioshutTestMarket) + suite.Error(err) + + suite.Contains(err.Error(), "could not calculate escalated price") + + }) +} + +func (suite *GHCRateEngineServiceSuite) setupInternationalOriginShuttlingServiceItem() models.PaymentServiceItem { + return factory.BuildPaymentServiceItemWithParams( + suite.DB(), + models.ReServiceCodeIOSHUT, + []factory.CreatePaymentServiceItemParams{ + { + Key: models.ServiceItemParamNameContractCode, + KeyType: models.ServiceItemParamTypeString, + Value: factory.DefaultContractCode, + }, + { + Key: models.ServiceItemParamNameReferenceDate, + KeyType: models.ServiceItemParamTypeDate, + Value: ioshutTestRequestedPickupDate.Format(DateParamFormat), + }, + { + Key: models.ServiceItemParamNameMarketOrigin, + KeyType: models.ServiceItemParamTypeString, + Value: ioshutTestMarket, + }, + { + Key: models.ServiceItemParamNameWeightBilled, + KeyType: models.ServiceItemParamTypeInteger, + Value: fmt.Sprintf("%d", int(ioshutTestWeight)), + }, + }, nil, nil, + ) +} diff --git a/pkg/services/ghcrateengine/intl_destination_additional_days_sit_pricer.go b/pkg/services/ghcrateengine/intl_destination_additional_days_sit_pricer.go new file mode 100644 index 00000000000..ed57a28b57c --- /dev/null +++ b/pkg/services/ghcrateengine/intl_destination_additional_days_sit_pricer.go @@ -0,0 +1,50 @@ +package ghcrateengine + +import ( + "time" + + "github.com/transcom/mymove/pkg/appcontext" + "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/unit" +) + +type intlDestinationAdditionalDaySITPricer struct { +} + +func NewIntlDestinationAdditionalDaySITPricer() services.IntlDestinationAdditionalDaySITPricer { + return &intlDestinationAdditionalDaySITPricer{} +} + +func (p intlDestinationAdditionalDaySITPricer) Price(appCtx appcontext.AppContext, contractCode string, referenceDate time.Time, numberOfDaysInSIT int, weight unit.Pound, perUnitCents int) (unit.Cents, services.PricingDisplayParams, error) { + return priceIntlAdditionalDaySIT(appCtx, models.ReServiceCodeIDASIT, contractCode, referenceDate, numberOfDaysInSIT, weight, perUnitCents) +} + +func (p intlDestinationAdditionalDaySITPricer) PriceUsingParams(appCtx appcontext.AppContext, params models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error) { + contractCode, err := getParamString(params, models.ServiceItemParamNameContractCode) + if err != nil { + return unit.Cents(0), nil, err + } + + referenceDate, err := getParamTime(params, models.ServiceItemParamNameReferenceDate) + if err != nil { + return unit.Cents(0), nil, err + } + + numberOfDaysInSIT, err := getParamInt(params, models.ServiceItemParamNameNumberDaysSIT) + if err != nil { + return unit.Cents(0), nil, err + } + + perUnitCents, err := getParamInt(params, models.ServiceItemParamNamePerUnitCents) + if err != nil { + return unit.Cents(0), nil, err + } + + weightBilled, err := getParamInt(params, models.ServiceItemParamNameWeightBilled) + if err != nil { + return unit.Cents(0), nil, err + } + + return p.Price(appCtx, contractCode, referenceDate, numberOfDaysInSIT, unit.Pound(weightBilled), perUnitCents) +} diff --git a/pkg/services/ghcrateengine/intl_destination_additional_days_sit_pricer_test.go b/pkg/services/ghcrateengine/intl_destination_additional_days_sit_pricer_test.go new file mode 100644 index 00000000000..76a40753891 --- /dev/null +++ b/pkg/services/ghcrateengine/intl_destination_additional_days_sit_pricer_test.go @@ -0,0 +1,127 @@ +package ghcrateengine + +import ( + "fmt" + "strconv" + "time" + + "github.com/transcom/mymove/pkg/factory" + "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/testdatagen" + "github.com/transcom/mymove/pkg/unit" +) + +const ( + idasitTestContractYearName = "Base Period Year 1" + idasitTestPerUnitCents = unit.Cents(15000) + idasitTestTotalCost = unit.Cents(1575000) + idasitTestIsPeakPeriod = true + idasitTestEscalationCompounded = 1.0000 + idasitTestWeight = unit.Pound(2100) + idasitTestPriceCents = unit.Cents(500) + idasitNumerDaysInSIT = 5 +) + +var idasitTestRequestedPickupDate = time.Date(testdatagen.TestYear, peakStart.month, peakStart.day, 0, 0, 0, 0, time.UTC) + +func (suite *GHCRateEngineServiceSuite) TestIntlDestinationAdditionalDaySITPricer() { + pricer := NewIntlDestinationAdditionalDaySITPricer() + + suite.Run("success using PaymentServiceItemParams", func() { + paymentServiceItem := suite.setupIntlDestinationAdditionalDayServiceItem() + + totalCost, displayParams, err := pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.NoError(err) + suite.Equal(idasitTestTotalCost, totalCost) + + expectedParams := services.PricingDisplayParams{ + {Key: models.ServiceItemParamNameContractYearName, Value: idasitTestContractYearName}, + {Key: models.ServiceItemParamNamePriceRateOrFactor, Value: FormatCents(idasitTestPerUnitCents)}, + {Key: models.ServiceItemParamNameIsPeak, Value: FormatBool(idasitTestIsPeakPeriod)}, + {Key: models.ServiceItemParamNameEscalationCompounded, Value: FormatEscalation(idasitTestEscalationCompounded)}, + } + suite.validatePricerCreatedParams(expectedParams, displayParams) + }) + + suite.Run("invalid parameters to PriceUsingParams", func() { + paymentServiceItem := suite.setupIntlDestinationAdditionalDayServiceItem() + + // WeightBilled + paymentServiceItem.PaymentServiceItemParams[4].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean + _, _, err := pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.Error(err) + suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to an int", models.ServiceItemParamNameWeightBilled)) + + // PerUnitCents + paymentServiceItem.PaymentServiceItemParams[3].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean + _, _, err = pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.Error(err) + suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to an int", models.ServiceItemParamNamePerUnitCents)) + + // NumberDaysSIT + paymentServiceItem.PaymentServiceItemParams[2].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean + _, _, err = pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.Error(err) + suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to an int", models.ServiceItemParamNameNumberDaysSIT)) + + // ReferenceDate + paymentServiceItem.PaymentServiceItemParams[1].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean + _, _, err = pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.Error(err) + suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to a time", models.ServiceItemParamNameReferenceDate)) + + // ContractCode + paymentServiceItem.PaymentServiceItemParams[0].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean + _, _, err = pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.Error(err) + suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to a string", models.ServiceItemParamNameContractCode)) + }) +} + +func (suite *GHCRateEngineServiceSuite) setupIntlDestinationAdditionalDayServiceItem() models.PaymentServiceItem { + contract := testdatagen.FetchOrMakeReContract(suite.DB(), testdatagen.Assertions{}) + startDate := time.Date(2018, time.January, 1, 12, 0, 0, 0, time.UTC) + endDate := time.Date(2018, time.December, 31, 12, 0, 0, 0, time.UTC) + testdatagen.FetchOrMakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + Contract: contract, + ContractID: contract.ID, + StartDate: startDate, + EndDate: endDate, + Escalation: 1.0, + EscalationCompounded: 1.0, + }, + }) + return factory.BuildPaymentServiceItemWithParams( + suite.DB(), + models.ReServiceCodeIDASIT, + []factory.CreatePaymentServiceItemParams{ + { + Key: models.ServiceItemParamNameContractCode, + KeyType: models.ServiceItemParamTypeString, + Value: contract.Code, + }, + { + Key: models.ServiceItemParamNameReferenceDate, + KeyType: models.ServiceItemParamTypeDate, + Value: idasitTestRequestedPickupDate.Format(DateParamFormat), + }, + { + Key: models.ServiceItemParamNameNumberDaysSIT, + KeyType: models.ServiceItemParamTypeInteger, + Value: fmt.Sprintf("%d", int(idasitNumerDaysInSIT)), + }, + { + Key: models.ServiceItemParamNamePerUnitCents, + KeyType: models.ServiceItemParamTypeInteger, + Value: fmt.Sprintf("%d", int(idasitTestPerUnitCents)), + }, + { + Key: models.ServiceItemParamNameWeightBilled, + KeyType: models.ServiceItemParamTypeInteger, + Value: strconv.Itoa(idasitTestWeight.Int()), + }, + }, nil, nil, + ) +} diff --git a/pkg/services/ghcrateengine/intl_destination_first_day_sit_pricer.go b/pkg/services/ghcrateengine/intl_destination_first_day_sit_pricer.go new file mode 100644 index 00000000000..eb1354ebbd0 --- /dev/null +++ b/pkg/services/ghcrateengine/intl_destination_first_day_sit_pricer.go @@ -0,0 +1,45 @@ +package ghcrateengine + +import ( + "time" + + "github.com/transcom/mymove/pkg/appcontext" + "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/unit" +) + +type intlDestinationFirstDaySITPricer struct { +} + +func NewIntlDestinationFirstDaySITPricer() services.IntlDestinationFirstDaySITPricer { + return &intlDestinationFirstDaySITPricer{} +} + +func (p intlDestinationFirstDaySITPricer) Price(appCtx appcontext.AppContext, contractCode string, referenceDate time.Time, weight unit.Pound, perUnitCents int) (unit.Cents, services.PricingDisplayParams, error) { + return priceIntlFirstDaySIT(appCtx, models.ReServiceCodeIDFSIT, contractCode, referenceDate, weight, perUnitCents) +} + +func (p intlDestinationFirstDaySITPricer) PriceUsingParams(appCtx appcontext.AppContext, params models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error) { + contractCode, err := getParamString(params, models.ServiceItemParamNameContractCode) + if err != nil { + return unit.Cents(0), nil, err + } + + referenceDate, err := getParamTime(params, models.ServiceItemParamNameReferenceDate) + if err != nil { + return unit.Cents(0), nil, err + } + + perUnitCents, err := getParamInt(params, models.ServiceItemParamNamePerUnitCents) + if err != nil { + return unit.Cents(0), nil, err + } + + weightBilled, err := getParamInt(params, models.ServiceItemParamNameWeightBilled) + if err != nil { + return unit.Cents(0), nil, err + } + + return p.Price(appCtx, contractCode, referenceDate, unit.Pound(weightBilled), perUnitCents) +} diff --git a/pkg/services/ghcrateengine/intl_destination_first_day_sit_pricer_test.go b/pkg/services/ghcrateengine/intl_destination_first_day_sit_pricer_test.go new file mode 100644 index 00000000000..fbaebec2df0 --- /dev/null +++ b/pkg/services/ghcrateengine/intl_destination_first_day_sit_pricer_test.go @@ -0,0 +1,116 @@ +package ghcrateengine + +import ( + "fmt" + "strconv" + "time" + + "github.com/transcom/mymove/pkg/factory" + "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/testdatagen" + "github.com/transcom/mymove/pkg/unit" +) + +const ( + idfsitTestContractYearName = "Base Period Year 1" + idfsitTestPerUnitCents = unit.Cents(15000) + idfsitTestTotalCost = unit.Cents(315000) + idfsitTestIsPeakPeriod = true + idfsitTestEscalationCompounded = 1.0000 + idfsitTestWeight = unit.Pound(2100) + idfsitTestPriceCents = unit.Cents(500) + idfsitNumerDaysInSIT = 5 +) + +var idfsitTestRequestedPickupDate = time.Date(testdatagen.TestYear, peakStart.month, peakStart.day, 0, 0, 0, 0, time.UTC) + +func (suite *GHCRateEngineServiceSuite) TestIntlDestinationFirstDaySITPricer() { + pricer := NewIntlDestinationFirstDaySITPricer() + + suite.Run("success using PaymentServiceItemParams", func() { + paymentServiceItem := suite.setupIntlDestinationFirstDayServiceItem() + + totalCost, displayParams, err := pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.NoError(err) + suite.Equal(idfsitTestTotalCost, totalCost) + + expectedParams := services.PricingDisplayParams{ + {Key: models.ServiceItemParamNameContractYearName, Value: idfsitTestContractYearName}, + {Key: models.ServiceItemParamNamePriceRateOrFactor, Value: FormatCents(idfsitTestPerUnitCents)}, + {Key: models.ServiceItemParamNameIsPeak, Value: FormatBool(idfsitTestIsPeakPeriod)}, + {Key: models.ServiceItemParamNameEscalationCompounded, Value: FormatEscalation(idfsitTestEscalationCompounded)}, + } + suite.validatePricerCreatedParams(expectedParams, displayParams) + }) + + suite.Run("invalid parameters to PriceUsingParams", func() { + paymentServiceItem := suite.setupIntlDestinationFirstDayServiceItem() + + // WeightBilled + paymentServiceItem.PaymentServiceItemParams[3].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean + _, _, err := pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.Error(err) + suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to an int", models.ServiceItemParamNameWeightBilled)) + + // PerUnitCents + paymentServiceItem.PaymentServiceItemParams[2].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean + _, _, err = pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.Error(err) + suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to an int", models.ServiceItemParamNamePerUnitCents)) + + // ReferenceDate + paymentServiceItem.PaymentServiceItemParams[1].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean + _, _, err = pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.Error(err) + suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to a time", models.ServiceItemParamNameReferenceDate)) + + // ContractCode + paymentServiceItem.PaymentServiceItemParams[0].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean + _, _, err = pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.Error(err) + suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to a string", models.ServiceItemParamNameContractCode)) + }) +} + +func (suite *GHCRateEngineServiceSuite) setupIntlDestinationFirstDayServiceItem() models.PaymentServiceItem { + contract := testdatagen.FetchOrMakeReContract(suite.DB(), testdatagen.Assertions{}) + startDate := time.Date(2018, time.January, 1, 12, 0, 0, 0, time.UTC) + endDate := time.Date(2018, time.December, 31, 12, 0, 0, 0, time.UTC) + testdatagen.FetchOrMakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + Contract: contract, + ContractID: contract.ID, + StartDate: startDate, + EndDate: endDate, + Escalation: 1.0, + EscalationCompounded: 1.0, + }, + }) + return factory.BuildPaymentServiceItemWithParams( + suite.DB(), + models.ReServiceCodeIDFSIT, + []factory.CreatePaymentServiceItemParams{ + { + Key: models.ServiceItemParamNameContractCode, + KeyType: models.ServiceItemParamTypeString, + Value: contract.Code, + }, + { + Key: models.ServiceItemParamNameReferenceDate, + KeyType: models.ServiceItemParamTypeDate, + Value: idfsitTestRequestedPickupDate.Format(DateParamFormat), + }, + { + Key: models.ServiceItemParamNamePerUnitCents, + KeyType: models.ServiceItemParamTypeInteger, + Value: fmt.Sprintf("%d", int(idfsitTestPerUnitCents)), + }, + { + Key: models.ServiceItemParamNameWeightBilled, + KeyType: models.ServiceItemParamTypeInteger, + Value: strconv.Itoa(idfsitTestWeight.Int()), + }, + }, nil, nil, + ) +} diff --git a/pkg/services/ghcrateengine/intl_origin_additional_days_sit_pricer.go b/pkg/services/ghcrateengine/intl_origin_additional_days_sit_pricer.go new file mode 100644 index 00000000000..e9b4dc22478 --- /dev/null +++ b/pkg/services/ghcrateengine/intl_origin_additional_days_sit_pricer.go @@ -0,0 +1,50 @@ +package ghcrateengine + +import ( + "time" + + "github.com/transcom/mymove/pkg/appcontext" + "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/unit" +) + +type intlOriginAdditionalDaySITPricer struct { +} + +func NewIntlOriginAdditionalDaySITPricer() services.IntlOriginAdditionalDaySITPricer { + return &intlOriginAdditionalDaySITPricer{} +} + +func (p intlOriginAdditionalDaySITPricer) Price(appCtx appcontext.AppContext, contractCode string, referenceDate time.Time, numberOfDaysInSIT int, weight unit.Pound, perUnitCents int) (unit.Cents, services.PricingDisplayParams, error) { + return priceIntlAdditionalDaySIT(appCtx, models.ReServiceCodeIOASIT, contractCode, referenceDate, numberOfDaysInSIT, weight, perUnitCents) +} + +func (p intlOriginAdditionalDaySITPricer) PriceUsingParams(appCtx appcontext.AppContext, params models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error) { + contractCode, err := getParamString(params, models.ServiceItemParamNameContractCode) + if err != nil { + return unit.Cents(0), nil, err + } + + referenceDate, err := getParamTime(params, models.ServiceItemParamNameReferenceDate) + if err != nil { + return unit.Cents(0), nil, err + } + + numberOfDaysInSIT, err := getParamInt(params, models.ServiceItemParamNameNumberDaysSIT) + if err != nil { + return unit.Cents(0), nil, err + } + + perUnitCents, err := getParamInt(params, models.ServiceItemParamNamePerUnitCents) + if err != nil { + return unit.Cents(0), nil, err + } + + weightBilled, err := getParamInt(params, models.ServiceItemParamNameWeightBilled) + if err != nil { + return unit.Cents(0), nil, err + } + + return p.Price(appCtx, contractCode, referenceDate, numberOfDaysInSIT, unit.Pound(weightBilled), perUnitCents) +} diff --git a/pkg/services/ghcrateengine/intl_origin_additional_days_sit_pricer_test.go b/pkg/services/ghcrateengine/intl_origin_additional_days_sit_pricer_test.go new file mode 100644 index 00000000000..87686d5110a --- /dev/null +++ b/pkg/services/ghcrateengine/intl_origin_additional_days_sit_pricer_test.go @@ -0,0 +1,127 @@ +package ghcrateengine + +import ( + "fmt" + "strconv" + "time" + + "github.com/transcom/mymove/pkg/factory" + "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/testdatagen" + "github.com/transcom/mymove/pkg/unit" +) + +const ( + ioasitTestContractYearName = "Base Period Year 1" + ioasitTestPerUnitCents = unit.Cents(15000) + ioasitTestTotalCost = unit.Cents(1575000) + ioasitTestIsPeakPeriod = true + ioasitTestEscalationCompounded = 1.0000 + ioasitTestWeight = unit.Pound(2100) + ioasitTestPriceCents = unit.Cents(500) + ioasitNumerDaysInSIT = 5 +) + +var ioasitTestRequestedPickupDate = time.Date(testdatagen.TestYear, peakStart.month, peakStart.day, 0, 0, 0, 0, time.UTC) + +func (suite *GHCRateEngineServiceSuite) TestIntlOriginAdditionalDaySITPricer() { + pricer := NewIntlOriginAdditionalDaySITPricer() + + suite.Run("success using PaymentServiceItemParams", func() { + paymentServiceItem := suite.setupIntlOriginAdditionalDayServiceItem() + + totalCost, displayParams, err := pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.NoError(err) + suite.Equal(ioasitTestTotalCost, totalCost) + + expectedParams := services.PricingDisplayParams{ + {Key: models.ServiceItemParamNameContractYearName, Value: ioasitTestContractYearName}, + {Key: models.ServiceItemParamNamePriceRateOrFactor, Value: FormatCents(ioasitTestPerUnitCents)}, + {Key: models.ServiceItemParamNameIsPeak, Value: FormatBool(ioasitTestIsPeakPeriod)}, + {Key: models.ServiceItemParamNameEscalationCompounded, Value: FormatEscalation(ioasitTestEscalationCompounded)}, + } + suite.validatePricerCreatedParams(expectedParams, displayParams) + }) + + suite.Run("invalid parameters to PriceUsingParams", func() { + paymentServiceItem := suite.setupIntlOriginAdditionalDayServiceItem() + + // WeightBilled + paymentServiceItem.PaymentServiceItemParams[4].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean + _, _, err := pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.Error(err) + suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to an int", models.ServiceItemParamNameWeightBilled)) + + // PerUnitCents + paymentServiceItem.PaymentServiceItemParams[3].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean + _, _, err = pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.Error(err) + suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to an int", models.ServiceItemParamNamePerUnitCents)) + + // NumberDaysSIT + paymentServiceItem.PaymentServiceItemParams[2].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean + _, _, err = pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.Error(err) + suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to an int", models.ServiceItemParamNameNumberDaysSIT)) + + // ReferenceDate + paymentServiceItem.PaymentServiceItemParams[1].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean + _, _, err = pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.Error(err) + suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to a time", models.ServiceItemParamNameReferenceDate)) + + // ContractCode + paymentServiceItem.PaymentServiceItemParams[0].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean + _, _, err = pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.Error(err) + suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to a string", models.ServiceItemParamNameContractCode)) + }) +} + +func (suite *GHCRateEngineServiceSuite) setupIntlOriginAdditionalDayServiceItem() models.PaymentServiceItem { + contract := testdatagen.FetchOrMakeReContract(suite.DB(), testdatagen.Assertions{}) + startDate := time.Date(2018, time.January, 1, 12, 0, 0, 0, time.UTC) + endDate := time.Date(2018, time.December, 31, 12, 0, 0, 0, time.UTC) + testdatagen.FetchOrMakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + Contract: contract, + ContractID: contract.ID, + StartDate: startDate, + EndDate: endDate, + Escalation: 1.0, + EscalationCompounded: 1.0, + }, + }) + return factory.BuildPaymentServiceItemWithParams( + suite.DB(), + models.ReServiceCodeIOASIT, + []factory.CreatePaymentServiceItemParams{ + { + Key: models.ServiceItemParamNameContractCode, + KeyType: models.ServiceItemParamTypeString, + Value: contract.Code, + }, + { + Key: models.ServiceItemParamNameReferenceDate, + KeyType: models.ServiceItemParamTypeDate, + Value: ioasitTestRequestedPickupDate.Format(DateParamFormat), + }, + { + Key: models.ServiceItemParamNameNumberDaysSIT, + KeyType: models.ServiceItemParamTypeInteger, + Value: fmt.Sprintf("%d", int(ioasitNumerDaysInSIT)), + }, + { + Key: models.ServiceItemParamNamePerUnitCents, + KeyType: models.ServiceItemParamTypeInteger, + Value: fmt.Sprintf("%d", int(ioasitTestPerUnitCents)), + }, + { + Key: models.ServiceItemParamNameWeightBilled, + KeyType: models.ServiceItemParamTypeInteger, + Value: strconv.Itoa(ioasitTestWeight.Int()), + }, + }, nil, nil, + ) +} diff --git a/pkg/services/ghcrateengine/intl_origin_first_day_sit_pricer.go b/pkg/services/ghcrateengine/intl_origin_first_day_sit_pricer.go new file mode 100644 index 00000000000..2070d13835b --- /dev/null +++ b/pkg/services/ghcrateengine/intl_origin_first_day_sit_pricer.go @@ -0,0 +1,45 @@ +package ghcrateengine + +import ( + "time" + + "github.com/transcom/mymove/pkg/appcontext" + "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/unit" +) + +type intlOriginFirstDaySITPricer struct { +} + +func NewIntlOriginFirstDaySITPricer() services.IntlOriginFirstDaySITPricer { + return &intlOriginFirstDaySITPricer{} +} + +func (p intlOriginFirstDaySITPricer) Price(appCtx appcontext.AppContext, contractCode string, referenceDate time.Time, weight unit.Pound, perUnitCents int) (unit.Cents, services.PricingDisplayParams, error) { + return priceIntlFirstDaySIT(appCtx, models.ReServiceCodeIOFSIT, contractCode, referenceDate, weight, perUnitCents) +} + +func (p intlOriginFirstDaySITPricer) PriceUsingParams(appCtx appcontext.AppContext, params models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error) { + contractCode, err := getParamString(params, models.ServiceItemParamNameContractCode) + if err != nil { + return unit.Cents(0), nil, err + } + + referenceDate, err := getParamTime(params, models.ServiceItemParamNameReferenceDate) + if err != nil { + return unit.Cents(0), nil, err + } + + perUnitCents, err := getParamInt(params, models.ServiceItemParamNamePerUnitCents) + if err != nil { + return unit.Cents(0), nil, err + } + + weightBilled, err := getParamInt(params, models.ServiceItemParamNameWeightBilled) + if err != nil { + return unit.Cents(0), nil, err + } + + return p.Price(appCtx, contractCode, referenceDate, unit.Pound(weightBilled), perUnitCents) +} diff --git a/pkg/services/ghcrateengine/intl_origin_first_day_sit_pricer_test.go b/pkg/services/ghcrateengine/intl_origin_first_day_sit_pricer_test.go new file mode 100644 index 00000000000..ae6d9069fc4 --- /dev/null +++ b/pkg/services/ghcrateengine/intl_origin_first_day_sit_pricer_test.go @@ -0,0 +1,116 @@ +package ghcrateengine + +import ( + "fmt" + "strconv" + "time" + + "github.com/transcom/mymove/pkg/factory" + "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/testdatagen" + "github.com/transcom/mymove/pkg/unit" +) + +const ( + iofsitTestContractYearName = "Base Period Year 1" + iofsitTestPerUnitCents = unit.Cents(15000) + iofsitTestTotalCost = unit.Cents(315000) + iofsitTestIsPeakPeriod = true + iofsitTestEscalationCompounded = 1.0000 + iofsitTestWeight = unit.Pound(2100) + iofsitTestPriceCents = unit.Cents(500) + iofsitNumerDaysInSIT = 5 +) + +var iofsitTestRequestedPickupDate = time.Date(testdatagen.TestYear, peakStart.month, peakStart.day, 0, 0, 0, 0, time.UTC) + +func (suite *GHCRateEngineServiceSuite) TestIntlOriginFirstDaySITPricer() { + pricer := NewIntlOriginFirstDaySITPricer() + + suite.Run("success using PaymentServiceItemParams", func() { + paymentServiceItem := suite.setupIntlOriginFirstDayServiceItem() + + totalCost, displayParams, err := pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.NoError(err) + suite.Equal(iofsitTestTotalCost, totalCost) + + expectedParams := services.PricingDisplayParams{ + {Key: models.ServiceItemParamNameContractYearName, Value: iofsitTestContractYearName}, + {Key: models.ServiceItemParamNamePriceRateOrFactor, Value: FormatCents(iofsitTestPerUnitCents)}, + {Key: models.ServiceItemParamNameIsPeak, Value: FormatBool(iofsitTestIsPeakPeriod)}, + {Key: models.ServiceItemParamNameEscalationCompounded, Value: FormatEscalation(iofsitTestEscalationCompounded)}, + } + suite.validatePricerCreatedParams(expectedParams, displayParams) + }) + + suite.Run("invalid parameters to PriceUsingParams", func() { + paymentServiceItem := suite.setupIntlOriginFirstDayServiceItem() + + // WeightBilled + paymentServiceItem.PaymentServiceItemParams[3].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean + _, _, err := pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.Error(err) + suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to an int", models.ServiceItemParamNameWeightBilled)) + + // PerUnitCents + paymentServiceItem.PaymentServiceItemParams[2].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean + _, _, err = pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.Error(err) + suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to an int", models.ServiceItemParamNamePerUnitCents)) + + // ReferenceDate + paymentServiceItem.PaymentServiceItemParams[1].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean + _, _, err = pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.Error(err) + suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to a time", models.ServiceItemParamNameReferenceDate)) + + // ContractCode + paymentServiceItem.PaymentServiceItemParams[0].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean + _, _, err = pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.Error(err) + suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to a string", models.ServiceItemParamNameContractCode)) + }) +} + +func (suite *GHCRateEngineServiceSuite) setupIntlOriginFirstDayServiceItem() models.PaymentServiceItem { + contract := testdatagen.FetchOrMakeReContract(suite.DB(), testdatagen.Assertions{}) + startDate := time.Date(2018, time.January, 1, 12, 0, 0, 0, time.UTC) + endDate := time.Date(2018, time.December, 31, 12, 0, 0, 0, time.UTC) + testdatagen.FetchOrMakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + Contract: contract, + ContractID: contract.ID, + StartDate: startDate, + EndDate: endDate, + Escalation: 1.0, + EscalationCompounded: 1.0, + }, + }) + return factory.BuildPaymentServiceItemWithParams( + suite.DB(), + models.ReServiceCodeIOFSIT, + []factory.CreatePaymentServiceItemParams{ + { + Key: models.ServiceItemParamNameContractCode, + KeyType: models.ServiceItemParamTypeString, + Value: contract.Code, + }, + { + Key: models.ServiceItemParamNameReferenceDate, + KeyType: models.ServiceItemParamTypeDate, + Value: iofsitTestRequestedPickupDate.Format(DateParamFormat), + }, + { + Key: models.ServiceItemParamNamePerUnitCents, + KeyType: models.ServiceItemParamTypeInteger, + Value: fmt.Sprintf("%d", int(iofsitTestPerUnitCents)), + }, + { + Key: models.ServiceItemParamNameWeightBilled, + KeyType: models.ServiceItemParamTypeInteger, + Value: strconv.Itoa(iofsitTestWeight.Int()), + }, + }, nil, nil, + ) +} diff --git a/pkg/services/ghcrateengine/param_convert.go b/pkg/services/ghcrateengine/param_convert.go index 16ecf8d9fe6..553794946be 100644 --- a/pkg/services/ghcrateengine/param_convert.go +++ b/pkg/services/ghcrateengine/param_convert.go @@ -66,6 +66,20 @@ func getParamString(params models.PaymentServiceItemParams, name models.ServiceI return paymentServiceItemParam.Value, nil } +func getParamMarket(params models.PaymentServiceItemParams, name models.ServiceItemParamName) (models.Market, error) { + paymentServiceItemParam := getPaymentServiceItemParam(params, name) + if paymentServiceItemParam == nil { + return "", fmt.Errorf("could not find param with key %s", name) + } + + paramType := paymentServiceItemParam.ServiceItemParamKey.Type + if paramType != models.ServiceItemParamTypeString { + return "", fmt.Errorf("trying to convert %s to a string, but param is of type %s", name, paramType) + } + + return models.Market(paymentServiceItemParam.Value), nil +} + func getParamTime(params models.PaymentServiceItemParams, name models.ServiceItemParamName) (time.Time, error) { paymentServiceItemParam := getPaymentServiceItemParam(params, name) if paymentServiceItemParam == nil { diff --git a/pkg/services/ghcrateengine/param_convert_test.go b/pkg/services/ghcrateengine/param_convert_test.go index f4a5ddc5867..9da3a9deb33 100644 --- a/pkg/services/ghcrateengine/param_convert_test.go +++ b/pkg/services/ghcrateengine/param_convert_test.go @@ -145,6 +145,26 @@ func (suite *GHCRateEngineServiceSuite) Test_getParamTime() { }) } +func (suite *GHCRateEngineServiceSuite) Test_getParamMarket() { + + params := models.PaymentServiceItemParams{ + setupParamConvertParam(models.ServiceItemParamNameMarketOrigin, models.ServiceItemParamTypeString, models.MarketConus.String()), + setupParamConvertParam(models.ServiceItemParamNameMarketDest, models.ServiceItemParamTypeString, models.MarketOconus.String()), + } + + suite.Run("finding expected market origin", func() { + value, err := getParamMarket(params, models.ServiceItemParamNameMarketOrigin) + suite.NoError(err) + suite.Equal(models.MarketConus, value) + }) + + suite.Run("finding expected market destination", func() { + value, err := getParamMarket(params, models.ServiceItemParamNameMarketDest) + suite.NoError(err) + suite.Equal(models.MarketOconus, value) + }) +} + func setupParamConvertParam(key models.ServiceItemParamName, keyType models.ServiceItemParamType, value string) models.PaymentServiceItemParam { return models.PaymentServiceItemParam{ Value: value, diff --git a/pkg/services/ghcrateengine/pricer_helpers_intl.go b/pkg/services/ghcrateengine/pricer_helpers_intl.go index 924dad55537..9754f552b3d 100644 --- a/pkg/services/ghcrateengine/pricer_helpers_intl.go +++ b/pkg/services/ghcrateengine/pricer_helpers_intl.go @@ -13,6 +13,56 @@ import ( "github.com/transcom/mymove/pkg/unit" ) +func priceInternationalShuttling(appCtx appcontext.AppContext, shuttlingCode models.ReServiceCode, contractCode string, referenceDate time.Time, weight unit.Pound, market models.Market) (unit.Cents, services.PricingDisplayParams, error) { + if shuttlingCode != models.ReServiceCodeIOSHUT && shuttlingCode != models.ReServiceCodeIDSHUT { + return 0, nil, fmt.Errorf("unsupported international shuttling code of %s", shuttlingCode) + } + // 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 weight < minInternationalWeight { + return 0, nil, fmt.Errorf("Weight must be a minimum of %d", minInternationalWeight) + } + if market == "" { + return 0, nil, errors.New("Market is required") + } + + // look up rate for international accessorial price + internationalAccessorialPrice, err := fetchInternationalAccessorialPrice(appCtx, contractCode, shuttlingCode, 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 * weight.ToCWTFloat64() + totalCost := unit.Cents(math.Round(escalatedPrice)) + + params := services.PricingDisplayParams{ + { + Key: models.ServiceItemParamNamePriceRateOrFactor, + Value: FormatCents(internationalAccessorialPrice.PerUnitCents), + }, + { + Key: models.ServiceItemParamNameContractYearName, + Value: contractYear.Name, + }, + { + Key: models.ServiceItemParamNameEscalationCompounded, + Value: FormatEscalation(contractYear.EscalationCompounded), + }, + } + return totalCost, params, nil +} + func priceIntlPackUnpack(appCtx appcontext.AppContext, packUnpackCode models.ReServiceCode, contractCode string, referenceDate time.Time, weight unit.Pound, perUnitCents int) (unit.Cents, services.PricingDisplayParams, error) { if packUnpackCode != models.ReServiceCodeIHPK && packUnpackCode != models.ReServiceCodeIHUPK { return 0, nil, fmt.Errorf("unsupported pack/unpack code of %s", packUnpackCode) @@ -64,3 +114,111 @@ func priceIntlPackUnpack(appCtx appcontext.AppContext, packUnpackCode models.ReS return totalCost, displayParams, nil } + +func priceIntlFirstDaySIT(appCtx appcontext.AppContext, firstDaySITCode models.ReServiceCode, contractCode string, referenceDate time.Time, weight unit.Pound, perUnitCents int) (unit.Cents, services.PricingDisplayParams, error) { + if firstDaySITCode != models.ReServiceCodeIOFSIT && firstDaySITCode != models.ReServiceCodeIDFSIT { + return 0, nil, fmt.Errorf("unsupported first day SIT code of %s", firstDaySITCode) + } + if len(contractCode) == 0 { + return 0, nil, errors.New("ContractCode is required") + } + if referenceDate.IsZero() { + return 0, nil, errors.New("ReferenceDate is required") + } + if perUnitCents == 0 { + return 0, nil, errors.New("PerUnitCents is required") + } + + isPeakPeriod := IsPeakPeriod(referenceDate) + + contract, err := fetchContractByContractCode(appCtx, contractCode) + if err != nil { + return 0, nil, fmt.Errorf("could not find contract with code: %s: %w", contractCode, err) + } + + basePrice := float64(perUnitCents) + escalatedPrice, contractYear, err := escalatePriceForContractYear(appCtx, contract.ID, referenceDate, false, basePrice) + if err != nil { + return 0, nil, fmt.Errorf("could not calculate escalated price: %w", err) + } + + escalatedPrice = escalatedPrice * weight.ToCWTFloat64() + totalCost := unit.Cents(math.Round(escalatedPrice)) + + displayParams := services.PricingDisplayParams{ + { + Key: models.ServiceItemParamNameContractYearName, + Value: contractYear.Name, + }, + { + Key: models.ServiceItemParamNamePriceRateOrFactor, + Value: FormatCents(unit.Cents(perUnitCents)), + }, + { + Key: models.ServiceItemParamNameIsPeak, + Value: FormatBool(isPeakPeriod), + }, + { + Key: models.ServiceItemParamNameEscalationCompounded, + Value: FormatEscalation(contractYear.EscalationCompounded), + }, + } + + return totalCost, displayParams, nil +} + +func priceIntlAdditionalDaySIT(appCtx appcontext.AppContext, additionalDaySITCode models.ReServiceCode, contractCode string, referenceDate time.Time, numberOfDaysInSIT int, weight unit.Pound, perUnitCents int) (unit.Cents, services.PricingDisplayParams, error) { + if additionalDaySITCode != models.ReServiceCodeIOASIT && additionalDaySITCode != models.ReServiceCodeIDASIT { + return 0, nil, fmt.Errorf("unsupported additional day SIT code of %s", additionalDaySITCode) + } + if len(contractCode) == 0 { + return 0, nil, errors.New("ContractCode is required") + } + if referenceDate.IsZero() { + return 0, nil, errors.New("ReferenceDate is required") + } + if numberOfDaysInSIT == 0 { + return 0, nil, errors.New("NumberDaysSIT is required") + } + if perUnitCents == 0 { + return 0, nil, errors.New("PerUnitCents is required") + } + + isPeakPeriod := IsPeakPeriod(referenceDate) + + contract, err := fetchContractByContractCode(appCtx, contractCode) + if err != nil { + return 0, nil, fmt.Errorf("could not find contract with code: %s: %w", contractCode, err) + } + + basePrice := float64(perUnitCents) + escalatedPrice, contractYear, err := escalatePriceForContractYear(appCtx, contract.ID, referenceDate, false, basePrice) + if err != nil { + return 0, nil, fmt.Errorf("could not calculate escalated price: %w", err) + } + + escalatedPrice = escalatedPrice * weight.ToCWTFloat64() + totalForNumberOfDaysPrice := escalatedPrice * float64(numberOfDaysInSIT) + totalCost := unit.Cents(math.Round(totalForNumberOfDaysPrice)) + + displayParams := services.PricingDisplayParams{ + { + Key: models.ServiceItemParamNameContractYearName, + Value: contractYear.Name, + }, + { + Key: models.ServiceItemParamNamePriceRateOrFactor, + Value: FormatCents(unit.Cents(perUnitCents)), + }, + { + Key: models.ServiceItemParamNameIsPeak, + Value: FormatBool(isPeakPeriod), + }, + { + Key: models.ServiceItemParamNameEscalationCompounded, + Value: FormatEscalation(contractYear.EscalationCompounded), + }, + } + + return totalCost, displayParams, nil +} diff --git a/pkg/services/ghcrateengine/pricer_helpers_intl_test.go b/pkg/services/ghcrateengine/pricer_helpers_intl_test.go index 19539e4c976..14e3d6c8618 100644 --- a/pkg/services/ghcrateengine/pricer_helpers_intl_test.go +++ b/pkg/services/ghcrateengine/pricer_helpers_intl_test.go @@ -6,8 +6,63 @@ import ( "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/services" "github.com/transcom/mymove/pkg/testdatagen" + "github.com/transcom/mymove/pkg/unit" ) +func (suite *GHCRateEngineServiceSuite) Test_priceInternationalShuttling() { + suite.Run("origin golden path", func() { + suite.setupInternationalAccessorialPrice(models.ReServiceCodeIOSHUT, ioshutTestMarket, ioshutTestBasePriceCents, testdatagen.DefaultContractCode, ioshutTestEscalationCompounded) + + priceCents, displayParams, err := priceInternationalShuttling(suite.AppContextForTest(), models.ReServiceCodeIOSHUT, testdatagen.DefaultContractCode, ioshutTestRequestedPickupDate, ioshutTestWeight, ioshutTestMarket) + suite.NoError(err) + suite.Equal(ioshutTestPriceCents, priceCents) + + expectedParams := services.PricingDisplayParams{ + {Key: models.ServiceItemParamNameContractYearName, Value: testdatagen.DefaultContractCode}, + {Key: models.ServiceItemParamNameEscalationCompounded, Value: FormatEscalation(ioshutTestEscalationCompounded)}, + {Key: models.ServiceItemParamNamePriceRateOrFactor, Value: FormatCents(ioshutTestBasePriceCents)}, + } + suite.validatePricerCreatedParams(expectedParams, displayParams) + }) + + suite.Run("invalid service code", func() { + suite.setupInternationalAccessorialPrice(models.ReServiceCodeIOSHUT, ioshutTestMarket, ioshutTestBasePriceCents, testdatagen.DefaultContractCode, ioshutTestEscalationCompounded) + _, _, err := priceInternationalShuttling(suite.AppContextForTest(), models.ReServiceCodeCS, testdatagen.DefaultContractCode, ioshutTestRequestedPickupDate, ioshutTestWeight, ioshutTestMarket) + + suite.Error(err) + suite.Contains(err.Error(), "unsupported international shuttling code") + }) + + suite.Run("invalid weight", func() { + suite.setupInternationalAccessorialPrice(models.ReServiceCodeIOSHUT, ioshutTestMarket, ioshutTestBasePriceCents, testdatagen.DefaultContractCode, ioshutTestEscalationCompounded) + + badWeight := unit.Pound(250) + _, _, err := priceInternationalShuttling(suite.AppContextForTest(), models.ReServiceCodeIOSHUT, testdatagen.DefaultContractCode, ioshutTestRequestedPickupDate, badWeight, ioshutTestMarket) + + suite.Error(err) + suite.Contains(err.Error(), "Weight must be a minimum of 500") + }) + + suite.Run("not finding a rate record", func() { + suite.setupInternationalAccessorialPrice(models.ReServiceCodeIOSHUT, ioshutTestMarket, ioshutTestBasePriceCents, testdatagen.DefaultContractCode, ioshutTestEscalationCompounded) + + _, _, err := priceInternationalShuttling(suite.AppContextForTest(), models.ReServiceCodeIOSHUT, "BOGUS", ioshutTestRequestedPickupDate, ioshutTestWeight, ioshutTestMarket) + + 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.ReServiceCodeIOSHUT, ioshutTestMarket, ioshutTestBasePriceCents, testdatagen.DefaultContractCode, ioshutTestEscalationCompounded) + + twoYearsLaterPickupDate := ioshutTestRequestedPickupDate.AddDate(2, 0, 0) + _, _, err := priceInternationalShuttling(suite.AppContextForTest(), models.ReServiceCodeIOSHUT, testdatagen.DefaultContractCode, twoYearsLaterPickupDate, ioshutTestWeight, ioshutTestMarket) + + suite.Error(err) + suite.Contains(err.Error(), "could not calculate escalated price: could not lookup contract year") + }) +} + func (suite *GHCRateEngineServiceSuite) TestPriceIntlPackUnpack() { suite.Run("success with IHPK", func() { suite.setupIntlPackServiceItem() @@ -44,3 +99,109 @@ func (suite *GHCRateEngineServiceSuite) TestPriceIntlPackUnpack() { }) } + +func (suite *GHCRateEngineServiceSuite) TestPriceIntlFirstDaySIT() { + suite.Run("success with IDFSIT", func() { + suite.setupIntlDestinationFirstDayServiceItem() + totalCost, displayParams, err := priceIntlFirstDaySIT(suite.AppContextForTest(), models.ReServiceCodeIDFSIT, testdatagen.DefaultContractCode, idfsitTestRequestedPickupDate, idfsitTestWeight, idfsitTestPerUnitCents.Int()) + suite.NoError(err) + suite.Equal(idfsitTestTotalCost, totalCost) + + expectedParams := services.PricingDisplayParams{ + {Key: models.ServiceItemParamNameContractYearName, Value: idfsitTestContractYearName}, + {Key: models.ServiceItemParamNameEscalationCompounded, Value: FormatEscalation(idfsitTestEscalationCompounded)}, + {Key: models.ServiceItemParamNameIsPeak, Value: FormatBool(idfsitTestIsPeakPeriod)}, + {Key: models.ServiceItemParamNamePriceRateOrFactor, Value: FormatCents(idfsitTestPerUnitCents)}, + } + suite.validatePricerCreatedParams(expectedParams, displayParams) + }) + + suite.Run("success with IOFSIT", func() { + suite.setupIntlOriginFirstDayServiceItem() + totalCost, displayParams, err := priceIntlFirstDaySIT(suite.AppContextForTest(), models.ReServiceCodeIOFSIT, testdatagen.DefaultContractCode, iofsitTestRequestedPickupDate, iofsitTestWeight, iofsitTestPerUnitCents.Int()) + suite.NoError(err) + suite.Equal(iofsitTestTotalCost, totalCost) + + expectedParams := services.PricingDisplayParams{ + {Key: models.ServiceItemParamNameContractYearName, Value: iofsitTestContractYearName}, + {Key: models.ServiceItemParamNameEscalationCompounded, Value: FormatEscalation(iofsitTestEscalationCompounded)}, + {Key: models.ServiceItemParamNameIsPeak, Value: FormatBool(iofsitTestIsPeakPeriod)}, + {Key: models.ServiceItemParamNamePriceRateOrFactor, Value: FormatCents(iofsitTestPerUnitCents)}, + } + suite.validatePricerCreatedParams(expectedParams, displayParams) + }) + + suite.Run("Invalid parameters to Price", func() { + suite.setupIntlDestinationFirstDayServiceItem() + _, _, err := priceIntlFirstDaySIT(suite.AppContextForTest(), models.ReServiceCodeDLH, testdatagen.DefaultContractCode, idfsitTestRequestedPickupDate, idfsitTestWeight, idfsitTestPerUnitCents.Int()) + suite.Error(err) + suite.Contains(err.Error(), "unsupported first day SIT code") + + _, _, err = priceIntlFirstDaySIT(suite.AppContextForTest(), models.ReServiceCodeIDFSIT, "", idfsitTestRequestedPickupDate, idfsitTestWeight, idfsitTestPerUnitCents.Int()) + suite.Error(err) + suite.Contains(err.Error(), "ContractCode is required") + + _, _, err = priceIntlFirstDaySIT(suite.AppContextForTest(), models.ReServiceCodeIDFSIT, testdatagen.DefaultContractCode, time.Time{}, idfsitTestWeight, idfsitTestPerUnitCents.Int()) + suite.Error(err) + suite.Contains(err.Error(), "ReferenceDate is required") + + _, _, err = priceIntlFirstDaySIT(suite.AppContextForTest(), models.ReServiceCodeIDFSIT, testdatagen.DefaultContractCode, idfsitTestRequestedPickupDate, idfsitTestWeight, 0) + suite.Error(err) + suite.Contains(err.Error(), "PerUnitCents is required") + }) +} + +func (suite *GHCRateEngineServiceSuite) TestPriceIntlAdditionalDaySIT() { + suite.Run("success with IDASIT", func() { + suite.setupIntlDestinationAdditionalDayServiceItem() + totalCost, displayParams, err := priceIntlAdditionalDaySIT(suite.AppContextForTest(), models.ReServiceCodeIDASIT, testdatagen.DefaultContractCode, idasitTestRequestedPickupDate, idasitNumerDaysInSIT, idasitTestWeight, idasitTestPerUnitCents.Int()) + suite.NoError(err) + suite.Equal(idasitTestTotalCost, totalCost) + + expectedParams := services.PricingDisplayParams{ + {Key: models.ServiceItemParamNameContractYearName, Value: idasitTestContractYearName}, + {Key: models.ServiceItemParamNameEscalationCompounded, Value: FormatEscalation(idasitTestEscalationCompounded)}, + {Key: models.ServiceItemParamNameIsPeak, Value: FormatBool(idasitTestIsPeakPeriod)}, + {Key: models.ServiceItemParamNamePriceRateOrFactor, Value: FormatCents(idasitTestPerUnitCents)}, + } + suite.validatePricerCreatedParams(expectedParams, displayParams) + }) + + suite.Run("success with IOASIT", func() { + suite.setupIntlOriginAdditionalDayServiceItem() + totalCost, displayParams, err := priceIntlAdditionalDaySIT(suite.AppContextForTest(), models.ReServiceCodeIOASIT, testdatagen.DefaultContractCode, ioasitTestRequestedPickupDate, idasitNumerDaysInSIT, ioasitTestWeight, ioasitTestPerUnitCents.Int()) + suite.NoError(err) + suite.Equal(ioasitTestTotalCost, totalCost) + + expectedParams := services.PricingDisplayParams{ + {Key: models.ServiceItemParamNameContractYearName, Value: ioasitTestContractYearName}, + {Key: models.ServiceItemParamNameEscalationCompounded, Value: FormatEscalation(ioasitTestEscalationCompounded)}, + {Key: models.ServiceItemParamNameIsPeak, Value: FormatBool(ioasitTestIsPeakPeriod)}, + {Key: models.ServiceItemParamNamePriceRateOrFactor, Value: FormatCents(ioasitTestPerUnitCents)}, + } + suite.validatePricerCreatedParams(expectedParams, displayParams) + }) + + suite.Run("Invalid parameters to Price", func() { + suite.setupIntlDestinationAdditionalDayServiceItem() + _, _, err := priceIntlAdditionalDaySIT(suite.AppContextForTest(), models.ReServiceCodeDLH, testdatagen.DefaultContractCode, idasitTestRequestedPickupDate, idasitNumerDaysInSIT, idasitTestWeight, idasitTestPerUnitCents.Int()) + suite.Error(err) + suite.Contains(err.Error(), "unsupported additional day SIT code") + + _, _, err = priceIntlAdditionalDaySIT(suite.AppContextForTest(), models.ReServiceCodeIDASIT, "", idasitTestRequestedPickupDate, idasitNumerDaysInSIT, idasitTestWeight, idasitTestPerUnitCents.Int()) + suite.Error(err) + suite.Contains(err.Error(), "ContractCode is required") + + _, _, err = priceIntlAdditionalDaySIT(suite.AppContextForTest(), models.ReServiceCodeIDASIT, testdatagen.DefaultContractCode, time.Time{}, idasitNumerDaysInSIT, idasitTestWeight, idasitTestPerUnitCents.Int()) + suite.Error(err) + suite.Contains(err.Error(), "ReferenceDate is required") + + _, _, err = priceIntlAdditionalDaySIT(suite.AppContextForTest(), models.ReServiceCodeIDASIT, testdatagen.DefaultContractCode, idasitTestRequestedPickupDate, idasitNumerDaysInSIT, idasitTestWeight, 0) + suite.Error(err) + suite.Contains(err.Error(), "PerUnitCents is required") + + _, _, err = priceIntlAdditionalDaySIT(suite.AppContextForTest(), models.ReServiceCodeIDASIT, testdatagen.DefaultContractCode, idasitTestRequestedPickupDate, 0, idasitTestWeight, 0) + suite.Error(err) + suite.Contains(err.Error(), "NumberDaysSIT is required") + }) +} diff --git a/pkg/services/ghcrateengine/pricer_helpers_test.go b/pkg/services/ghcrateengine/pricer_helpers_test.go index 06b9ec30044..ee8fddf3ebe 100644 --- a/pkg/services/ghcrateengine/pricer_helpers_test.go +++ b/pkg/services/ghcrateengine/pricer_helpers_test.go @@ -595,6 +595,7 @@ func (suite *GHCRateEngineServiceSuite) Test_priceDomesticShuttling() { suite.Contains(err.Error(), "could not calculate escalated price: could not lookup contract year") }) } + func (suite *GHCRateEngineServiceSuite) Test_priceDomesticCrating() { suite.Run("crating golden path", func() { suite.setupDomesticAccessorialPrice(models.ReServiceCodeDCRT, dcrtTestServiceSchedule, dcrtTestBasePriceCents, testdatagen.DefaultContractCode, dcrtTestEscalationCompounded) diff --git a/pkg/services/ghcrateengine/pricer_query_helpers.go b/pkg/services/ghcrateengine/pricer_query_helpers.go index 84cde4fc64c..318c6f32864 100644 --- a/pkg/services/ghcrateengine/pricer_query_helpers.go +++ b/pkg/services/ghcrateengine/pricer_query_helpers.go @@ -64,6 +64,23 @@ func fetchDomServiceAreaPrice(appCtx appcontext.AppContext, contractCode string, return domServiceAreaPrice, nil } +func fetchInternationalAccessorialPrice(appCtx appcontext.AppContext, contractCode string, serviceCode models.ReServiceCode, market models.Market) (models.ReIntlAccessorialPrice, error) { + var internationalAccessorialPrice models.ReIntlAccessorialPrice + err := appCtx.DB().Q(). + Join("re_services", "service_id = re_services.id"). + Join("re_contracts", "re_contracts.id = re_intl_accessorial_prices.contract_id"). + Where("re_contracts.code = $1", contractCode). + Where("re_services.code = $2", serviceCode). + Where("market = $3", market). + First(&internationalAccessorialPrice) + + if err != nil { + return models.ReIntlAccessorialPrice{}, err + } + + return internationalAccessorialPrice, nil +} + func fetchAccessorialPrice(appCtx appcontext.AppContext, contractCode string, serviceCode models.ReServiceCode, schedule int) (models.ReDomesticAccessorialPrice, error) { var domAccessorialPrice models.ReDomesticAccessorialPrice err := appCtx.DB().Q(). diff --git a/pkg/services/ghcrateengine/pricer_query_helpers_test.go b/pkg/services/ghcrateengine/pricer_query_helpers_test.go index 349f0bdeae4..35b4b6860f9 100644 --- a/pkg/services/ghcrateengine/pricer_query_helpers_test.go +++ b/pkg/services/ghcrateengine/pricer_query_helpers_test.go @@ -78,6 +78,16 @@ func (suite *GHCRateEngineServiceSuite) Test_fetchAccessorialPrice() { }) } +func (suite *GHCRateEngineServiceSuite) Test_fetchInternationalAccessorialPrice() { + suite.Run("golden path", func() { + suite.setupInternationalAccessorialPrice(models.ReServiceCodeIDSHUT, idshutTestMarket, idshutTestBasePriceCents, testdatagen.DefaultContractCode, idshutTestEscalationCompounded) + internationalAccessorialPrice, err := fetchInternationalAccessorialPrice(suite.AppContextForTest(), testdatagen.DefaultContractCode, models.ReServiceCodeIDSHUT, idshutTestMarket) + + suite.NoError(err) + suite.Equal(idshutTestBasePriceCents, internationalAccessorialPrice.PerUnitCents) + }) +} + func (suite *GHCRateEngineServiceSuite) Test_fetchContractYear() { testDate := time.Date(testdatagen.TestYear, time.June, 17, 8, 45, 44, 333, time.UTC) testEscalationCompounded := 1.0512 diff --git a/pkg/services/ghcrateengine/service_item_pricer.go b/pkg/services/ghcrateengine/service_item_pricer.go index a673f832b63..777ca2283bf 100644 --- a/pkg/services/ghcrateengine/service_item_pricer.go +++ b/pkg/services/ghcrateengine/service_item_pricer.go @@ -65,6 +65,10 @@ func PricerForServiceItem(serviceCode models.ReServiceCode) (services.ParamsPric return NewDomesticDestinationShuttlingPricer(), nil case models.ReServiceCodeDOSHUT: return NewDomesticOriginShuttlingPricer(), nil + case models.ReServiceCodeIDSHUT: + return NewInternationalDestinationShuttlingPricer(), nil + case models.ReServiceCodeIOSHUT: + return NewInternationalOriginShuttlingPricer(), nil case models.ReServiceCodeDCRT: return NewDomesticCratingPricer(), nil case models.ReServiceCodeDUCRT: @@ -103,6 +107,14 @@ func PricerForServiceItem(serviceCode models.ReServiceCode) (services.ParamsPric return NewPortFuelSurchargePricer(), nil case models.ReServiceCodePODFSC: return NewPortFuelSurchargePricer(), nil + case models.ReServiceCodeIOFSIT: + return NewIntlOriginFirstDaySITPricer(), nil + case models.ReServiceCodeIOASIT: + return NewIntlOriginAdditionalDaySITPricer(), nil + case models.ReServiceCodeIDFSIT: + return NewIntlDestinationFirstDaySITPricer(), nil + case models.ReServiceCodeIDASIT: + return NewIntlDestinationAdditionalDaySITPricer(), nil default: // TODO: We may want a different error type here after all pricers have been implemented return nil, apperror.NewNotImplementedError(fmt.Sprintf("pricer not found for code %s", serviceCode)) diff --git a/pkg/services/ghcrateengine/service_item_pricer_test.go b/pkg/services/ghcrateengine/service_item_pricer_test.go index 86e5e858af1..c27652cf90d 100644 --- a/pkg/services/ghcrateengine/service_item_pricer_test.go +++ b/pkg/services/ghcrateengine/service_item_pricer_test.go @@ -51,6 +51,8 @@ func (suite *GHCRateEngineServiceSuite) TestGetPricer() { {models.ReServiceCodeDDP, &domesticDestinationPricer{}}, {models.ReServiceCodeDDSHUT, &domesticDestinationShuttlingPricer{}}, {models.ReServiceCodeDOSHUT, &domesticOriginShuttlingPricer{}}, + {models.ReServiceCodeIDSHUT, &internationalDestinationShuttlingPricer{}}, + {models.ReServiceCodeIOSHUT, &internationalOriginShuttlingPricer{}}, {models.ReServiceCodeDCRT, &domesticCratingPricer{}}, {models.ReServiceCodeDUCRT, &domesticUncratingPricer{}}, {models.ReServiceCodeDPK, &domesticPackPricer{}}, diff --git a/pkg/services/ghcrateengine/shared.go b/pkg/services/ghcrateengine/shared.go index 171d8668bb1..1a76f817734 100644 --- a/pkg/services/ghcrateengine/shared.go +++ b/pkg/services/ghcrateengine/shared.go @@ -12,6 +12,9 @@ const minDomesticWeight = unit.Pound(500) // minIntlWeightHHG is the minimum weight used in intl calculations (weights below this are upgraded to the min) 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) + // 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/ghcrateengine/shared_test.go b/pkg/services/ghcrateengine/shared_test.go index 72b669e73ca..4547dafa0ce 100644 --- a/pkg/services/ghcrateengine/shared_test.go +++ b/pkg/services/ghcrateengine/shared_test.go @@ -2,6 +2,8 @@ package ghcrateengine import ( "time" + + "github.com/transcom/mymove/pkg/unit" ) func (suite *GHCRateEngineServiceSuite) TestIsPeakPeriod() { @@ -39,3 +41,11 @@ func (suite *GHCRateEngineServiceSuite) TestIsPeakPeriod() { suite.False(IsPeakPeriod(date)) }) } + +func (suite *GHCRateEngineServiceSuite) TestGetDomesticWeight() { + suite.Run("test getDomesticWeight", func() { + domesticWeight := GetMinDomesticWeight() + suite.NotNil(domesticWeight) + suite.Equal(domesticWeight, unit.Pound(500)) + }) +} diff --git a/pkg/services/invoice/ghc_payment_request_invoice_generator.go b/pkg/services/invoice/ghc_payment_request_invoice_generator.go index fd111048868..4af73b9e28d 100644 --- a/pkg/services/invoice/ghc_payment_request_invoice_generator.go +++ b/pkg/services/invoice/ghc_payment_request_invoice_generator.go @@ -447,16 +447,26 @@ func (g ghcPaymentRequestInvoiceGenerator) createBuyerAndSellerOrganizationNames return apperror.NewQueryError("MTOShipments", err, fmt.Sprintf("error querying for shipments pickup address gbloc to use in N1*BY segments in PaymentRequest %s: %s", paymentRequestID, err)) } } - pickupPostalCodeToGbloc, gblocErr := models.FetchGBLOCForPostalCode(appCtx.DB(), address.PostalCode) - if gblocErr != nil { - return apperror.NewInvalidInputError(pickupPostalCodeToGbloc.ID, gblocErr, nil, "unable to determine GBLOC for pickup postal code") + var pickupPostalCodeToGbloc *string + if *address.IsOconus { + originDutyLocationGBLOCOconus, gblocErr := models.FetchAddressGbloc(appCtx.DB(), address, orders.ServiceMember) + if gblocErr != nil { + return apperror.NewInvalidInputError(address.ID, gblocErr, nil, "unable to determine GBLOC for Oconus pickup postal code") + } + pickupPostalCodeToGbloc = originDutyLocationGBLOCOconus + } else { + originDutyLocationGBLOCConus, gblocErr := models.FetchGBLOCForPostalCode(appCtx.DB(), originDutyLocation.Address.PostalCode) + if gblocErr != nil { + return apperror.NewInvalidInputError(address.ID, gblocErr, nil, "unable to determine GBLOC for pickup postal code") + } + pickupPostalCodeToGbloc = &originDutyLocationGBLOCConus.GBLOC } header.BuyerOrganizationName = edisegment.N1{ EntityIdentifierCode: "BY", Name: truncateStr(originDutyLocation.Name, maxLocationlength), IdentificationCodeQualifier: "92", - IdentificationCode: modifyGblocIfMarines(*orders.ServiceMember.Affiliation, pickupPostalCodeToGbloc.GBLOC), + IdentificationCode: modifyGblocIfMarines(*orders.ServiceMember.Affiliation, *pickupPostalCodeToGbloc), } // seller organization name @@ -482,9 +492,19 @@ func (g ghcPaymentRequestInvoiceGenerator) createOriginAndDestinationSegments(ap return apperror.NewConflictError(orders.ID, "Invalid Order, must have NewDutyLocation") } - destPostalCodeToGbloc, gblocErr := models.FetchGBLOCForPostalCode(appCtx.DB(), destinationDutyLocation.Address.PostalCode) - if gblocErr != nil { - return apperror.NewInvalidInputError(destinationDutyLocation.ID, gblocErr, nil, "unable to determine GBLOC for duty location postal code") + var destPostalCodeToGbloc *string + if *destinationDutyLocation.Address.IsOconus { + destPostalCodeToGblocOconus, gblocErr := models.FetchAddressGbloc(appCtx.DB(), destinationDutyLocation.Address, orders.ServiceMember) + if gblocErr != nil { + return apperror.NewInvalidInputError(destinationDutyLocation.ID, gblocErr, nil, "unable to determine GBLOC for Oconus duty location postal code") + } + destPostalCodeToGbloc = destPostalCodeToGblocOconus + } else { + ddestPostalCodeToGblocConus, gblocErr := models.FetchGBLOCForPostalCode(appCtx.DB(), destinationDutyLocation.Address.PostalCode) + if gblocErr != nil { + return apperror.NewInvalidInputError(destinationDutyLocation.ID, gblocErr, nil, "unable to determine GBLOC for duty location postal code") + } + destPostalCodeToGbloc = &ddestPostalCodeToGblocConus.GBLOC } // destination name @@ -492,7 +512,7 @@ func (g ghcPaymentRequestInvoiceGenerator) createOriginAndDestinationSegments(ap EntityIdentifierCode: "ST", Name: truncateStr(destinationDutyLocation.Name, maxLocationlength), IdentificationCodeQualifier: "10", - IdentificationCode: modifyGblocIfMarines(*orders.ServiceMember.Affiliation, destPostalCodeToGbloc.GBLOC), + IdentificationCode: modifyGblocIfMarines(*orders.ServiceMember.Affiliation, *destPostalCodeToGbloc), } // destination address diff --git a/pkg/services/mocks/InternationalDestinationShuttlingPricer.go b/pkg/services/mocks/InternationalDestinationShuttlingPricer.go new file mode 100644 index 00000000000..f6871e1fd4b --- /dev/null +++ b/pkg/services/mocks/InternationalDestinationShuttlingPricer.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" +) + +// InternationalDestinationShuttlingPricer is an autogenerated mock type for the InternationalDestinationShuttlingPricer type +type InternationalDestinationShuttlingPricer struct { + mock.Mock +} + +// Price provides a mock function with given fields: appCtx, contractCode, requestedPickupDate, weight, market +func (_m *InternationalDestinationShuttlingPricer) Price(appCtx appcontext.AppContext, contractCode string, requestedPickupDate time.Time, weight unit.Pound, market models.Market) (unit.Cents, services.PricingDisplayParams, error) { + ret := _m.Called(appCtx, contractCode, requestedPickupDate, weight, 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.Pound, models.Market) (unit.Cents, services.PricingDisplayParams, error)); ok { + return rf(appCtx, contractCode, requestedPickupDate, weight, market) + } + if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, time.Time, unit.Pound, models.Market) unit.Cents); ok { + r0 = rf(appCtx, contractCode, requestedPickupDate, weight, market) + } else { + r0 = ret.Get(0).(unit.Cents) + } + + if rf, ok := ret.Get(1).(func(appcontext.AppContext, string, time.Time, unit.Pound, models.Market) services.PricingDisplayParams); ok { + r1 = rf(appCtx, contractCode, requestedPickupDate, weight, 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.Pound, models.Market) error); ok { + r2 = rf(appCtx, contractCode, requestedPickupDate, weight, market) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// PriceUsingParams provides a mock function with given fields: appCtx, params +func (_m *InternationalDestinationShuttlingPricer) 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 +} + +// NewInternationalDestinationShuttlingPricer creates a new instance of InternationalDestinationShuttlingPricer. 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 NewInternationalDestinationShuttlingPricer(t interface { + mock.TestingT + Cleanup(func()) +}) *InternationalDestinationShuttlingPricer { + mock := &InternationalDestinationShuttlingPricer{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/services/mocks/InternationalOriginShuttlingPricer.go b/pkg/services/mocks/InternationalOriginShuttlingPricer.go new file mode 100644 index 00000000000..fbfc2d9ba26 --- /dev/null +++ b/pkg/services/mocks/InternationalOriginShuttlingPricer.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" +) + +// InternationalOriginShuttlingPricer is an autogenerated mock type for the InternationalOriginShuttlingPricer type +type InternationalOriginShuttlingPricer struct { + mock.Mock +} + +// Price provides a mock function with given fields: appCtx, contractCode, requestedPickupDate, weight, market +func (_m *InternationalOriginShuttlingPricer) Price(appCtx appcontext.AppContext, contractCode string, requestedPickupDate time.Time, weight unit.Pound, market models.Market) (unit.Cents, services.PricingDisplayParams, error) { + ret := _m.Called(appCtx, contractCode, requestedPickupDate, weight, 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.Pound, models.Market) (unit.Cents, services.PricingDisplayParams, error)); ok { + return rf(appCtx, contractCode, requestedPickupDate, weight, market) + } + if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, time.Time, unit.Pound, models.Market) unit.Cents); ok { + r0 = rf(appCtx, contractCode, requestedPickupDate, weight, market) + } else { + r0 = ret.Get(0).(unit.Cents) + } + + if rf, ok := ret.Get(1).(func(appcontext.AppContext, string, time.Time, unit.Pound, models.Market) services.PricingDisplayParams); ok { + r1 = rf(appCtx, contractCode, requestedPickupDate, weight, 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.Pound, models.Market) error); ok { + r2 = rf(appCtx, contractCode, requestedPickupDate, weight, market) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// PriceUsingParams provides a mock function with given fields: appCtx, params +func (_m *InternationalOriginShuttlingPricer) 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 +} + +// NewInternationalOriginShuttlingPricer creates a new instance of InternationalOriginShuttlingPricer. 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 NewInternationalOriginShuttlingPricer(t interface { + mock.TestingT + Cleanup(func()) +}) *InternationalOriginShuttlingPricer { + mock := &InternationalOriginShuttlingPricer{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/services/mocks/IntlDestinationAdditionalDaySITPricer.go b/pkg/services/mocks/IntlDestinationAdditionalDaySITPricer.go new file mode 100644 index 00000000000..7a11b759d7f --- /dev/null +++ b/pkg/services/mocks/IntlDestinationAdditionalDaySITPricer.go @@ -0,0 +1,109 @@ +// Code generated by mockery. DO NOT EDIT. + +package mocks + +import ( + mock "github.com/stretchr/testify/mock" + appcontext "github.com/transcom/mymove/pkg/appcontext" + + models "github.com/transcom/mymove/pkg/models" + + services "github.com/transcom/mymove/pkg/services" + + time "time" + + unit "github.com/transcom/mymove/pkg/unit" +) + +// IntlDestinationAdditionalDaySITPricer is an autogenerated mock type for the IntlDestinationAdditionalDaySITPricer type +type IntlDestinationAdditionalDaySITPricer struct { + mock.Mock +} + +// Price provides a mock function with given fields: appCtx, contractCode, requestedPickupDate, numberOfDaysInSIT, weight, perUnitCents +func (_m *IntlDestinationAdditionalDaySITPricer) Price(appCtx appcontext.AppContext, contractCode string, requestedPickupDate time.Time, numberOfDaysInSIT int, weight unit.Pound, perUnitCents int) (unit.Cents, services.PricingDisplayParams, error) { + ret := _m.Called(appCtx, contractCode, requestedPickupDate, numberOfDaysInSIT, weight, perUnitCents) + + if len(ret) == 0 { + panic("no return value specified for Price") + } + + var r0 unit.Cents + var r1 services.PricingDisplayParams + var r2 error + if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, time.Time, int, unit.Pound, int) (unit.Cents, services.PricingDisplayParams, error)); ok { + return rf(appCtx, contractCode, requestedPickupDate, numberOfDaysInSIT, weight, perUnitCents) + } + if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, time.Time, int, unit.Pound, int) unit.Cents); ok { + r0 = rf(appCtx, contractCode, requestedPickupDate, numberOfDaysInSIT, weight, perUnitCents) + } else { + r0 = ret.Get(0).(unit.Cents) + } + + if rf, ok := ret.Get(1).(func(appcontext.AppContext, string, time.Time, int, unit.Pound, int) services.PricingDisplayParams); ok { + r1 = rf(appCtx, contractCode, requestedPickupDate, numberOfDaysInSIT, weight, perUnitCents) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(services.PricingDisplayParams) + } + } + + if rf, ok := ret.Get(2).(func(appcontext.AppContext, string, time.Time, int, unit.Pound, int) error); ok { + r2 = rf(appCtx, contractCode, requestedPickupDate, numberOfDaysInSIT, weight, perUnitCents) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// PriceUsingParams provides a mock function with given fields: appCtx, params +func (_m *IntlDestinationAdditionalDaySITPricer) PriceUsingParams(appCtx appcontext.AppContext, params models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error) { + ret := _m.Called(appCtx, params) + + if len(ret) == 0 { + panic("no return value specified for PriceUsingParams") + } + + var r0 unit.Cents + var r1 services.PricingDisplayParams + var r2 error + if rf, ok := ret.Get(0).(func(appcontext.AppContext, models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error)); ok { + return rf(appCtx, params) + } + if rf, ok := ret.Get(0).(func(appcontext.AppContext, models.PaymentServiceItemParams) unit.Cents); ok { + r0 = rf(appCtx, params) + } else { + r0 = ret.Get(0).(unit.Cents) + } + + if rf, ok := ret.Get(1).(func(appcontext.AppContext, models.PaymentServiceItemParams) services.PricingDisplayParams); ok { + r1 = rf(appCtx, params) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(services.PricingDisplayParams) + } + } + + if rf, ok := ret.Get(2).(func(appcontext.AppContext, models.PaymentServiceItemParams) error); ok { + r2 = rf(appCtx, params) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// NewIntlDestinationAdditionalDaySITPricer creates a new instance of IntlDestinationAdditionalDaySITPricer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewIntlDestinationAdditionalDaySITPricer(t interface { + mock.TestingT + Cleanup(func()) +}) *IntlDestinationAdditionalDaySITPricer { + mock := &IntlDestinationAdditionalDaySITPricer{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/services/mocks/IntlDestinationFirstDaySITPricer.go b/pkg/services/mocks/IntlDestinationFirstDaySITPricer.go new file mode 100644 index 00000000000..99df36da131 --- /dev/null +++ b/pkg/services/mocks/IntlDestinationFirstDaySITPricer.go @@ -0,0 +1,109 @@ +// Code generated by mockery. DO NOT EDIT. + +package mocks + +import ( + mock "github.com/stretchr/testify/mock" + appcontext "github.com/transcom/mymove/pkg/appcontext" + + models "github.com/transcom/mymove/pkg/models" + + services "github.com/transcom/mymove/pkg/services" + + time "time" + + unit "github.com/transcom/mymove/pkg/unit" +) + +// IntlDestinationFirstDaySITPricer is an autogenerated mock type for the IntlDestinationFirstDaySITPricer type +type IntlDestinationFirstDaySITPricer struct { + mock.Mock +} + +// Price provides a mock function with given fields: appCtx, contractCode, requestedPickupDate, weight, perUnitCents +func (_m *IntlDestinationFirstDaySITPricer) Price(appCtx appcontext.AppContext, contractCode string, requestedPickupDate time.Time, weight unit.Pound, perUnitCents int) (unit.Cents, services.PricingDisplayParams, error) { + ret := _m.Called(appCtx, contractCode, requestedPickupDate, weight, perUnitCents) + + if len(ret) == 0 { + panic("no return value specified for Price") + } + + var r0 unit.Cents + var r1 services.PricingDisplayParams + var r2 error + if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, time.Time, unit.Pound, int) (unit.Cents, services.PricingDisplayParams, error)); ok { + return rf(appCtx, contractCode, requestedPickupDate, weight, perUnitCents) + } + if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, time.Time, unit.Pound, int) unit.Cents); ok { + r0 = rf(appCtx, contractCode, requestedPickupDate, weight, perUnitCents) + } else { + r0 = ret.Get(0).(unit.Cents) + } + + if rf, ok := ret.Get(1).(func(appcontext.AppContext, string, time.Time, unit.Pound, int) services.PricingDisplayParams); ok { + r1 = rf(appCtx, contractCode, requestedPickupDate, weight, perUnitCents) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(services.PricingDisplayParams) + } + } + + if rf, ok := ret.Get(2).(func(appcontext.AppContext, string, time.Time, unit.Pound, int) error); ok { + r2 = rf(appCtx, contractCode, requestedPickupDate, weight, perUnitCents) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// PriceUsingParams provides a mock function with given fields: appCtx, params +func (_m *IntlDestinationFirstDaySITPricer) PriceUsingParams(appCtx appcontext.AppContext, params models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error) { + ret := _m.Called(appCtx, params) + + if len(ret) == 0 { + panic("no return value specified for PriceUsingParams") + } + + var r0 unit.Cents + var r1 services.PricingDisplayParams + var r2 error + if rf, ok := ret.Get(0).(func(appcontext.AppContext, models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error)); ok { + return rf(appCtx, params) + } + if rf, ok := ret.Get(0).(func(appcontext.AppContext, models.PaymentServiceItemParams) unit.Cents); ok { + r0 = rf(appCtx, params) + } else { + r0 = ret.Get(0).(unit.Cents) + } + + if rf, ok := ret.Get(1).(func(appcontext.AppContext, models.PaymentServiceItemParams) services.PricingDisplayParams); ok { + r1 = rf(appCtx, params) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(services.PricingDisplayParams) + } + } + + if rf, ok := ret.Get(2).(func(appcontext.AppContext, models.PaymentServiceItemParams) error); ok { + r2 = rf(appCtx, params) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// NewIntlDestinationFirstDaySITPricer creates a new instance of IntlDestinationFirstDaySITPricer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewIntlDestinationFirstDaySITPricer(t interface { + mock.TestingT + Cleanup(func()) +}) *IntlDestinationFirstDaySITPricer { + mock := &IntlDestinationFirstDaySITPricer{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/services/mocks/IntlOriginAdditionalDaySITPricer.go b/pkg/services/mocks/IntlOriginAdditionalDaySITPricer.go new file mode 100644 index 00000000000..a931b2d3879 --- /dev/null +++ b/pkg/services/mocks/IntlOriginAdditionalDaySITPricer.go @@ -0,0 +1,109 @@ +// Code generated by mockery. DO NOT EDIT. + +package mocks + +import ( + mock "github.com/stretchr/testify/mock" + appcontext "github.com/transcom/mymove/pkg/appcontext" + + models "github.com/transcom/mymove/pkg/models" + + services "github.com/transcom/mymove/pkg/services" + + time "time" + + unit "github.com/transcom/mymove/pkg/unit" +) + +// IntlOriginAdditionalDaySITPricer is an autogenerated mock type for the IntlOriginAdditionalDaySITPricer type +type IntlOriginAdditionalDaySITPricer struct { + mock.Mock +} + +// Price provides a mock function with given fields: appCtx, contractCode, requestedPickupDate, numberOfDaysInSIT, weight, perUnitCents +func (_m *IntlOriginAdditionalDaySITPricer) Price(appCtx appcontext.AppContext, contractCode string, requestedPickupDate time.Time, numberOfDaysInSIT int, weight unit.Pound, perUnitCents int) (unit.Cents, services.PricingDisplayParams, error) { + ret := _m.Called(appCtx, contractCode, requestedPickupDate, numberOfDaysInSIT, weight, perUnitCents) + + if len(ret) == 0 { + panic("no return value specified for Price") + } + + var r0 unit.Cents + var r1 services.PricingDisplayParams + var r2 error + if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, time.Time, int, unit.Pound, int) (unit.Cents, services.PricingDisplayParams, error)); ok { + return rf(appCtx, contractCode, requestedPickupDate, numberOfDaysInSIT, weight, perUnitCents) + } + if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, time.Time, int, unit.Pound, int) unit.Cents); ok { + r0 = rf(appCtx, contractCode, requestedPickupDate, numberOfDaysInSIT, weight, perUnitCents) + } else { + r0 = ret.Get(0).(unit.Cents) + } + + if rf, ok := ret.Get(1).(func(appcontext.AppContext, string, time.Time, int, unit.Pound, int) services.PricingDisplayParams); ok { + r1 = rf(appCtx, contractCode, requestedPickupDate, numberOfDaysInSIT, weight, perUnitCents) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(services.PricingDisplayParams) + } + } + + if rf, ok := ret.Get(2).(func(appcontext.AppContext, string, time.Time, int, unit.Pound, int) error); ok { + r2 = rf(appCtx, contractCode, requestedPickupDate, numberOfDaysInSIT, weight, perUnitCents) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// PriceUsingParams provides a mock function with given fields: appCtx, params +func (_m *IntlOriginAdditionalDaySITPricer) PriceUsingParams(appCtx appcontext.AppContext, params models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error) { + ret := _m.Called(appCtx, params) + + if len(ret) == 0 { + panic("no return value specified for PriceUsingParams") + } + + var r0 unit.Cents + var r1 services.PricingDisplayParams + var r2 error + if rf, ok := ret.Get(0).(func(appcontext.AppContext, models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error)); ok { + return rf(appCtx, params) + } + if rf, ok := ret.Get(0).(func(appcontext.AppContext, models.PaymentServiceItemParams) unit.Cents); ok { + r0 = rf(appCtx, params) + } else { + r0 = ret.Get(0).(unit.Cents) + } + + if rf, ok := ret.Get(1).(func(appcontext.AppContext, models.PaymentServiceItemParams) services.PricingDisplayParams); ok { + r1 = rf(appCtx, params) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(services.PricingDisplayParams) + } + } + + if rf, ok := ret.Get(2).(func(appcontext.AppContext, models.PaymentServiceItemParams) error); ok { + r2 = rf(appCtx, params) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// NewIntlOriginAdditionalDaySITPricer creates a new instance of IntlOriginAdditionalDaySITPricer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewIntlOriginAdditionalDaySITPricer(t interface { + mock.TestingT + Cleanup(func()) +}) *IntlOriginAdditionalDaySITPricer { + mock := &IntlOriginAdditionalDaySITPricer{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/services/mocks/IntlOriginFirstDaySITPricer.go b/pkg/services/mocks/IntlOriginFirstDaySITPricer.go new file mode 100644 index 00000000000..d36cb1e92c9 --- /dev/null +++ b/pkg/services/mocks/IntlOriginFirstDaySITPricer.go @@ -0,0 +1,109 @@ +// Code generated by mockery. DO NOT EDIT. + +package mocks + +import ( + mock "github.com/stretchr/testify/mock" + appcontext "github.com/transcom/mymove/pkg/appcontext" + + models "github.com/transcom/mymove/pkg/models" + + services "github.com/transcom/mymove/pkg/services" + + time "time" + + unit "github.com/transcom/mymove/pkg/unit" +) + +// IntlOriginFirstDaySITPricer is an autogenerated mock type for the IntlOriginFirstDaySITPricer type +type IntlOriginFirstDaySITPricer struct { + mock.Mock +} + +// Price provides a mock function with given fields: appCtx, contractCode, requestedPickupDate, weight, perUnitCents +func (_m *IntlOriginFirstDaySITPricer) Price(appCtx appcontext.AppContext, contractCode string, requestedPickupDate time.Time, weight unit.Pound, perUnitCents int) (unit.Cents, services.PricingDisplayParams, error) { + ret := _m.Called(appCtx, contractCode, requestedPickupDate, weight, perUnitCents) + + if len(ret) == 0 { + panic("no return value specified for Price") + } + + var r0 unit.Cents + var r1 services.PricingDisplayParams + var r2 error + if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, time.Time, unit.Pound, int) (unit.Cents, services.PricingDisplayParams, error)); ok { + return rf(appCtx, contractCode, requestedPickupDate, weight, perUnitCents) + } + if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, time.Time, unit.Pound, int) unit.Cents); ok { + r0 = rf(appCtx, contractCode, requestedPickupDate, weight, perUnitCents) + } else { + r0 = ret.Get(0).(unit.Cents) + } + + if rf, ok := ret.Get(1).(func(appcontext.AppContext, string, time.Time, unit.Pound, int) services.PricingDisplayParams); ok { + r1 = rf(appCtx, contractCode, requestedPickupDate, weight, perUnitCents) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(services.PricingDisplayParams) + } + } + + if rf, ok := ret.Get(2).(func(appcontext.AppContext, string, time.Time, unit.Pound, int) error); ok { + r2 = rf(appCtx, contractCode, requestedPickupDate, weight, perUnitCents) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// PriceUsingParams provides a mock function with given fields: appCtx, params +func (_m *IntlOriginFirstDaySITPricer) PriceUsingParams(appCtx appcontext.AppContext, params models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error) { + ret := _m.Called(appCtx, params) + + if len(ret) == 0 { + panic("no return value specified for PriceUsingParams") + } + + var r0 unit.Cents + var r1 services.PricingDisplayParams + var r2 error + if rf, ok := ret.Get(0).(func(appcontext.AppContext, models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error)); ok { + return rf(appCtx, params) + } + if rf, ok := ret.Get(0).(func(appcontext.AppContext, models.PaymentServiceItemParams) unit.Cents); ok { + r0 = rf(appCtx, params) + } else { + r0 = ret.Get(0).(unit.Cents) + } + + if rf, ok := ret.Get(1).(func(appcontext.AppContext, models.PaymentServiceItemParams) services.PricingDisplayParams); ok { + r1 = rf(appCtx, params) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(services.PricingDisplayParams) + } + } + + if rf, ok := ret.Get(2).(func(appcontext.AppContext, models.PaymentServiceItemParams) error); ok { + r2 = rf(appCtx, params) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// NewIntlOriginFirstDaySITPricer creates a new instance of IntlOriginFirstDaySITPricer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewIntlOriginFirstDaySITPricer(t interface { + mock.TestingT + Cleanup(func()) +}) *IntlOriginFirstDaySITPricer { + mock := &IntlOriginFirstDaySITPricer{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/services/mocks/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/OfficeUserDeleter.go b/pkg/services/mocks/OfficeUserDeleter.go new file mode 100644 index 00000000000..bf491487c6c --- /dev/null +++ b/pkg/services/mocks/OfficeUserDeleter.go @@ -0,0 +1,47 @@ +// Code generated by mockery. DO NOT EDIT. + +package mocks + +import ( + mock "github.com/stretchr/testify/mock" + appcontext "github.com/transcom/mymove/pkg/appcontext" + + uuid "github.com/gofrs/uuid" +) + +// OfficeUserDeleter is an autogenerated mock type for the OfficeUserDeleter type +type OfficeUserDeleter struct { + mock.Mock +} + +// DeleteOfficeUser provides a mock function with given fields: appCtx, id +func (_m *OfficeUserDeleter) DeleteOfficeUser(appCtx appcontext.AppContext, id uuid.UUID) error { + ret := _m.Called(appCtx, id) + + if len(ret) == 0 { + panic("no return value specified for DeleteOfficeUser") + } + + var r0 error + if rf, ok := ret.Get(0).(func(appcontext.AppContext, uuid.UUID) error); ok { + r0 = rf(appCtx, id) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// NewOfficeUserDeleter creates a new instance of OfficeUserDeleter. 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 NewOfficeUserDeleter(t interface { + mock.TestingT + Cleanup(func()) +}) *OfficeUserDeleter { + mock := &OfficeUserDeleter{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/services/mocks/RequestedOfficeUserDeleter.go b/pkg/services/mocks/RequestedOfficeUserDeleter.go new file mode 100644 index 00000000000..4c1a8790e34 --- /dev/null +++ b/pkg/services/mocks/RequestedOfficeUserDeleter.go @@ -0,0 +1,47 @@ +// Code generated by mockery. DO NOT EDIT. + +package mocks + +import ( + mock "github.com/stretchr/testify/mock" + appcontext "github.com/transcom/mymove/pkg/appcontext" + + uuid "github.com/gofrs/uuid" +) + +// RequestedOfficeUserDeleter is an autogenerated mock type for the RequestedOfficeUserDeleter type +type RequestedOfficeUserDeleter struct { + mock.Mock +} + +// DeleteRequestedOfficeUser provides a mock function with given fields: appCtx, id +func (_m *RequestedOfficeUserDeleter) DeleteRequestedOfficeUser(appCtx appcontext.AppContext, id uuid.UUID) error { + ret := _m.Called(appCtx, id) + + if len(ret) == 0 { + panic("no return value specified for DeleteRequestedOfficeUser") + } + + var r0 error + if rf, ok := ret.Get(0).(func(appcontext.AppContext, uuid.UUID) error); ok { + r0 = rf(appCtx, id) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// NewRequestedOfficeUserDeleter creates a new instance of RequestedOfficeUserDeleter. 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 NewRequestedOfficeUserDeleter(t interface { + mock.TestingT + Cleanup(func()) +}) *RequestedOfficeUserDeleter { + mock := &RequestedOfficeUserDeleter{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/services/mocks/TransportationOfficesFetcher.go b/pkg/services/mocks/TransportationOfficesFetcher.go index eed32753594..d0c017b3e19 100644 --- a/pkg/services/mocks/TransportationOfficesFetcher.go +++ b/pkg/services/mocks/TransportationOfficesFetcher.go @@ -46,9 +46,9 @@ func (_m *TransportationOfficesFetcher) GetAllGBLOCs(appCtx appcontext.AppContex return r0, r1 } -// GetCounselingOffices provides a mock function with given fields: appCtx, dutyLocationID -func (_m *TransportationOfficesFetcher) GetCounselingOffices(appCtx appcontext.AppContext, dutyLocationID uuid.UUID) (*models.TransportationOffices, error) { - ret := _m.Called(appCtx, dutyLocationID) +// GetCounselingOffices provides a mock function with given fields: appCtx, dutyLocationID, serviceMemberID +func (_m *TransportationOfficesFetcher) GetCounselingOffices(appCtx appcontext.AppContext, dutyLocationID uuid.UUID, serviceMemberID uuid.UUID) (*models.TransportationOffices, error) { + ret := _m.Called(appCtx, dutyLocationID, serviceMemberID) if len(ret) == 0 { panic("no return value specified for GetCounselingOffices") @@ -56,19 +56,19 @@ func (_m *TransportationOfficesFetcher) GetCounselingOffices(appCtx appcontext.A var r0 *models.TransportationOffices var r1 error - if rf, ok := ret.Get(0).(func(appcontext.AppContext, uuid.UUID) (*models.TransportationOffices, error)); ok { - return rf(appCtx, dutyLocationID) + if rf, ok := ret.Get(0).(func(appcontext.AppContext, uuid.UUID, uuid.UUID) (*models.TransportationOffices, error)); ok { + return rf(appCtx, dutyLocationID, serviceMemberID) } - if rf, ok := ret.Get(0).(func(appcontext.AppContext, uuid.UUID) *models.TransportationOffices); ok { - r0 = rf(appCtx, dutyLocationID) + if rf, ok := ret.Get(0).(func(appcontext.AppContext, uuid.UUID, uuid.UUID) *models.TransportationOffices); ok { + r0 = rf(appCtx, dutyLocationID, serviceMemberID) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*models.TransportationOffices) } } - if rf, ok := ret.Get(1).(func(appcontext.AppContext, uuid.UUID) error); ok { - r1 = rf(appCtx, dutyLocationID) + if rf, ok := ret.Get(1).(func(appcontext.AppContext, uuid.UUID, uuid.UUID) error); ok { + r1 = rf(appCtx, dutyLocationID, serviceMemberID) } else { r1 = ret.Error(1) } @@ -106,9 +106,9 @@ func (_m *TransportationOfficesFetcher) GetTransportationOffice(appCtx appcontex return r0, r1 } -// GetTransportationOffices provides a mock function with given fields: appCtx, search, forPpm -func (_m *TransportationOfficesFetcher) GetTransportationOffices(appCtx appcontext.AppContext, search string, forPpm bool) (*models.TransportationOffices, error) { - ret := _m.Called(appCtx, search, forPpm) +// GetTransportationOffices provides a mock function with given fields: appCtx, search, forPpm, forAdminOfficeUserReqFilter +func (_m *TransportationOfficesFetcher) GetTransportationOffices(appCtx appcontext.AppContext, search string, forPpm bool, forAdminOfficeUserReqFilter bool) (*models.TransportationOffices, error) { + ret := _m.Called(appCtx, search, forPpm, forAdminOfficeUserReqFilter) if len(ret) == 0 { panic("no return value specified for GetTransportationOffices") @@ -116,19 +116,19 @@ func (_m *TransportationOfficesFetcher) GetTransportationOffices(appCtx appconte var r0 *models.TransportationOffices var r1 error - if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, bool) (*models.TransportationOffices, error)); ok { - return rf(appCtx, search, forPpm) + if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, bool, bool) (*models.TransportationOffices, error)); ok { + return rf(appCtx, search, forPpm, forAdminOfficeUserReqFilter) } - if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, bool) *models.TransportationOffices); ok { - r0 = rf(appCtx, search, forPpm) + if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, bool, bool) *models.TransportationOffices); ok { + r0 = rf(appCtx, search, forPpm, forAdminOfficeUserReqFilter) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*models.TransportationOffices) } } - if rf, ok := ret.Get(1).(func(appcontext.AppContext, string, bool) error); ok { - r1 = rf(appCtx, search, forPpm) + if rf, ok := ret.Get(1).(func(appcontext.AppContext, string, bool, bool) error); ok { + r1 = rf(appCtx, search, forPpm, forAdminOfficeUserReqFilter) } else { r1 = ret.Error(1) } diff --git a/pkg/services/move.go b/pkg/services/move.go index 8ddda83a8ec..ff3abe681de 100644 --- a/pkg/services/move.go +++ b/pkg/services/move.go @@ -31,7 +31,9 @@ type MoveFetcher interface { type MoveFetcherBulkAssignment interface { FetchMovesForBulkAssignmentCounseling(appCtx appcontext.AppContext, gbloc string, officeId uuid.UUID) ([]models.MoveWithEarliestDate, error) + FetchMovesForBulkAssignmentCloseout(appCtx appcontext.AppContext, gbloc string, officeId uuid.UUID) ([]models.MoveWithEarliestDate, error) FetchMovesForBulkAssignmentTaskOrder(appCtx appcontext.AppContext, gbloc string, officeId uuid.UUID) ([]models.MoveWithEarliestDate, error) + FetchMovesForBulkAssignmentPaymentRequest(appCtx appcontext.AppContext, gbloc string, officeId uuid.UUID) ([]models.MoveWithEarliestDate, error) } //go:generate mockery --name MoveSearcher diff --git a/pkg/services/move/move_fetcher.go b/pkg/services/move/move_fetcher.go index f7e0970c656..e0d6aea4f8a 100644 --- a/pkg/services/move/move_fetcher.go +++ b/pkg/services/move/move_fetcher.go @@ -27,7 +27,7 @@ func (f moveFetcher) FetchMove(appCtx appcontext.AppContext, locator string, sea move := &models.Move{} query := appCtx.DB(). EagerPreload("CloseoutOffice.Address", "Contractor", "ShipmentGBLOC", "LockedByOfficeUser", "LockedByOfficeUser.TransportationOffice", "AdditionalDocuments", - "AdditionalDocuments.UserUploads"). + "AdditionalDocuments.UserUploads", "CounselingOffice"). LeftJoin("move_to_gbloc", "move_to_gbloc.move_id = moves.id"). LeftJoin("office_users", "office_users.id = moves.locked_by"). Where("locator = $1", locator) @@ -135,7 +135,8 @@ func (f moveFetcherBulkAssignment) FetchMovesForBulkAssignmentCounseling(appCtx INNER JOIN mto_shipments ON mto_shipments.move_id = moves.id LEFT JOIN ppm_shipments ON ppm_shipments.shipment_id = mto_shipments.id WHERE - moves.status = 'NEEDS SERVICE COUNSELING' + mto_shipments.deleted_at IS NULL + AND moves.status = 'NEEDS SERVICE COUNSELING' AND orders.gbloc = $1 AND moves.show = $2 AND moves.sc_assigned_id IS NULL @@ -166,40 +167,149 @@ func (f moveFetcherBulkAssignment) FetchMovesForBulkAssignmentCounseling(appCtx return moves, nil } -func (f moveFetcherBulkAssignment) FetchMovesForBulkAssignmentTaskOrder(appCtx appcontext.AppContext, gbloc string, officeId uuid.UUID) ([]models.MoveWithEarliestDate, error) { +func (f moveFetcherBulkAssignment) FetchMovesForBulkAssignmentCloseout(appCtx appcontext.AppContext, gbloc string, officeId uuid.UUID) ([]models.MoveWithEarliestDate, error) { var moves []models.MoveWithEarliestDate - err := appCtx.DB(). - RawQuery(`SELECT + query := `SELECT moves.id, - MIN(LEAST( - COALESCE(mto_shipments.requested_pickup_date, '9999-12-31'), - COALESCE(mto_shipments.requested_delivery_date, '9999-12-31'), - COALESCE(ppm_shipments.expected_departure_date, '9999-12-31') - )) AS earliest_date + ppm_shipments.submitted_at AS earliest_date FROM moves INNER JOIN orders ON orders.id = moves.orders_id - INNER JOIN service_members ON orders.service_member_id = service_members.id + INNER JOIN service_members ON service_members.id = orders.service_member_id INNER JOIN mto_shipments ON mto_shipments.move_id = moves.id - LEFT JOIN ppm_shipments ON ppm_shipments.shipment_id = mto_shipments.id - LEFT JOIN move_to_gbloc ON move_to_gbloc.move_id = moves.id + INNER JOIN ppm_shipments ON ppm_shipments.shipment_id = mto_shipments.id WHERE - (moves.status IN ('APPROVALS REQUESTED', 'SUBMITTED', 'SERVICE COUNSELING COMPLETED')) + (moves.status IN ('APPROVED', 'SERVICE COUNSELING COMPLETED')) AND moves.show = $1 - AND moves.too_assigned_id IS NULL - AND (orders.orders_type NOT IN ($2, $3, $4)) - AND service_members.affiliation != 'MARINES' - AND ((mto_shipments.shipment_type != $5 AND move_to_gbloc.gbloc = $6) OR (mto_shipments.shipment_type = $7 AND orders.gbloc = $8)) - GROUP BY moves.id - ORDER BY earliest_date ASC`, - models.BoolPointer(true), - internalmessages.OrdersTypeBLUEBARK, - internalmessages.OrdersTypeWOUNDEDWARRIOR, - internalmessages.OrdersTypeSAFETY, - models.MTOShipmentTypeHHGOutOfNTS, - gbloc, - models.MTOShipmentTypeHHGOutOfNTS, - gbloc). + AND moves.sc_assigned_id IS NULL` + + switch gbloc { + case "NAVY": + query += ` AND (service_members.affiliation in ('` + string(models.AffiliationNAVY) + `'))` + case "TVCB": + query += ` AND (service_members.affiliation in ('` + string(models.AffiliationMARINES) + `'))` + case "USCG": + query += ` AND (service_members.affiliation in ('` + string(models.AffiliationCOASTGUARD) + `'))` + default: + query += ` AND moves.closeout_office_id = '` + officeId.String() + `' + AND (service_members.affiliation NOT IN ('` + + string(models.AffiliationNAVY) + `', '` + + string(models.AffiliationMARINES) + `', '` + + string(models.AffiliationCOASTGUARD) + `'))` + } + + query += ` AND (ppm_shipments.status IN ($2)) + AND (orders.orders_type NOT IN ($3, $4, $5)) + GROUP BY moves.id, ppm_shipments.submitted_at + ORDER BY earliest_date ASC` + + err := appCtx.DB().RawQuery(query, + models.BoolPointer(true), + models.PPMShipmentStatusNeedsCloseout, + internalmessages.OrdersTypeBLUEBARK, + internalmessages.OrdersTypeWOUNDEDWARRIOR, + internalmessages.OrdersTypeSAFETY).All(&moves) + + if err != nil { + return nil, fmt.Errorf("error fetching moves for office: %s with error %w", officeId, err) + } + + if len(moves) < 1 { + return nil, nil + } + + return moves, nil +} + +func (f moveFetcherBulkAssignment) FetchMovesForBulkAssignmentTaskOrder(appCtx appcontext.AppContext, gbloc string, officeId uuid.UUID) ([]models.MoveWithEarliestDate, error) { + var moves []models.MoveWithEarliestDate + + sqlQuery := ` + SELECT + moves.id, + MIN(LEAST( + COALESCE(mto_shipments.requested_pickup_date, '9999-12-31'), + COALESCE(mto_shipments.requested_delivery_date, '9999-12-31'), + COALESCE(ppm_shipments.expected_departure_date, '9999-12-31') + )) AS earliest_date + FROM moves + INNER JOIN orders ON orders.id = moves.orders_id + INNER JOIN service_members ON orders.service_member_id = service_members.id + INNER JOIN mto_shipments ON mto_shipments.move_id = moves.id + INNER JOIN duty_locations as origin_dl ON orders.origin_duty_location_id = origin_dl.id + LEFT JOIN ppm_shipments ON ppm_shipments.shipment_id = mto_shipments.id + LEFT JOIN move_to_gbloc ON move_to_gbloc.move_id = moves.id + WHERE + mto_shipments.deleted_at IS NULL + AND (moves.status IN ('APPROVALS REQUESTED', 'SUBMITTED', 'SERVICE COUNSELING COMPLETED')) + AND moves.show = $1 + AND moves.too_assigned_id IS NULL + AND (orders.orders_type NOT IN ($2, $3, $4)) + AND (moves.ppm_type IS NULL OR (moves.ppm_type = 'PARTIAL' or (moves.ppm_type = 'FULL' and origin_dl.provides_services_counseling = 'false'))) ` + if gbloc == "USMC" { + sqlQuery += ` + AND service_members.affiliation ILIKE 'MARINES' ` + } else { + sqlQuery += ` + AND service_members.affiliation != 'MARINES' + AND ((mto_shipments.shipment_type != 'HHG_OUTOF_NTS' AND move_to_gbloc.gbloc = '` + gbloc + `') + OR (mto_shipments.shipment_type = 'HHG_OUTOF_NTS' AND orders.gbloc = '` + gbloc + `')) ` + } + sqlQuery += ` + GROUP BY moves.id + ORDER BY earliest_date ASC` + + err := appCtx.DB().RawQuery(sqlQuery, + models.BoolPointer(true), + internalmessages.OrdersTypeBLUEBARK, + internalmessages.OrdersTypeWOUNDEDWARRIOR, + internalmessages.OrdersTypeSAFETY). + All(&moves) + + if err != nil { + return nil, fmt.Errorf("error fetching moves for office: %s with error %w", officeId, err) + } + + if len(moves) < 1 { + return nil, nil + } + + return moves, nil +} + +func (f moveFetcherBulkAssignment) FetchMovesForBulkAssignmentPaymentRequest(appCtx appcontext.AppContext, gbloc string, officeId uuid.UUID) ([]models.MoveWithEarliestDate, error) { + var moves []models.MoveWithEarliestDate + + sqlQuery := ` + SELECT + moves.id, + min(payment_requests.requested_at) AS earliest_date + FROM moves + INNER JOIN orders ON orders.id = moves.orders_id + INNER JOIN service_members ON orders.service_member_id = service_members.id + INNER JOIN payment_requests on payment_requests.move_id = moves.id + LEFT JOIN move_to_gbloc ON move_to_gbloc.move_id = moves.id + WHERE moves.show = $1 + AND (orders.orders_type NOT IN ($2, $3, $4)) + AND moves.tio_assigned_id IS NULL + AND payment_requests.status = 'PENDING' ` + if gbloc == "USMC" { + sqlQuery += ` + AND service_members.affiliation ILIKE 'MARINES' ` + } else { + sqlQuery += ` + AND service_members.affiliation != 'MARINES' + AND move_to_gbloc.gbloc = '` + gbloc + `' ` + } + sqlQuery += ` + GROUP BY moves.id + ORDER BY earliest_date ASC` + + err := appCtx.DB().RawQuery(sqlQuery, + models.BoolPointer(true), + internalmessages.OrdersTypeBLUEBARK, + internalmessages.OrdersTypeWOUNDEDWARRIOR, + internalmessages.OrdersTypeSAFETY). All(&moves) if err != nil { diff --git a/pkg/services/move/move_fetcher_test.go b/pkg/services/move/move_fetcher_test.go index 92fb23751bb..6a6db7f28be 100644 --- a/pkg/services/move/move_fetcher_test.go +++ b/pkg/services/move/move_fetcher_test.go @@ -3,6 +3,8 @@ package move import ( "time" + "github.com/gofrs/uuid" + "github.com/transcom/mymove/pkg/apperror" "github.com/transcom/mymove/pkg/factory" "github.com/transcom/mymove/pkg/gen/internalmessages" @@ -105,7 +107,7 @@ func (suite *MoveServiceSuite) TestMoveFetcher() { }) } -func (suite *MoveServiceSuite) TestMoveFetcherBulkAssignment() { +func (suite *MoveServiceSuite) TestMoveFetcherBulkAssignmentSC() { setupTestData := func() (services.MoveFetcherBulkAssignment, models.Move, models.TransportationOffice, models.OfficeUser) { moveFetcher := NewMoveFetcherBulkAssignment() transportationOffice := factory.BuildTransportationOffice(suite.DB(), nil, nil) @@ -149,7 +151,7 @@ func (suite *MoveServiceSuite) TestMoveFetcherBulkAssignment() { return moveFetcher, move, transportationOffice, officeUser } - suite.Run("Returns moves that fulfill the query's criteria", func() { + suite.Run("SC - Returns moves that fulfill the query's criteria", func() { moveFetcher, _, _, officeUser := setupTestData() moves, err := moveFetcher.FetchMovesForBulkAssignmentCounseling(suite.AppContextForTest(), "KKFA", officeUser.TransportationOffice.ID) suite.FatalNoError(err) @@ -434,9 +436,96 @@ func (suite *MoveServiceSuite) TestMoveFetcherBulkAssignment() { suite.Equal(assignedMove.Orders.OrdersType, internalmessages.OrdersTypePERMANENTCHANGEOFSTATION) }) - suite.Run("TOO: Returns moves that fulfill the query criteria", func() { + suite.Run("Closeout returns non Navy/USCG/USMC ppms in needs closeout status", func() { moveFetcher := NewMoveFetcherBulkAssignment() transportationOffice := factory.BuildTransportationOffice(suite.DB(), nil, nil) + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CloseoutOffice, + }, + }, []roles.RoleType{roles.RoleTypeServicesCounselor}) + + submittedAt := time.Now() + + // create non USMC/USCG/NAVY ppm in need closeout status + factory.BuildMoveWithPPMShipment(suite.DB(), []factory.Customization{ + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CloseoutOffice, + }, + { + Model: models.PPMShipment{ + Status: models.PPMShipmentStatusNeedsCloseout, + SubmittedAt: &submittedAt, + }, + }, + { + Model: models.Move{ + Status: models.MoveStatusAPPROVED, + }, + }, + }, nil) + + // create non closeout needed ppm + factory.BuildMoveWithPPMShipment(suite.DB(), []factory.Customization{ + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CloseoutOffice, + }, + { + Model: models.PPMShipment{ + Status: models.PPMShipmentStatusWaitingOnCustomer, + SubmittedAt: &submittedAt, + }, + }, + { + Model: models.Move{ + Status: models.MoveStatusAPPROVED, + }, + }, + }, nil) + + marine := models.AffiliationMARINES + marinePPM := factory.BuildPPMShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusAPPROVED, + }, + }, + { + Model: models.MTOShipment{ + ShipmentType: models.MTOShipmentTypePPM, + }, + }, + { + Model: models.PPMShipment{ + Status: models.PPMShipmentStatusNeedsCloseout, + SubmittedAt: &submittedAt, + }, + }, + { + Model: models.ServiceMember{ + Affiliation: &marine, + }, + }, + }, nil) + + moves, err := moveFetcher.FetchMovesForBulkAssignmentCloseout(suite.AppContextForTest(), "KKFA", officeUser.TransportationOffice.ID) + suite.FatalNoError(err) + suite.Equal(1, len(moves)) + suite.NotEqual(marinePPM.ID, moves[0].ID) + }) +} + +func (suite *MoveServiceSuite) TestMoveFetcherBulkAssignmentTOO() { + setupTestData := func() (services.MoveFetcherBulkAssignment, models.TransportationOffice, models.OfficeUser) { + moveFetcher := NewMoveFetcherBulkAssignment() + transportationOffice := factory.BuildTransportationOffice(suite.DB(), nil, nil) + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ { Model: transportationOffice, @@ -471,6 +560,127 @@ func (suite *MoveServiceSuite) TestMoveFetcherBulkAssignment() { }, }, nil) + return moveFetcher, transportationOffice, officeUser + } + + suite.Run("TOO: Returns moves that fulfill the query's criteria", func() { + moveFetcher, _, officeUser := setupTestData() + moves, err := moveFetcher.FetchMovesForBulkAssignmentTaskOrder(suite.AppContextForTest(), "KKFA", officeUser.TransportationOffice.ID) + suite.FatalNoError(err) + suite.Equal(2, len(moves)) + }) + + suite.Run("TOO: Does not return moves with safety, bluebark, or wounded warrior order types", func() { + moveFetcher, transportationOffice, officeUser := setupTestData() + factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.Order{ + OrdersType: internalmessages.OrdersTypeSAFETY, + }, + }, + { + Model: models.Move{ + Status: models.MoveStatusServiceCounselingCompleted, + }, + }, + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + }, nil) + factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.Order{ + OrdersType: internalmessages.OrdersTypeBLUEBARK, + }, + }, + { + Model: models.Move{ + Status: models.MoveStatusServiceCounselingCompleted, + }, + }, + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + }, nil) + factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.Order{ + OrdersType: internalmessages.OrdersTypeWOUNDEDWARRIOR, + }, + }, + { + Model: models.Move{ + Status: models.MoveStatusServiceCounselingCompleted, + }, + }, + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + }, nil) + + moves, err := moveFetcher.FetchMovesForBulkAssignmentTaskOrder(suite.AppContextForTest(), "KKFA", officeUser.TransportationOffice.ID) + suite.FatalNoError(err) + suite.Equal(2, len(moves)) + }) + + suite.Run("TOO: Does not return moves that are already assigned", func() { + moveFetcher := NewMoveFetcherBulkAssignment() + transportationOffice := factory.BuildTransportationOffice(suite.DB(), nil, nil) + + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + }, []roles.RoleType{roles.RoleTypeTOO}) + + assignedMove := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusServiceCounselingCompleted, + }, + }, + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + { + Model: officeUser, + LinkOnly: true, + Type: &factory.OfficeUsers.TOOAssignedUser, + }, + }, nil) + + moves, err := moveFetcher.FetchMovesForBulkAssignmentTaskOrder(suite.AppContextForTest(), "KKFA", officeUser.TransportationOffice.ID) + suite.FatalNoError(err) + + // confirm that the assigned move isn't returned + for _, move := range moves { + suite.NotEqual(move.ID, assignedMove.ID) + } + + // confirm that the rest of the details are correct + // move is SERVICE COUNSELING COMPLETED + suite.Equal(assignedMove.Status, models.MoveStatusServiceCounselingCompleted) + // GBLOC is the same + suite.Equal(*assignedMove.Orders.OriginDutyLocationGBLOC, officeUser.TransportationOffice.Gbloc) + // Show is true + suite.Equal(assignedMove.Show, models.BoolPointer(true)) + // Orders type isn't WW, BB, or Safety + suite.Equal(assignedMove.Orders.OrdersType, internalmessages.OrdersTypePERMANENTCHANGEOFSTATION) + }) + + suite.Run("TOO: Does not return payment requests with Marines if GBLOC not USMC", func() { + moveFetcher, transportationOffice, officeUser := setupTestData() + marine := models.AffiliationMARINES factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ { @@ -494,4 +704,360 @@ func (suite *MoveServiceSuite) TestMoveFetcherBulkAssignment() { suite.FatalNoError(err) suite.Equal(2, len(moves)) }) + + suite.Run("TOO: Only return payment requests with Marines if GBLOC is USMC", func() { + moveFetcher, transportationOffice, officeUser := setupTestData() + + marine := models.AffiliationMARINES + factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusServiceCounselingCompleted, + }, + }, + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + { + Model: models.ServiceMember{ + Affiliation: &marine, + }, + }, + }, nil) + + moves, err := moveFetcher.FetchMovesForBulkAssignmentTaskOrder(suite.AppContextForTest(), "USMC", officeUser.TransportationOffice.ID) + suite.FatalNoError(err) + suite.Equal(1, len(moves)) + }) + +} + +func (suite *MoveServiceSuite) TestMoveFetcherBulkAssignmentTIO() { + setupTestData := func() (services.MoveFetcherBulkAssignment, models.TransportationOffice, models.OfficeUser) { + moveFetcher := NewMoveFetcherBulkAssignment() + transportationOffice := factory.BuildTransportationOffice(suite.DB(), nil, nil) + + // this move has a transportation office associated with it that matches + // the TIO's transportation office and should be found + move := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + }, + }, + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + }, nil) + factory.BuildPaymentRequest(suite.DB(), []factory.Customization{ + { + Model: models.PaymentRequest{ + ID: uuid.Must(uuid.NewV4()), + IsFinal: false, + Status: models.PaymentRequestStatusPending, + RejectionReason: nil, + }, + }, + { + Model: move, + LinkOnly: true, + }, + }, nil) + + move2 := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + }, + }, + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + }, nil) + factory.BuildPaymentRequest(suite.DB(), []factory.Customization{ + { + Model: models.PaymentRequest{ + ID: uuid.Must(uuid.NewV4()), + IsFinal: false, + Status: models.PaymentRequestStatusPending, + RejectionReason: nil, + }, + }, + { + Model: move2, + LinkOnly: true, + }, + }, nil) + + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + }, []roles.RoleType{roles.RoleTypeTIO}) + + return moveFetcher, transportationOffice, officeUser + } + + suite.Run("TIO: Returns moves that fulfill the query criteria", func() { + moveFetcher, _, officeUser := setupTestData() + moves, err := moveFetcher.FetchMovesForBulkAssignmentPaymentRequest(suite.AppContextForTest(), "KKFA", officeUser.TransportationOffice.ID) + suite.FatalNoError(err) + suite.Equal(2, len(moves)) + }) + + suite.Run("Does not return moves that are already assigned", func() { + moveFetcher := NewMoveFetcherBulkAssignment() + transportationOffice := factory.BuildTransportationOffice(suite.DB(), nil, nil) + + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + }, []roles.RoleType{roles.RoleTypeTIO}) + + move := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + }, + }, + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + { + Model: officeUser, + LinkOnly: true, + Type: &factory.OfficeUsers.TIOAssignedUser, + }, + }, nil) + assignedPaymentRequest := factory.BuildPaymentRequest(suite.DB(), []factory.Customization{ + { + Model: models.PaymentRequest{ + ID: uuid.Must(uuid.NewV4()), + IsFinal: false, + Status: models.PaymentRequestStatusPending, + RejectionReason: nil, + }, + }, + { + Model: move, + LinkOnly: true, + }, + }, nil) + + moves, err := moveFetcher.FetchMovesForBulkAssignmentPaymentRequest(suite.AppContextForTest(), "KKFA", officeUser.TransportationOffice.ID) + suite.FatalNoError(err) + + // confirm that the assigned move isn't returned + for _, move := range moves { + suite.NotEqual(move.ID, assignedPaymentRequest.ID) + } + + // confirm that the rest of the details are correct + // move is APPROVALS REQUESTED STATUS + suite.Equal(assignedPaymentRequest.Status, models.PaymentRequestStatusPending) + // GBLOC is the same + suite.Equal(*move.Orders.OriginDutyLocationGBLOC, officeUser.TransportationOffice.Gbloc) + // Show is true + suite.Equal(move.Show, models.BoolPointer(true)) + // Orders type isn't WW, BB, or Safety + suite.Equal(move.Orders.OrdersType, internalmessages.OrdersTypePERMANENTCHANGEOFSTATION) + }) + + suite.Run("TIO: Does not return moves with safety, bluebark, or wounded warrior order types", func() { + moveFetcher, transportationOffice, officeUser := setupTestData() + moveSafety := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.Order{ + OrdersType: internalmessages.OrdersTypeSAFETY, + }, + }, + { + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + }, + }, + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + }, nil) + factory.BuildPaymentRequest(suite.DB(), []factory.Customization{ + { + Model: models.PaymentRequest{ + ID: uuid.Must(uuid.NewV4()), + IsFinal: false, + Status: models.PaymentRequestStatusPending, + RejectionReason: nil, + }, + }, + { + Model: moveSafety, + LinkOnly: true, + }, + }, nil) + + moveBB := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.Order{ + OrdersType: internalmessages.OrdersTypeBLUEBARK, + }, + }, + { + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + }, + }, + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + }, nil) + factory.BuildPaymentRequest(suite.DB(), []factory.Customization{ + { + Model: models.PaymentRequest{ + ID: uuid.Must(uuid.NewV4()), + IsFinal: false, + Status: models.PaymentRequestStatusPending, + RejectionReason: nil, + }, + }, + { + Model: moveBB, + LinkOnly: true, + }, + }, nil) + + moveWW := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.Order{ + OrdersType: internalmessages.OrdersTypeWOUNDEDWARRIOR, + }, + }, + { + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + }, + }, + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + }, nil) + factory.BuildPaymentRequest(suite.DB(), []factory.Customization{ + { + Model: models.PaymentRequest{ + ID: uuid.Must(uuid.NewV4()), + IsFinal: false, + Status: models.PaymentRequestStatusPending, + RejectionReason: nil, + }, + }, + { + Model: moveWW, + LinkOnly: true, + }, + }, nil) + + moves, err := moveFetcher.FetchMovesForBulkAssignmentPaymentRequest(suite.AppContextForTest(), "KKFA", officeUser.TransportationOffice.ID) + suite.FatalNoError(err) + suite.Equal(2, len(moves)) + }) + + suite.Run("TIO: Does not return payment requests with Marines if GBLOC not USMC", func() { + moveFetcher, transportationOffice, officeUser := setupTestData() + + marine := models.AffiliationMARINES + move := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + }, + }, + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + { + Model: models.ServiceMember{ + Affiliation: &marine, + }, + }, + }, nil) + factory.BuildPaymentRequest(suite.DB(), []factory.Customization{ + { + Model: models.PaymentRequest{ + ID: uuid.Must(uuid.NewV4()), + IsFinal: false, + Status: models.PaymentRequestStatusPending, + RejectionReason: nil, + }, + }, + { + Model: move, + LinkOnly: true, + }, + }, nil) + + moves, err := moveFetcher.FetchMovesForBulkAssignmentPaymentRequest(suite.AppContextForTest(), "KKFA", officeUser.TransportationOffice.ID) + suite.FatalNoError(err) + suite.Equal(2, len(moves)) + }) + + suite.Run("TIO: Only return payment requests with Marines if GBLOC is USMC", func() { + moveFetcher, transportationOffice, officeUser := setupTestData() + + marine := models.AffiliationMARINES + move := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusServiceCounselingCompleted, + }, + }, + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + { + Model: models.ServiceMember{ + Affiliation: &marine, + }, + }, + }, nil) + factory.BuildPaymentRequest(suite.DB(), []factory.Customization{ + { + Model: models.PaymentRequest{ + ID: uuid.Must(uuid.NewV4()), + IsFinal: false, + Status: models.PaymentRequestStatusPending, + RejectionReason: nil, + }, + }, + { + Model: move, + LinkOnly: true, + }, + }, nil) + + moves, err := moveFetcher.FetchMovesForBulkAssignmentPaymentRequest(suite.AppContextForTest(), "USMC", officeUser.TransportationOffice.ID) + suite.FatalNoError(err) + suite.Equal(1, len(moves)) + }) } diff --git a/pkg/services/move/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_history/move_history_fetcher_test.go b/pkg/services/move_history/move_history_fetcher_test.go index 161dde49e93..9900d97cd1a 100644 --- a/pkg/services/move_history/move_history_fetcher_test.go +++ b/pkg/services/move_history/move_history_fetcher_test.go @@ -378,9 +378,8 @@ func (suite *MoveHistoryServiceSuite) TestMoveHistoryFetcherScenarios() { mock.Anything, mock.Anything, false, - 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{ { @@ -555,7 +554,6 @@ func (suite *MoveHistoryServiceSuite) TestMoveHistoryFetcherScenarios() { 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()) @@ -630,7 +628,6 @@ func (suite *MoveHistoryServiceSuite) TestMoveHistoryFetcherScenarios() { 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()) 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 a0dee9339f1..6f2fea37c45 100644 --- a/pkg/services/move_task_order/move_task_order_fetcher.go +++ b/pkg/services/move_task_order/move_task_order_fetcher.go @@ -14,6 +14,7 @@ import ( "github.com/transcom/mymove/pkg/appcontext" "github.com/transcom/mymove/pkg/apperror" "github.com/transcom/mymove/pkg/cli" + "github.com/transcom/mymove/pkg/db/utilities" "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/services" "github.com/transcom/mymove/pkg/services/featureflag" @@ -158,7 +159,6 @@ func (f moveTaskOrderFetcher) FetchMoveTaskOrder(appCtx appcontext.AppContext, s "MTOShipments.SecondaryPickupAddress.Country", "MTOShipments.TertiaryDeliveryAddress.Country", "MTOShipments.TertiaryPickupAddress.Country", - "MTOShipments.MTOAgents", "MTOShipments.SITDurationUpdates", "MTOShipments.StorageFacility", "MTOShipments.StorageFacility.Address", @@ -209,6 +209,20 @@ func (f moveTaskOrderFetcher) FetchMoveTaskOrder(appCtx appcontext.AppContext, s mto.Orders.Entitlement.WeightAllotted = &allotment } + for i := range mto.MTOShipments { + var nonDeletedAgents models.MTOAgents + loadErr := appCtx.DB(). + Scope(utilities.ExcludeDeletedScope()). + Where("mto_shipment_id = ?", mto.MTOShipments[i].ID). + All(&nonDeletedAgents) + + if loadErr != nil { + return &models.Move{}, apperror.NewQueryError("MTOAgents", loadErr, "") + } + + mto.MTOShipments[i].MTOAgents = nonDeletedAgents + } + // 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: @@ -351,13 +365,23 @@ func (f moveTaskOrderFetcher) FetchMoveTaskOrder(appCtx appcontext.AppContext, s mto.MTOServiceItems = loadedServiceItems if mto.Orders.DestinationGBLOC == nil { - newDutyLocationGBLOC, err := models.FetchGBLOCForPostalCode(appCtx.DB(), mto.Orders.NewDutyLocation.Address.PostalCode) - if err != nil { - err = apperror.NewBadDataError("New duty location GBLOC cannot be verified") - appCtx.Logger().Error(err.Error()) - return &models.Move{}, apperror.NewQueryError("DestinationGBLOC", err, "") + var newDutyLocationGBLOC *string + if *mto.Orders.NewDutyLocation.Address.IsOconus { + newDutyLocationGBLOCOconus, err := models.FetchAddressGbloc(appCtx.DB(), mto.Orders.NewDutyLocation.Address, mto.Orders.ServiceMember) + if err != nil { + return nil, apperror.NewNotFoundError(mto.Orders.NewDutyLocation.ID, "while looking for Duty Location Oconus GBLOC") + } + newDutyLocationGBLOC = newDutyLocationGBLOCOconus + } else { + newDutyLocationGBLOCConus, err := models.FetchGBLOCForPostalCode(appCtx.DB(), mto.Orders.NewDutyLocation.Address.PostalCode) + if err != nil { + err = apperror.NewBadDataError("New duty location GBLOC cannot be verified") + appCtx.Logger().Error(err.Error()) + return &models.Move{}, apperror.NewQueryError("DestinationGBLOC", err, "") + } + newDutyLocationGBLOC = &newDutyLocationGBLOCConus.GBLOC } - mto.Orders.DestinationGBLOC = &newDutyLocationGBLOC.GBLOC + mto.Orders.DestinationGBLOC = newDutyLocationGBLOC } return mto, nil diff --git a/pkg/services/move_task_order/move_task_order_fetcher_test.go b/pkg/services/move_task_order/move_task_order_fetcher_test.go index 07565525901..3cf793bc3ba 100644 --- a/pkg/services/move_task_order/move_task_order_fetcher_test.go +++ b/pkg/services/move_task_order/move_task_order_fetcher_test.go @@ -569,6 +569,75 @@ func (suite *MoveTaskOrderServiceSuite) TestMoveTaskOrderFetcher() { suite.True(found, "Expected service item ReServiceCodePODFSC") }) + suite.Run("Success - Move contains only deleted MTOAgents", func() { + move := factory.BuildMove(suite.DB(), nil, nil) + + factory.BuildMTOAgent(suite.DB(), []factory.Customization{ + {Model: models.MTOAgent{ + MTOAgentType: models.MTOAgentReceiving, + DeletedAt: models.TimePointer(time.Now()), + }}, + {Model: move, LinkOnly: true}, + }, nil) + + searchParams := services.MoveTaskOrderFetcherParams{ + MoveTaskOrderID: move.ID, + } + + actualMTO, err := mtoFetcher.FetchMoveTaskOrder(suite.AppContextForTest(), &searchParams) + suite.NoError(err) + + suite.Equal(move.ID, actualMTO.ID) + suite.Len(actualMTO.MTOShipments[0].MTOAgents, 0, "Expected no active agents since all are deleted") + }) + + suite.Run("Success - Move contains one MTOAgent", func() { + move := factory.BuildMove(suite.DB(), nil, nil) + + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + {Model: move, + LinkOnly: true}, + }, nil) + + factory.BuildMTOAgent(suite.DB(), []factory.Customization{ + {Model: models.MTOAgent{ + MTOAgentType: models.MTOAgentReceiving, + DeletedAt: models.TimePointer(time.Now()), + }}, + {Model: move, LinkOnly: true}, + {Model: shipment, LinkOnly: true}, + }, nil) + + activeAgent := factory.BuildMTOAgent(suite.DB(), []factory.Customization{ + {Model: models.MTOAgent{ + FirstName: models.StringPointer("John"), + LastName: models.StringPointer("Doe"), + Email: models.StringPointer("John.doe@example.com"), + Phone: models.StringPointer("222-222-2222"), + MTOAgentType: models.MTOAgentReleasing, + }}, + {Model: move, LinkOnly: true}, + {Model: shipment, LinkOnly: true}, + }, nil) + + searchParams := services.MoveTaskOrderFetcherParams{ + MoveTaskOrderID: move.ID, + } + + actualMTO, err := mtoFetcher.FetchMoveTaskOrder(suite.AppContextForTest(), &searchParams) + suite.NoError(err) + + suite.Equal(move.ID, actualMTO.ID) + suite.Len(actualMTO.MTOShipments[0].MTOAgents, 1, "Expected only one active agent in the result") + + activeAgentReturned := actualMTO.MTOShipments[0].MTOAgents[0] + suite.Equal(activeAgent.FirstName, activeAgentReturned.FirstName, "First names should match") + suite.Equal(activeAgent.LastName, activeAgentReturned.LastName, "Last names should match") + suite.Equal(activeAgent.Email, activeAgentReturned.Email, "Emails should match") + suite.Equal(activeAgent.Phone, activeAgentReturned.Phone, "Phone numbers should match") + suite.Equal(activeAgent.MTOAgentType, activeAgentReturned.MTOAgentType, "Agent types should match") + }) + } func (suite *MoveTaskOrderServiceSuite) TestGetMoveTaskOrderFetcher() { diff --git a/pkg/services/move_task_order/move_task_order_updater_test.go b/pkg/services/move_task_order/move_task_order_updater_test.go index 778619788a9..b6368248eb0 100644 --- a/pkg/services/move_task_order/move_task_order_updater_test.go +++ b/pkg/services/move_task_order/move_task_order_updater_test.go @@ -81,7 +81,6 @@ func (suite *MoveTaskOrderServiceSuite) TestMoveTaskOrderUpdater_UpdateStatusSer mock.Anything, mock.Anything, false, - false, ).Return(400, nil) mtoUpdater := mt.NewMoveTaskOrderUpdater( queryBuilder, @@ -396,7 +395,6 @@ func (suite *MoveTaskOrderServiceSuite) TestMoveTaskOrderUpdater_UpdatePostCouns mock.Anything, mock.Anything, false, - false, ).Return(400, nil) setUpSignedCertificationCreatorMock := func(returnValue ...interface{}) services.SignedCertificationCreator { @@ -565,7 +563,6 @@ func (suite *MoveTaskOrderServiceSuite) TestMoveTaskOrderUpdater_ShowHide() { mock.Anything, mock.Anything, false, - false, ).Return(400, nil) setUpSignedCertificationCreatorMock := func(returnValue ...interface{}) services.SignedCertificationCreator { @@ -829,7 +826,6 @@ func (suite *MoveTaskOrderServiceSuite) TestMoveTaskOrderUpdater_MakeAvailableTo mock.Anything, mock.Anything, false, - false, ).Return(400, nil) serviceItemCreator := mtoserviceitem.NewMTOServiceItemCreator(planner, queryBuilder, moveRouter, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticPackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticShorthaulPricer(), ghcrateengine.NewDomesticOriginPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) mtoUpdater := mt.NewMoveTaskOrderUpdater(queryBuilder, serviceItemCreator, moveRouter, setUpSignedCertificationCreatorMock(nil, nil), setUpSignedCertificationUpdaterMock(nil, nil), ppmEstimator) @@ -895,7 +891,6 @@ func (suite *MoveTaskOrderServiceSuite) TestMoveTaskOrderUpdater_MakeAvailableTo mock.Anything, mock.Anything, false, - false, ).Return(400, nil) serviceItemCreator := mtoserviceitem.NewMTOServiceItemCreator(planner, queryBuilder, moveRouter, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticPackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticShorthaulPricer(), ghcrateengine.NewDomesticOriginPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) mtoUpdater := mt.NewMoveTaskOrderUpdater(queryBuilder, serviceItemCreator, moveRouter, setUpSignedCertificationCreatorMock(nil, nil), setUpSignedCertificationUpdaterMock(nil, nil), ppmEstimator) @@ -1146,7 +1141,6 @@ func (suite *MoveTaskOrderServiceSuite) TestMoveTaskOrderUpdater_UpdatePPMType() mock.Anything, mock.Anything, false, - false, ).Return(400, nil) setUpSignedCertificationCreatorMock := func(returnValue ...interface{}) services.SignedCertificationCreator { 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 6c7da2ec3be..8002d3a38d2 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 } @@ -128,12 +130,12 @@ func (o *mtoServiceItemCreator) findEstimatedPrice(appCtx appcontext.AppContext, return 0, err } if mtoShipment.PickupAddress != nil && mtoShipment.DestinationAddress != nil { - distance, err = o.planner.ZipTransitDistance(appCtx, mtoShipment.PickupAddress.PostalCode, mtoShipment.DestinationAddress.PostalCode, false, false) + distance, err = o.planner.ZipTransitDistance(appCtx, mtoShipment.PickupAddress.PostalCode, mtoShipment.DestinationAddress.PostalCode, false) if err != nil { 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 } @@ -144,12 +146,12 @@ func (o *mtoServiceItemCreator) findEstimatedPrice(appCtx appcontext.AppContext, return 0, err } if mtoShipment.PickupAddress != nil && mtoShipment.DestinationAddress != nil { - distance, err = o.planner.ZipTransitDistance(appCtx, mtoShipment.PickupAddress.PostalCode, mtoShipment.DestinationAddress.PostalCode, false, false) + distance, err = o.planner.ZipTransitDistance(appCtx, mtoShipment.PickupAddress.PostalCode, mtoShipment.DestinationAddress.PostalCode, false) if err != nil { 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 } @@ -167,13 +169,13 @@ func (o *mtoServiceItemCreator) findEstimatedPrice(appCtx appcontext.AppContext, } if mtoShipment.PickupAddress != nil && mtoShipment.DestinationAddress != nil { - distance, err = o.planner.ZipTransitDistance(appCtx, mtoShipment.PickupAddress.PostalCode, mtoShipment.DestinationAddress.PostalCode, false, false) + distance, err = o.planner.ZipTransitDistance(appCtx, mtoShipment.PickupAddress.PostalCode, mtoShipment.DestinationAddress.PostalCode, false) if err != nil { return 0, err } } - 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 } @@ -303,14 +305,14 @@ func (o *mtoServiceItemCreator) calculateSITDeliveryMiles(appCtx appcontext.AppC originalSITAddressZip = mtoShipment.PickupAddress.PostalCode } if mtoShipment.PickupAddress != nil && originalSITAddressZip != "" { - distance, err = o.planner.ZipTransitDistance(appCtx, mtoShipment.PickupAddress.PostalCode, originalSITAddressZip, false, false) + distance, err = o.planner.ZipTransitDistance(appCtx, mtoShipment.PickupAddress.PostalCode, originalSITAddressZip, false) } } if serviceItem.ReService.Code == models.ReServiceCodeDDFSIT || serviceItem.ReService.Code == models.ReServiceCodeDDASIT || serviceItem.ReService.Code == models.ReServiceCodeDDSFSC || serviceItem.ReService.Code == models.ReServiceCodeDDDSIT { // Creation: Destination SIT: distance between shipment destination address & service item destination address if mtoShipment.DestinationAddress != nil && serviceItem.SITDestinationFinalAddress != nil { - distance, err = o.planner.ZipTransitDistance(appCtx, mtoShipment.DestinationAddress.PostalCode, serviceItem.SITDestinationFinalAddress.PostalCode, false, false) + distance, err = o.planner.ZipTransitDistance(appCtx, mtoShipment.DestinationAddress.PostalCode, serviceItem.SITDestinationFinalAddress.PostalCode, false) } } if err != nil { @@ -326,6 +328,7 @@ func (o *mtoServiceItemCreator) CreateMTOServiceItem(appCtx appcontext.AppContex var err error var requestedServiceItems models.MTOServiceItems // used in case additional service items need to be auto-created var createdServiceItems models.MTOServiceItems + var createdInternationalServiceItemIds []string var move models.Move moveID := serviceItem.MoveTaskOrderID @@ -605,8 +608,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 } @@ -671,9 +674,16 @@ func (o *mtoServiceItemCreator) CreateMTOServiceItem(appCtx appcontext.AppContex } } - verrs, err = o.builder.CreateOne(txnAppCtx, requestedServiceItem) - if verrs != nil || err != nil { - return fmt.Errorf("%#v %e", verrs, err) + if mtoShipment.MarketCode == models.MarketCodeInternational { + createdInternationalServiceItemIds, err = models.CreateInternationalAccessorialServiceItemsForShipment(appCtx.DB(), *serviceItem.MTOShipmentID, models.MTOServiceItems{*serviceItem}) + if err != nil { + return err + } + } else { + verrs, err = o.builder.CreateOne(txnAppCtx, requestedServiceItem) + if verrs != nil || err != nil { + return fmt.Errorf("%#v %e", verrs, err) + } } // need isOconus information for InternationalCrates in model_to_payload @@ -683,6 +693,13 @@ func (o *mtoServiceItemCreator) CreateMTOServiceItem(appCtx appcontext.AppContex createdServiceItems = append(createdServiceItems, *requestedServiceItem) + if mtoShipment.MarketCode == models.MarketCodeInternational { + requestedServiceItem.ID, err = uuid.FromString(createdInternationalServiceItemIds[0]) + if err != nil { + return fmt.Errorf("%e", err) + } + } + // create dimensions if any for index := range requestedServiceItem.Dimensions { createDimension := &requestedServiceItem.Dimensions[index] @@ -937,3 +954,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 d2a7709b9ff..a939b438d9e 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 @@ -194,7 +194,6 @@ func (suite *MTOServiceItemServiceSuite) TestCreateMTOServiceItemWithInvalidMove 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()) serviceItemForUnapprovedMove := suite.buildValidServiceItemWithInvalidMove() @@ -224,7 +223,6 @@ func (suite *MTOServiceItemServiceSuite) TestCreateMTOServiceItem() { 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()) @@ -1036,7 +1034,6 @@ func (suite *MTOServiceItemServiceSuite) TestCreateOriginSITServiceItem() { 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()) @@ -1087,7 +1084,6 @@ func (suite *MTOServiceItemServiceSuite) TestCreateOriginSITServiceItem() { 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()) @@ -1163,7 +1159,6 @@ func (suite *MTOServiceItemServiceSuite) TestCreateOriginSITServiceItem() { 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()) @@ -1291,7 +1286,6 @@ func (suite *MTOServiceItemServiceSuite) TestCreateOriginSITServiceItem() { 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()) @@ -1327,7 +1321,6 @@ func (suite *MTOServiceItemServiceSuite) TestCreateOriginSITServiceItem() { 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()) @@ -1362,7 +1355,6 @@ func (suite *MTOServiceItemServiceSuite) TestCreateOriginSITServiceItem() { 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()) @@ -1439,7 +1431,6 @@ func (suite *MTOServiceItemServiceSuite) TestCreateOriginSITServiceItemFailToCre 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()) @@ -1475,7 +1466,6 @@ func (suite *MTOServiceItemServiceSuite) TestCreateDestSITServiceItem() { 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()) @@ -1776,7 +1766,6 @@ func (suite *MTOServiceItemServiceSuite) TestCreateDestSITServiceItem() { 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()) createdServiceItems, _, err := creator.CreateMTOServiceItem(suite.AppContextForTest(), &serviceItemDDASIT) @@ -1840,3 +1829,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 b3fee697b47..860c975f7ea 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,127 @@ func (p *mtoServiceItemUpdater) ConvertItemToCustomerExpense( return p.convertItemToCustomerExpense(appCtx, *mtoServiceItem, customerExpenseReason, convertToCustomerExpense, eTag, checkETag()) } +func (p *mtoServiceItemUpdater) findEstimatedPrice(appCtx appcontext.AppContext, serviceItem *models.MTOServiceItem, mtoShipment models.MTOShipment) (unit.Cents, error) { + if serviceItem.ReService.Code == models.ReServiceCodeDDP || + serviceItem.ReService.Code == models.ReServiceCodeDUPK || + serviceItem.ReService.Code == models.ReServiceCodeDLH || + serviceItem.ReService.Code == models.ReServiceCodeFSC || + serviceItem.ReService.Code == models.ReServiceCodeDDDSIT || + serviceItem.ReService.Code == models.ReServiceCodeDDSFSC { + + isPPM := false + if mtoShipment.ShipmentType == models.MTOShipmentTypePPM { + isPPM = true + } + + var pickupDate *time.Time + if mtoShipment.ActualPickupDate != nil { + pickupDate = mtoShipment.ActualPickupDate + } else { + if mtoShipment.RequestedPickupDate != nil { + pickupDate = mtoShipment.RequestedPickupDate + } + } + + currTime := time.Now() + var distance int + + var shipmentWeight unit.Pound + if mtoShipment.PrimeActualWeight != nil { + shipmentWeight = *mtoShipment.PrimeActualWeight + } else { + if mtoShipment.PrimeEstimatedWeight != nil { + shipmentWeight = *mtoShipment.PrimeEstimatedWeight + } else { + return 0, apperror.NewInvalidInputError(serviceItem.ID, nil, nil, "No estimated or actual weight exists for this service item.") + } + } + + contractCode, err := FetchContractCode(appCtx, currTime) + if err != nil && pickupDate != nil { + contractCode, err = FetchContractCode(appCtx, *pickupDate) + if err != nil { + return 0, err + } + } + + var price unit.Cents + + // destination + if serviceItem.ReService.Code == models.ReServiceCodeDDP { + var domesticServiceArea models.ReDomesticServiceArea + if mtoShipment.DestinationAddress != nil { + domesticServiceArea, err = fetchDomesticServiceArea(appCtx, contractCode, mtoShipment.DestinationAddress.PostalCode) + if err != nil { + return 0, err + } + } + + price, _, err = p.destinationPricer.Price(appCtx, contractCode, *pickupDate, shipmentWeight, domesticServiceArea.ServiceArea, isPPM) + if err != nil { + return 0, err + } + } + // 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 + } + } + price, _, err = p.linehaulPricer.Price(appCtx, contractCode, *pickupDate, unit.Miles(distance), shipmentWeight, domesticServiceArea.ServiceArea, isPPM) + if err != nil { + return 0, err + } + } + // unpacking + if serviceItem.ReService.Code == models.ReServiceCodeDUPK { + domesticServiceArea, err := fetchDomesticServiceArea(appCtx, contractCode, mtoShipment.DestinationAddress.PostalCode) + if err != nil { + return 0, err + } + price, _, err = p.unpackPricer.Price(appCtx, contractCode, *pickupDate, shipmentWeight, domesticServiceArea.ServicesSchedule, isPPM) + if err != nil { + return 0, err + } + } + // 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 + } + price, _, err = p.fuelSurchargePricer.Price(appCtx, *pickupDate, unit.Miles(distance), shipmentWeight, fscWeightBasedDistanceMultiplierFloat, eiaFuelPrice, isPPM) + if err != nil { + return 0, err + } + + } + return price, nil + } + return 0, nil +} + func (p *mtoServiceItemUpdater) findServiceItem(appCtx appcontext.AppContext, serviceItemID uuid.UUID) (*models.MTOServiceItem, error) { var serviceItem models.MTOServiceItem err := appCtx.DB().Q().EagerPreload( @@ -271,7 +398,7 @@ func (p *mtoServiceItemUpdater) updateServiceItem(appCtx appcontext.AppContext, if serviceItem.ReService.Code == models.ReServiceCodeDDDSIT || serviceItem.ReService.Code == models.ReServiceCodeDDSFSC { // Destination SIT: distance between shipment destination address & service item ORIGINAL destination address - milesCalculated, err := p.planner.ZipTransitDistance(appCtx, mtoShipment.DestinationAddress.PostalCode, serviceItem.SITDestinationOriginalAddress.PostalCode, false, false) + milesCalculated, err := p.planner.ZipTransitDistance(appCtx, mtoShipment.DestinationAddress.PostalCode, serviceItem.SITDestinationOriginalAddress.PostalCode, false) if err != nil { return nil, err } @@ -283,7 +410,7 @@ func (p *mtoServiceItemUpdater) updateServiceItem(appCtx appcontext.AppContext, if serviceItem.ReService.Code == models.ReServiceCodeDOPSIT || serviceItem.ReService.Code == models.ReServiceCodeDOSFSC { // Origin SIT: distance between shipment pickup address & service item ORIGINAL pickup address - milesCalculated, err := p.planner.ZipTransitDistance(appCtx, mtoShipment.PickupAddress.PostalCode, serviceItem.SITOriginHHGOriginalAddress.PostalCode, false, false) + milesCalculated, err := p.planner.ZipTransitDistance(appCtx, mtoShipment.PickupAddress.PostalCode, serviceItem.SITOriginHHGOriginalAddress.PostalCode, false) if err != nil { return nil, err } @@ -327,6 +454,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, @@ -411,7 +553,7 @@ func (p *mtoServiceItemUpdater) UpdateMTOServiceItemPrime( func calculateOriginSITRequiredDeliveryDate(appCtx appcontext.AppContext, shipment models.MTOShipment, planner route.Planner, sitCustomerContacted *time.Time, sitDepartureDate *time.Time) (*time.Time, error) { // Get a distance calculation between pickup and destination addresses. - distance, err := planner.ZipTransitDistance(appCtx, shipment.PickupAddress.PostalCode, shipment.DestinationAddress.PostalCode, false, false) + distance, err := planner.ZipTransitDistance(appCtx, shipment.PickupAddress.PostalCode, shipment.DestinationAddress.PostalCode, false) if err != nil { return nil, apperror.NewUnprocessableEntityError("cannot calculate distance between pickup and destination addresses") @@ -658,7 +800,7 @@ func (p *mtoServiceItemUpdater) UpdateMTOServiceItem( destZip = shipment.DestinationAddress.PostalCode } // we need to get the mileage from DTOD first, the db proc will consume that - mileage, err := p.planner.ZipTransitDistance(appCtx, pickupZip, destZip, true, true) + mileage, err := p.planner.ZipTransitDistance(appCtx, pickupZip, destZip, true) if err != nil { return err } 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 1d04aceabdd..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" @@ -51,9 +52,8 @@ func (suite *MTOServiceItemServiceSuite) TestMTOServiceItemUpdater() { mock.Anything, mock.Anything, false, - 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()) @@ -330,7 +330,6 @@ func (suite *MTOServiceItemServiceSuite) TestMTOServiceItemUpdater() { mock.Anything, mock.Anything, false, - false, ).Return(1234, nil) ghcDomesticTransitTime := models.GHCDomesticTransitTime{ @@ -456,7 +455,6 @@ func (suite *MTOServiceItemServiceSuite) TestMTOServiceItemUpdater() { mock.Anything, mock.Anything, false, - false, ).Return(1234, nil) ghcDomesticTransitTime := models.GHCDomesticTransitTime{ @@ -586,7 +584,6 @@ func (suite *MTOServiceItemServiceSuite) TestMTOServiceItemUpdater() { mock.Anything, mock.Anything, false, - false, ).Return(1234, nil) ghcDomesticTransitTime := models.GHCDomesticTransitTime{ @@ -714,7 +711,6 @@ func (suite *MTOServiceItemServiceSuite) TestMTOServiceItemUpdater() { mock.Anything, mock.Anything, false, - false, ).Return(1234, nil) ghcDomesticTransitTime := models.GHCDomesticTransitTime{ @@ -792,7 +788,6 @@ func (suite *MTOServiceItemServiceSuite) TestMTOServiceItemUpdater() { mock.Anything, mock.Anything, false, - false, ).Return(1234, nil) ghcDomesticTransitTime := models.GHCDomesticTransitTime{ @@ -864,7 +859,6 @@ func (suite *MTOServiceItemServiceSuite) TestMTOServiceItemUpdater() { mock.Anything, mock.Anything, false, - false, ).Return(1234, nil) ghcDomesticTransitTime := models.GHCDomesticTransitTime{ @@ -974,7 +968,6 @@ func (suite *MTOServiceItemServiceSuite) TestMTOServiceItemUpdater() { mock.Anything, mock.Anything, false, - false, ).Return(1234, nil) ghcDomesticTransitTime := models.GHCDomesticTransitTime{ @@ -1091,7 +1084,6 @@ func (suite *MTOServiceItemServiceSuite) TestMTOServiceItemUpdater() { mock.Anything, mock.Anything, false, - false, ).Return(1234, nil) ghcDomesticTransitTime := models.GHCDomesticTransitTime{ @@ -1253,7 +1245,6 @@ func (suite *MTOServiceItemServiceSuite) TestMTOServiceItemUpdater() { mock.Anything, mock.Anything, false, - false, ).Return(1234, nil) ghcDomesticTransitTime := models.GHCDomesticTransitTime{ @@ -1365,7 +1356,6 @@ func (suite *MTOServiceItemServiceSuite) TestMTOServiceItemUpdater() { mock.Anything, mock.Anything, false, - false, ).Return(1234, apperror.UnprocessableEntityError{}) ghcDomesticTransitTime := models.GHCDomesticTransitTime{ @@ -1401,7 +1391,6 @@ func (suite *MTOServiceItemServiceSuite) TestMTOServiceItemUpdater() { "50314", "98158", true, - true, ).Return(1000, nil) ghcDomesticTransitTime := models.GHCDomesticTransitTime{ @@ -2180,9 +2169,8 @@ func (suite *MTOServiceItemServiceSuite) TestUpdateMTOServiceItemStatus() { mock.Anything, mock.Anything, false, - 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("") @@ -2814,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 e6111caa531..756543e7c67 100644 --- a/pkg/services/mto_service_item/mto_service_item_validators.go +++ b/pkg/services/mto_service_item/mto_service_item_validators.go @@ -45,6 +45,11 @@ var allSITServiceItemsToCheck = []models.ReServiceCode{ models.ReServiceCodeDOSFSC, } +var allAccessorialServiceItemsToCheck = []models.ReServiceCode{ + models.ReServiceCodeIDSHUT, + models.ReServiceCodeIOSHUT, +} + var destSITServiceItems = []models.ReServiceCode{ models.ReServiceCodeDDDSIT, models.ReServiceCodeDDASIT, @@ -338,6 +343,39 @@ func (v *updateMTOServiceItemData) checkOldServiceItemStatus(_ appcontext.AppCon } } + if slices.Contains(allAccessorialServiceItemsToCheck, serviceItemData.oldServiceItem.ReService.Code) { + 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 + + if serviceItemData.updatedServiceItem.ReService.Code.String() != "" && serviceItemData.updatedServiceItem.ReService.Code.String() != serviceItemData.oldServiceItem.ReService.Code.String() { + invalidFieldChange = true + } + + if serviceItemData.updatedServiceItem.Reason != nil { + invalidFieldChange = true + } + + if serviceItemData.updatedServiceItem.RequestedApprovalsRequestedStatus != nil { + invalidFieldChange = true + } + + 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.") + } + + 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.") + } + } + return nil } @@ -382,7 +420,7 @@ func (v *updateMTOServiceItemData) checkPrimeAvailability(appCtx appcontext.AppC // checkNonPrimeFields checks that no fields were modified that are not allowed to be updated by the Prime func (v *updateMTOServiceItemData) checkNonPrimeFields(_ appcontext.AppContext) error { - if v.updatedServiceItem.Status != "" && v.updatedServiceItem.Status != v.oldServiceItem.Status && (!slices.Contains(allSITServiceItemsToCheck, v.oldServiceItem.ReService.Code)) { + if v.updatedServiceItem.Status != "" && v.updatedServiceItem.Status != v.oldServiceItem.Status && (!slices.Contains(allAccessorialServiceItemsToCheck, v.oldServiceItem.ReService.Code)) && (!slices.Contains(allSITServiceItemsToCheck, v.oldServiceItem.ReService.Code)) { v.verrs.Add("status", "cannot be updated") } @@ -648,6 +686,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 diff --git a/pkg/services/mto_shipment/mto_shipment_address_updater.go b/pkg/services/mto_shipment/mto_shipment_address_updater.go index b000991dea4..4b6efc1d478 100644 --- a/pkg/services/mto_shipment/mto_shipment_address_updater.go +++ b/pkg/services/mto_shipment/mto_shipment_address_updater.go @@ -72,9 +72,9 @@ func UpdateOriginSITServiceItemSITDeliveryMiles(planner route.Planner, shipment // Origin SIT: distance between shipment pickup address & service item ORIGINAL pickup address if serviceItem.SITOriginHHGOriginalAddress != nil { - milesCalculated, err = planner.ZipTransitDistance(appCtx, newAddress.PostalCode, serviceItem.SITOriginHHGOriginalAddress.PostalCode, false, false) + milesCalculated, err = planner.ZipTransitDistance(appCtx, newAddress.PostalCode, serviceItem.SITOriginHHGOriginalAddress.PostalCode, false) } else { - milesCalculated, err = planner.ZipTransitDistance(appCtx, oldAddress.PostalCode, newAddress.PostalCode, false, false) + milesCalculated, err = planner.ZipTransitDistance(appCtx, oldAddress.PostalCode, newAddress.PostalCode, false) } if err != nil { return nil, err diff --git a/pkg/services/mto_shipment/mto_shipment_address_updater_test.go b/pkg/services/mto_shipment/mto_shipment_address_updater_test.go index 53f23a5ee4c..4eace1a7413 100644 --- a/pkg/services/mto_shipment/mto_shipment_address_updater_test.go +++ b/pkg/services/mto_shipment/mto_shipment_address_updater_test.go @@ -20,7 +20,6 @@ func (suite *MTOShipmentServiceSuite) TestUpdateMTOShipmentAddress() { mock.Anything, mock.Anything, false, - false, ).Return(400, nil) addressCreator := address.NewAddressCreator() addressUpdater := address.NewAddressUpdater() @@ -172,7 +171,6 @@ func (suite *MTOShipmentServiceSuite) TestUpdateMTOShipmentAddress() { mock.Anything, mock.Anything, false, - false, ).Return(465, nil) mtoServiceItems, _ := UpdateOriginSITServiceItemSITDeliveryMiles(planner, &externalShipment, &newAddress, &oldAddress, suite.AppContextForTest()) suite.Equal(2, len(*mtoServiceItems)) 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 e83e6a6e223..a9735170435 100644 --- a/pkg/services/mto_shipment/mto_shipment_updater.go +++ b/pkg/services/mto_shipment/mto_shipment_updater.go @@ -844,7 +844,19 @@ func (f *mtoShipmentUpdater) updateShipmentRecord(appCtx appcontext.AppContext, } // when populating the market_code column, it is considered domestic if both pickup & dest are CONUS addresses - newShipment = models.DetermineShipmentMarketCode(newShipment) + if newShipment.ShipmentType != models.MTOShipmentTypePPM { + newShipment = models.DetermineShipmentMarketCode(newShipment) + } + + // 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 := CalculateRequiredDeliveryDateForInternationalShipment(appCtx, *newShipment.PickupAddress, *newShipment.DestinationAddress, *newShipment.ScheduledPickupDate, newShipment.ShipmentType) + if err != nil { + return err + } + + newShipment.RequiredDeliveryDate = &calculatedRDD + } if err := txnAppCtx.DB().Update(newShipment); err != nil { return err @@ -877,7 +889,7 @@ func (f *mtoShipmentUpdater) updateShipmentRecord(appCtx appcontext.AppContext, destZip = newShipment.DestinationAddress.PostalCode } // we need to get the mileage from DTOD first, the db proc will consume that - mileage, err := f.planner.ZipTransitDistance(appCtx, pickupZip, destZip, true, true) + mileage, err := f.planner.ZipTransitDistance(appCtx, pickupZip, destZip, true) if err != nil { return err } @@ -1040,6 +1052,7 @@ func (o *mtoShipmentStatusUpdater) createShipmentServiceItems(appCtx appcontext. func (o *mtoShipmentStatusUpdater) setRequiredDeliveryDate(appCtx appcontext.AppContext, shipment *models.MTOShipment) error { if shipment.ScheduledPickupDate != nil && shipment.RequiredDeliveryDate == nil && + shipment.ShipmentType != models.MTOShipmentTypeUnaccompaniedBaggage && (shipment.PrimeEstimatedWeight != nil || shipment.NTSRecordedWeight != nil) { var pickupLocation *models.Address @@ -1073,12 +1086,19 @@ 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) if calcErr != nil { return calcErr } shipment.RequiredDeliveryDate = requiredDeliveryDate + } else if shipment.ShipmentType == models.MTOShipmentTypeUnaccompaniedBaggage && shipment.ScheduledPickupDate != nil && !shipment.ScheduledDeliveryDate.IsZero() { + requiredDeliveryDate, calcErr := CalculateRequiredDeliveryDateForInternationalShipment(appCtx, *shipment.PickupAddress, *shipment.DestinationAddress, *shipment.ScheduledPickupDate, shipment.ShipmentType) + if calcErr != nil { + return calcErr + } + + shipment.RequiredDeliveryDate = &requiredDeliveryDate } return nil @@ -1190,21 +1210,10 @@ 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) (*time.Time, error) { internationalShipment := marketCode == models.MarketCodeInternational - // Get a distance calculation between pickup and destination addresses. - distance, err := planner.ZipTransitDistance(appCtx, pickupAddress.PostalCode, destinationAddress.PostalCode, false, internationalShipment) + + distance, err := planner.ZipTransitDistance(appCtx, pickupAddress.PostalCode, destinationAddress.PostalCode, internationalShipment) if err != nil { return nil, err } @@ -1223,23 +1232,100 @@ 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 + } } } - // Add an extra 10 days for being in AK - requiredDeliveryDate = requiredDeliveryDate.AddDate(0, 0, 10) + + if intlTransTime.HhgTransitTime != nil { + requiredDeliveryDate = requiredDeliveryDate.AddDate(0, 0, *intlTransTime.HhgTransitTime) + } } // return the value return &requiredDeliveryDate, nil } +// CalculateRequiredDeliveryDateForInternationalShipment function is used to get the Required delivery Date of a UB shipment by finding the re_intl_transit_time using the origin and destination address rate areas. +// The transit time is then added to the day after the pickup date then that date is used as the required delivery date for the UB shipment. +func CalculateRequiredDeliveryDateForInternationalShipment(appCtx appcontext.AppContext, pickupAddress models.Address, destinationAddress models.Address, pickupDate time.Time, shipmentType models.MTOShipmentType) (time.Time, error) { + + // Transit times does not include the pickup date. Setting the required delivery date to the day after pickup date + rdd := pickupDate.AddDate(0, 0, 1) + + // get the contract id + contractID, err := models.FetchContractId(appCtx.DB(), pickupDate) + if err != nil { + return rdd, err + } + + // get the rate area id for the origin address + originRateAreaID, err := models.FetchRateAreaID(appCtx.DB(), pickupAddress.ID, nil, contractID) + if err != nil { + return rdd, err + } + + // get the rate area id for the destination address + destRateAreaID, err := models.FetchRateAreaID(appCtx.DB(), destinationAddress.ID, nil, contractID) + if err != nil { + return rdd, err + } + + // lookup the intl transit time + internationalTransitTime, err := models.FetchInternationalTransitTime(appCtx.DB(), originRateAreaID, destRateAreaID) + if err != nil { + return rdd, err + } + + // rdd plus the intl ub transit time + return rdd.AddDate(0, 0, *internationalTransitTime.UbTransitTime), nil +} + // This private function is used to generically construct service items when shipments are approved. func constructMTOServiceItemModels(shipmentID uuid.UUID, mtoID uuid.UUID, reServiceCodes []models.ReServiceCode) models.MTOServiceItems { serviceItems := make(models.MTOServiceItems, len(reServiceCodes)) @@ -1355,11 +1441,11 @@ func UpdateDestinationSITServiceItemsSITDeliveryMiles(planner route.Planner, app if TOOApprovalRequired { if serviceItem.SITDestinationOriginalAddress != nil { // if TOO approval was required, shipment destination address has been updated at this point - milesCalculated, err = planner.ZipTransitDistance(appCtx, shipment.DestinationAddress.PostalCode, serviceItem.SITDestinationOriginalAddress.PostalCode, false, false) + milesCalculated, err = planner.ZipTransitDistance(appCtx, shipment.DestinationAddress.PostalCode, serviceItem.SITDestinationOriginalAddress.PostalCode, false) } } else { // if TOO approval was not required, use the newAddress - milesCalculated, err = planner.ZipTransitDistance(appCtx, newAddress.PostalCode, serviceItem.SITDestinationOriginalAddress.PostalCode, false, false) + milesCalculated, err = planner.ZipTransitDistance(appCtx, newAddress.PostalCode, serviceItem.SITDestinationOriginalAddress.PostalCode, false) } if err != nil { return err diff --git a/pkg/services/mto_shipment/mto_shipment_updater_test.go b/pkg/services/mto_shipment/mto_shipment_updater_test.go index 0b5790e625f..44ab1d6a386 100644 --- a/pkg/services/mto_shipment/mto_shipment_updater_test.go +++ b/pkg/services/mto_shipment/mto_shipment_updater_test.go @@ -52,7 +52,6 @@ func (suite *MTOShipmentServiceSuite) TestMTOShipmentUpdater() { mock.Anything, mock.Anything, false, - false, ).Return(1000, nil) moveRouter := moveservices.NewMoveRouter() waf := entitlements.NewWeightAllotmentFetcher() @@ -157,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) @@ -192,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") @@ -212,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) @@ -467,7 +473,6 @@ func (suite *MTOShipmentServiceSuite) TestMTOShipmentUpdater() { mock.AnythingOfType("*appcontext.appContext"), "50314", "99505", - false, true, ).Return(1000, nil) planner.On("ZipTransitDistance", @@ -475,7 +480,6 @@ func (suite *MTOShipmentServiceSuite) TestMTOShipmentUpdater() { "97220", "99505", true, - true, ).Return(1000, nil) ghcDomesticTransitTime := models.GHCDomesticTransitTime{ @@ -672,7 +676,6 @@ func (suite *MTOShipmentServiceSuite) TestMTOShipmentUpdater() { mock.AnythingOfType("*appcontext.appContext"), "50314", "99505", - false, true, ).Return(1000, nil) planner.On("ZipTransitDistance", @@ -680,7 +683,6 @@ func (suite *MTOShipmentServiceSuite) TestMTOShipmentUpdater() { "50314", "97220", true, - true, ).Return(1000, nil) ghcDomesticTransitTime := models.GHCDomesticTransitTime{ @@ -2199,7 +2201,6 @@ func (suite *MTOShipmentServiceSuite) TestUpdateMTOShipmentStatus() { mock.AnythingOfType("string"), mock.AnythingOfType("string"), false, - false, ).Return(500, nil).Run(func(args mock.Arguments) { TransitDistancePickupArg = args.Get(1).(string) TransitDistanceDestinationArg = args.Get(2).(string) @@ -2468,6 +2469,137 @@ 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}) + + 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) + fmt.Println("fetchedShipment.RequiredDeliveryDate") + fmt.Println(fetchedShipment.RequiredDeliveryDate) + suite.Equal(rdd20DaysDate.Format(time.RFC3339), fetchedShipment.RequiredDeliveryDate.Format(time.RFC3339)) + } + }) + suite.Run("Cannot set SUBMITTED status on shipment via UpdateMTOShipmentStatus", func() { setupTestData() @@ -3361,7 +3493,6 @@ func (suite *MTOShipmentServiceSuite) TestUpdateStatusServiceItems() { mock.Anything, mock.Anything, false, - false, ).Return(400, nil) siCreator := mtoserviceitem.NewMTOServiceItemCreator(planner, builder, moveRouter, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticPackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticShorthaulPricer(), ghcrateengine.NewDomesticOriginPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) updater := NewMTOShipmentStatusUpdater(builder, siCreator, planner) @@ -3554,3 +3685,262 @@ 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.Anything, + mock.Anything, + false, + ).Return(1000, nil) + moveRouter := moveservices.NewMoveRouter() + waf := entitlements.NewWeightAllotmentFetcher() + moveWeights := moveservices.NewMoveWeights(NewShipmentReweighRequester(), waf) + mockShipmentRecalculator := mockservices.PaymentRequestShipmentRecalculator{} + mockShipmentRecalculator.On("ShipmentRecalculatePaymentRequest", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("uuid.UUID"), + ).Return(&models.PaymentRequests{}, nil) + mockSender := setUpMockNotificationSender() + addressCreator := address.NewAddressCreator() + addressUpdater := address.NewAddressUpdater() + + mtoShipmentUpdaterPrime := NewPrimeMTOShipmentUpdater(builder, fetcher, planner, moveRouter, moveWeights, mockSender, &mockShipmentRecalculator, addressUpdater, addressCreator) + + suite.Run("should error when unable to find IntlTransit time when updating UB Shipment", func() { + testdatagen.FetchOrMakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + StartDate: time.Now().AddDate(-1, 0, 0), + EndDate: time.Now().AddDate(1, 0, 0), + }, + }) + + moveWithUb := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) + + testScheduledPickupDate := time.Now().AddDate(0, -5, 0) + testScheduledPickupDate2 := time.Now().AddDate(0, -2, 0) + primeEstimatedWeight := unit.Pound(9000) + + oldUBMTOShipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + MarketCode: models.MarketCodeInternational, + Status: models.MTOShipmentStatusApproved, + ShipmentType: models.MTOShipmentTypeUnaccompaniedBaggage, + PrimeEstimatedWeight: &primeEstimatedWeight, + ScheduledPickupDate: &testScheduledPickupDate, + }, + }, + { + Model: moveWithUb, + LinkOnly: true, + }, + }, nil) + oldUBMTOShipment.ScheduledPickupDate = &testScheduledPickupDate + mtoShipment := models.MTOShipment{ + ID: oldUBMTOShipment.ID, + ShipmentType: models.MTOShipmentTypeUnaccompaniedBaggage, + ScheduledPickupDate: &testScheduledPickupDate2, + } + + eTag := etag.GenerateEtag(oldUBMTOShipment.UpdatedAt) + session := auth.Session{} + + updatedMTOShipment, err := mtoShipmentUpdaterPrime.UpdateMTOShipment(suite.AppContextWithSessionForTest(&session), &mtoShipment, eTag, "test") + + suite.Error(err) + suite.Nil(updatedMTOShipment) + suite.IsType(apperror.QueryError{}, err) + queryErr := err.(apperror.QueryError) + wrappedErr := queryErr.Unwrap() + suite.IsType(apperror.QueryError{}, wrappedErr) + suite.Equal("could not look up intl transit time", wrappedErr.Error()) + }) + + suite.Run("should update requiredDeliveryDate when scheduledPickupDate exist and shipment status is approved", func() { + + testdatagen.FetchOrMakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + StartDate: time.Now().AddDate(-1, 0, 0), + EndDate: time.Now().AddDate(1, 0, 0), + }, + }) + + moveWithUb := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) + + testScheduledPickupDate := time.Now().AddDate(0, -5, 0) + primeEstimatedWeight := unit.Pound(9000) + + usPostRegionPickup, err := uuid.FromString("d47d5da4-10d2-41f9-9985-d7dd0f05d98c") + suite.NoError(err) + + usPostRegionDest, err := uuid.FromString("4cb599d1-c12a-43e3-988c-d43d5641f291") + suite.NoError(err) + + // Create the addresses + pickupAddress := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "987 Other Avenue", + StreetAddress2: models.StringPointer("P.O. Box 1234"), + StreetAddress3: models.StringPointer("c/o Another Person"), + City: "Columbia", + State: "SC", + PostalCode: "29228", + IsOconus: models.BoolPointer(false), + UsPostRegionCityID: &usPostRegionPickup, + }, + }, + }, nil) + + destinationAddress := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "987 Other Avenue", + StreetAddress2: models.StringPointer("P.O. Box 12345"), + StreetAddress3: models.StringPointer("c/o Another Person"), + City: "Eielson AFB", + State: "AK", + PostalCode: "99702", + IsOconus: models.BoolPointer(true), + UsPostRegionCityID: &usPostRegionDest, + }, + }, + }, nil) + + var requiredDeliveryDate time.Time + oldUBMTOShipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + MarketCode: models.MarketCodeInternational, + Status: models.MTOShipmentStatusSubmitted, + ShipmentType: models.MTOShipmentTypeUnaccompaniedBaggage, + PrimeEstimatedWeight: &primeEstimatedWeight, + ScheduledPickupDate: &testScheduledPickupDate, + PickupAddressID: &pickupAddress.ID, + DestinationAddressID: &destinationAddress.ID, + RequiredDeliveryDate: &requiredDeliveryDate, + }, + }, + { + Model: moveWithUb, + LinkOnly: true, + }, + }, nil) + + suite.True(oldUBMTOShipment.RequiredDeliveryDate.IsZero()) + + eTag := etag.GenerateEtag(oldUBMTOShipment.UpdatedAt) + + session := auth.Session{} + builder := query.NewQueryBuilder() + moveRouter := moveservices.NewMoveRouter() + planner := &mocks.Planner{} + planner.On("ZipTransitDistance", + mock.AnythingOfType("*appcontext.appContext"), + mock.Anything, + mock.Anything, + ).Return(400, nil) + siCreator := mtoserviceitem.NewMTOServiceItemCreator(planner, builder, moveRouter, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticPackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticShorthaulPricer(), ghcrateengine.NewDomesticOriginPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) + updater := NewMTOShipmentStatusUpdater(builder, siCreator, planner) + + updatedShipment, err := updater.UpdateMTOShipmentStatus(suite.AppContextWithSessionForTest(&session), oldUBMTOShipment.ID, models.MTOShipmentStatusApproved, nil, nil, eTag) + + suite.Nil(err) + suite.NotNil(updatedShipment) + suite.NotNil(updatedShipment.RequiredDeliveryDate) + }) + + suite.Run("should update requiredDeliveryDate when scheduledPickupDate is updated", func() { + testdatagen.FetchOrMakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + StartDate: time.Now().AddDate(-1, 0, 0), + EndDate: time.Now().AddDate(1, 0, 0), + }, + }) + + moveWithUb := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) + + testScheduledPickupDate := time.Now().AddDate(0, -5, 0) + testScheduledPickupDate2 := time.Now().AddDate(0, -2, 0) + primeEstimatedWeight := unit.Pound(9000) + + usPostRegionPickup, err := uuid.FromString("d47d5da4-10d2-41f9-9985-d7dd0f05d98c") + suite.NoError(err) + + usPostRegionDest, err := uuid.FromString("4cb599d1-c12a-43e3-988c-d43d5641f291") + suite.NoError(err) + + // Create the addresses + pickupAddress := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "987 Other Avenue", + StreetAddress2: models.StringPointer("P.O. Box 1234"), + StreetAddress3: models.StringPointer("c/o Another Person"), + City: "Columbia", + State: "SC", + PostalCode: "29228", + IsOconus: models.BoolPointer(false), + UsPostRegionCityID: &usPostRegionPickup, + }, + }, + }, nil) + + destinationAddress := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "987 Other Avenue", + StreetAddress2: models.StringPointer("P.O. Box 12345"), + StreetAddress3: models.StringPointer("c/o Another Person"), + City: "Eielson AFB", + State: "AK", + PostalCode: "99702", + IsOconus: models.BoolPointer(true), + UsPostRegionCityID: &usPostRegionDest, + }, + }, + }, nil) + + var requiredDeliveryDate time.Time + oldUBMTOShipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + MarketCode: models.MarketCodeInternational, + Status: models.MTOShipmentStatusApproved, + ShipmentType: models.MTOShipmentTypeUnaccompaniedBaggage, + PrimeEstimatedWeight: &primeEstimatedWeight, + ScheduledPickupDate: &testScheduledPickupDate, + PickupAddressID: &pickupAddress.ID, + DestinationAddressID: &destinationAddress.ID, + RequiredDeliveryDate: &requiredDeliveryDate, + }, + }, + { + Model: moveWithUb, + LinkOnly: true, + }, + }, nil) + + suite.True(oldUBMTOShipment.RequiredDeliveryDate.IsZero()) + + mtoShipment := models.MTOShipment{ + ID: oldUBMTOShipment.ID, + ShipmentType: models.MTOShipmentTypeUnaccompaniedBaggage, + ScheduledPickupDate: &testScheduledPickupDate2, + } + + eTag := etag.GenerateEtag(oldUBMTOShipment.UpdatedAt) + session := auth.Session{} + + updatedMTOShipment, err := mtoShipmentUpdaterPrime.UpdateMTOShipment(suite.AppContextWithSessionForTest(&session), &mtoShipment, eTag, "test") + + suite.Nil(err) + suite.NotNil(updatedMTOShipment) + suite.NotNil(updatedMTOShipment.RequiredDeliveryDate) + }) +} diff --git a/pkg/services/mto_shipment/rules.go b/pkg/services/mto_shipment/rules.go index 0fe7e481ebc..63749cc54be 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) 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 b285684a62b..1072bdf7a24 100644 --- a/pkg/services/mto_shipment/shipment_approver.go +++ b/pkg/services/mto_shipment/shipment_approver.go @@ -108,7 +108,7 @@ func (f *shipmentApprover) ApproveShipment(appCtx appcontext.AppContext, shipmen destZip = shipment.DestinationAddress.PostalCode } // we need to get the mileage from DTOD first, the db proc will consume that - mileage, err := f.planner.ZipTransitDistance(appCtx, pickupZip, destZip, true, true) + mileage, err := f.planner.ZipTransitDistance(appCtx, pickupZip, destZip, true) if err != nil { return err } @@ -186,6 +186,7 @@ func (f *shipmentApprover) findShipment(appCtx appcontext.AppContext, shipmentID func (f *shipmentApprover) setRequiredDeliveryDate(appCtx appcontext.AppContext, shipment *models.MTOShipment) error { if shipment.ScheduledPickupDate != nil && shipment.RequiredDeliveryDate == nil && + shipment.ShipmentType != models.MTOShipmentTypeUnaccompaniedBaggage && (shipment.PrimeEstimatedWeight != nil || (shipment.ShipmentType == models.MTOShipmentTypeHHGOutOfNTS && shipment.NTSRecordedWeight != nil)) { @@ -213,12 +214,19 @@ 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) if calcErr != nil { return calcErr } shipment.RequiredDeliveryDate = requiredDeliveryDate + } else if shipment.ScheduledPickupDate != nil && !shipment.ScheduledPickupDate.IsZero() && shipment.ShipmentType == models.MTOShipmentTypeUnaccompaniedBaggage { + requiredDeliveryDate, calcErr := CalculateRequiredDeliveryDateForInternationalShipment(appCtx, *shipment.PickupAddress, *shipment.DestinationAddress, *shipment.ScheduledPickupDate, shipment.ShipmentType) + if calcErr != nil { + return calcErr + } + + shipment.RequiredDeliveryDate = &requiredDeliveryDate } return nil diff --git a/pkg/services/mto_shipment/shipment_approver_test.go b/pkg/services/mto_shipment/shipment_approver_test.go index 4c4f116244f..b40d2612591 100644 --- a/pkg/services/mto_shipment/shipment_approver_test.go +++ b/pkg/services/mto_shipment/shipment_approver_test.go @@ -94,7 +94,6 @@ func (suite *MTOShipmentServiceSuite) createApproveShipmentSubtestData() (subtes mock.Anything, mock.Anything, false, - false, ).Return(400, nil) siCreator := mtoserviceitem.NewMTOServiceItemCreator(planner, builder, moveRouter, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticPackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticShorthaulPricer(), ghcrateengine.NewDomesticOriginPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) subtestData.planner = &mocks.Planner{} @@ -282,7 +281,6 @@ func (suite *MTOShipmentServiceSuite) TestApproveShipment() { mock.AnythingOfType("*appcontext.appContext"), mock.Anything, mock.Anything, - false, true, ).Return(500, nil) @@ -506,7 +504,6 @@ func (suite *MTOShipmentServiceSuite) TestApproveShipment() { mock.Anything, mock.Anything, false, - false, ).Return(500, nil) preApprovalTime := time.Now() @@ -627,7 +624,6 @@ func (suite *MTOShipmentServiceSuite) TestApproveShipment() { createdShipment.PickupAddress.PostalCode, createdShipment.DestinationAddress.PostalCode, false, - false, ).Return(500, nil) shipmentHeavyEtag := etag.GenerateEtag(shipmentHeavy.UpdatedAt) @@ -904,7 +900,6 @@ func (suite *MTOShipmentServiceSuite) TestApproveShipment() { mock.AnythingOfType("string"), mock.AnythingOfType("string"), false, - false, ).Return(500, nil).Run(func(args mock.Arguments) { TransitDistancePickupArg = args.Get(1).(string) TransitDistanceDestinationArg = args.Get(2).(string) @@ -960,7 +955,6 @@ func (suite *MTOShipmentServiceSuite) TestApproveShipment() { mock.AnythingOfType("string"), mock.AnythingOfType("string"), false, - false, ).Return(500, nil) suite.Equal(8000, *shipment.MoveTaskOrder.Orders.Entitlement.AuthorizedWeight()) @@ -1002,7 +996,6 @@ func (suite *MTOShipmentServiceSuite) TestApproveShipment() { mock.AnythingOfType("string"), mock.AnythingOfType("string"), false, - false, ).Return(500, nil) shipmentEtag := etag.GenerateEtag(shipment.UpdatedAt) @@ -1085,6 +1078,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{ @@ -1103,9 +1097,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, }, }, { @@ -1153,6 +1148,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{ @@ -1171,9 +1167,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/mto_shipment/shipment_deleter_test.go b/pkg/services/mto_shipment/shipment_deleter_test.go index 35c86bc68ac..6a0d900c128 100644 --- a/pkg/services/mto_shipment/shipment_deleter_test.go +++ b/pkg/services/mto_shipment/shipment_deleter_test.go @@ -30,7 +30,6 @@ func (suite *MTOShipmentServiceSuite) TestShipmentDeleter() { mock.Anything, mock.Anything, false, - false, ).Return(400, nil) setUpSignedCertificationCreatorMock := func(returnValue ...interface{}) services.SignedCertificationCreator { @@ -262,7 +261,6 @@ func (suite *MTOShipmentServiceSuite) TestPrimeShipmentDeleter() { mock.Anything, mock.Anything, false, - false, ).Return(400, nil) setUpSignedCertificationCreatorMock := func(returnValue ...interface{}) services.SignedCertificationCreator { diff --git a/pkg/services/office_user.go b/pkg/services/office_user.go index c0a84c99e20..ac6b4f23f75 100644 --- a/pkg/services/office_user.go +++ b/pkg/services/office_user.go @@ -49,3 +49,10 @@ type OfficeUserCreator interface { type OfficeUserUpdater interface { UpdateOfficeUser(appCtx appcontext.AppContext, id uuid.UUID, payload *adminmessages.OfficeUserUpdate, primaryTransportationOfficeId uuid.UUID) (*models.OfficeUser, *validate.Errors, error) } + +// OfficeUserDeleter is the exported interface for hard deleting an office user and its associations (roles, privileges) +// +//go:generate mockery --name OfficeUserDeleter +type OfficeUserDeleter interface { + DeleteOfficeUser(appCtx appcontext.AppContext, id uuid.UUID) error +} diff --git a/pkg/services/office_user/office_user_deleter.go b/pkg/services/office_user/office_user_deleter.go new file mode 100644 index 00000000000..0e7967669f1 --- /dev/null +++ b/pkg/services/office_user/office_user_deleter.go @@ -0,0 +1,78 @@ +package officeuser + +import ( + "database/sql" + + "github.com/gofrs/uuid" + + "github.com/transcom/mymove/pkg/appcontext" + "github.com/transcom/mymove/pkg/apperror" + "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/services/query" +) + +type officeUserDeleter struct { + builder officeUserQueryBuilder +} + +func (o *officeUserDeleter) DeleteOfficeUser(appCtx appcontext.AppContext, id uuid.UUID) error { + // need to fetch the office user and any downstream associations (roles, privileges) + var officeUser models.OfficeUser + err := appCtx.DB().EagerPreload( + "User", + "User.Roles", + "User.Privileges", + ).Where("id = ?", id).Find(&officeUser, id) + if err == sql.ErrNoRows { + return apperror.NewNotFoundError(id, "while looking for OfficeUser") + } else if err != nil { + return err + } + + user := officeUser.User + transactionError := appCtx.NewTransaction(func(txnAppCtx appcontext.AppContext) error { + userIdFilter := []services.QueryFilter{query.NewQueryFilter("user_id", "=", user.ID.String())} + if len(user.Roles) > 0 { + // Delete associated roles (users_roles) + err = o.builder.DeleteMany(appCtx, &[]models.UsersRoles{}, userIdFilter) + if err != nil { + return err + } + } + + if len(user.Privileges) > 0 { + // Delete associated privileges (users_privileges) + err = o.builder.DeleteMany(appCtx, &[]models.UsersPrivileges{}, userIdFilter) + if err != nil { + return err + } + } + + // delete the office user (office_users) + err = o.builder.DeleteOne(appCtx, &officeUser) + if err != nil { + return err + } + + // finally, delete the user (user) + err = o.builder.DeleteOne(appCtx, &user) + if err != nil { + return err + } + + return nil + }) + + if transactionError != nil { + appCtx.Logger().Error(transactionError.Error()) + return transactionError + } + + return nil +} + +// NewOfficeUserDeleter returns a new office user deleter builder +func NewOfficeUserDeleter(builder officeUserQueryBuilder) services.OfficeUserDeleter { + return &officeUserDeleter{builder} +} diff --git a/pkg/services/office_user/office_user_deleter_test.go b/pkg/services/office_user/office_user_deleter_test.go new file mode 100644 index 00000000000..191527be2dc --- /dev/null +++ b/pkg/services/office_user/office_user_deleter_test.go @@ -0,0 +1,71 @@ +package officeuser + +import ( + "database/sql" + + "github.com/gofrs/uuid" + + "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/query" +) + +func (suite *OfficeUserServiceSuite) TestDeleteOfficeUser() { + queryBuilder := query.NewQueryBuilder() + deleter := NewOfficeUserDeleter(queryBuilder) + setupTestData := func() (models.User, models.OfficeUser) { + user := factory.BuildDefaultUser(suite.DB()) + status := models.OfficeUserStatusREQUESTED + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + { + Model: models.OfficeUser{ + Active: true, + UserID: &user.ID, + Email: user.OktaEmail, + Status: &status, + }, + }, + { + Model: user, + LinkOnly: true, + }, + }, []roles.RoleType{roles.RoleTypeTOO}) + return user, officeUser + } + + suite.Run("success - an office user is deleted", func() { + testUser, testOfficeUser := setupTestData() + + err := deleter.DeleteOfficeUser(suite.AppContextForTest(), testOfficeUser.ID) + suite.NoError(err) + + var user models.User + err = suite.DB().Where("id = ?", testUser.ID).First(&user) + suite.Error(err) + suite.Equal(sql.ErrNoRows, err, "sql: no rows in result set") + + var officeUser models.OfficeUser + err = suite.DB().Where("user_id = ?", testUser.ID).First(&officeUser) + suite.Error(err) + suite.Equal(sql.ErrNoRows, err, "sql: no rows in result set") + + // .All does not return a sql no rows error, so we will verify that the struct is empty + var userRoles []models.UsersRoles + err = suite.DB().Where("user_id = ?", testUser.ID).All(&userRoles) + suite.NoError(err) + suite.Empty(userRoles, "Expected no roles to remain for the user") + + var userPrivileges []models.UsersPrivileges + err = suite.DB().Where("user_id = ?", testUser.ID).All(&userPrivileges) + suite.NoError(err) + suite.Empty(userPrivileges, "Expected no privileges to remain for the user") + }) + + suite.Run("error - an office user is not found", func() { + officeUserID := uuid.Must(uuid.NewV4()) + + err := deleter.DeleteOfficeUser(suite.AppContextForTest(), officeUserID) + suite.Error(err) + }) +} diff --git a/pkg/services/office_user/office_user_fetcher.go b/pkg/services/office_user/office_user_fetcher.go index 350162324d2..881c278ac8a 100644 --- a/pkg/services/office_user/office_user_fetcher.go +++ b/pkg/services/office_user/office_user_fetcher.go @@ -19,6 +19,8 @@ type officeUserQueryBuilder interface { QueryForAssociations(appCtx appcontext.AppContext, model interface{}, associations services.QueryAssociations, filters []services.QueryFilter, pagination services.Pagination, ordering services.QueryOrder) error CreateOne(appCtx appcontext.AppContext, model interface{}) (*validate.Errors, error) UpdateOne(appCtx appcontext.AppContext, model interface{}, eTag *string) (*validate.Errors, error) + DeleteOne(appCtx appcontext.AppContext, model interface{}) error + DeleteMany(appCtx appcontext.AppContext, model interface{}, filters []services.QueryFilter) error } type officeUserFetcher struct { diff --git a/pkg/services/office_user/office_user_fetcher_test.go b/pkg/services/office_user/office_user_fetcher_test.go index f5a82564a69..a096e21dd3a 100644 --- a/pkg/services/office_user/office_user_fetcher_test.go +++ b/pkg/services/office_user/office_user_fetcher_test.go @@ -39,6 +39,14 @@ func (t *testOfficeUserQueryBuilder) QueryForAssociations(_ appcontext.AppContex return nil } +func (t *testOfficeUserQueryBuilder) DeleteOne(_ appcontext.AppContext, _ interface{}) error { + return nil +} + +func (t *testOfficeUserQueryBuilder) DeleteMany(_ appcontext.AppContext, _ interface{}, _ []services.QueryFilter) error { + return nil +} + func (suite *OfficeUserServiceSuite) TestFetchOfficeUser() { suite.Run("if the user is fetched, it should be re turned", func() { id, err := uuid.NewV4() @@ -83,27 +91,6 @@ func (suite *OfficeUserServiceSuite) TestFetchOfficeUser() { }) } -func (suite *OfficeUserServiceSuite) TestFetchOfficeUserByID() { - suite.Run("FetchOfficeUserByID returns office user on success", func() { - officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) - fetcher := NewOfficeUserFetcherPop() - - fetchedUser, err := fetcher.FetchOfficeUserByID(suite.AppContextForTest(), officeUser.ID) - - suite.NoError(err) - suite.Equal(officeUser.ID, fetchedUser.ID) - }) - - suite.Run("FetchOfficeUserByID returns zero value office user on error", func() { - fetcher := NewOfficeUserFetcherPop() - officeUser, err := fetcher.FetchOfficeUserByID(suite.AppContextForTest(), uuid.Nil) - - suite.Error(err) - suite.IsType(apperror.NotFoundError{}, err) - suite.Equal(uuid.Nil, officeUser.ID) - }) -} - func (suite *OfficeUserServiceSuite) TestFetchOfficeUsersWithWorkloadByRoleAndOffice() { fetcher := NewOfficeUserFetcherPop() suite.Run("FetchOfficeUsersWithWorkloadByRoleAndOffice returns an active office user's name, id, and workload when given a role and office", func() { 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/order_fetcher_test.go b/pkg/services/order/order_fetcher_test.go index 57e85401246..29245da3d39 100644 --- a/pkg/services/order/order_fetcher_test.go +++ b/pkg/services/order/order_fetcher_test.go @@ -2035,140 +2035,6 @@ func (suite *OrderServiceSuite) TestListAllOrderLocations() { }) } -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) - - setupData := func() { - factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ - { - Model: models.Move{ - Status: models.MoveStatusAPPROVED, - Locator: "AA1235", - }, - }, - { - Model: models.MTOShipment{ - RequestedPickupDate: &requestedMoveDate1, - }, - }, - }, nil) - factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ - { - Model: models.Move{ - Locator: "TTZ125", - }, - }, - { - Model: models.ServiceMember{ - FirstName: &serviceMemberFirstName, - Edipi: &edipi, - }, - }, - { - Model: models.MTOShipment{ - RequestedPickupDate: &requestedMoveDate2, - }, - }, - }, nil) - factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ - { - 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", - } - } - - orderFetcher := NewOrderFetcher(waf) - - 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) - }) - - 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("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) - }) - - 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) - }) - - 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) - }) - - 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.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)) - }) -} - func (suite *OrderServiceSuite) TestListAllOrderLocationsWithViewAsGBLOCParam() { waf := entitlements.NewWeightAllotmentFetcher() suite.Run("returns a list of all order locations in the current users queue", func() { @@ -2307,3 +2173,132 @@ func (suite *OrderServiceSuite) TestOriginDutyLocationFilter() { 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.Move{ + Status: models.MoveStatusAPPROVED, + Locator: "AA1235", + }, + }, + { + Model: models.MTOShipment{ + RequestedPickupDate: &requestedMoveDate1, + }, + }, + }, nil) + factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Locator: "TTZ125", + }, + }, + { + Model: models.ServiceMember{ + FirstName: &serviceMemberFirstName, + Edipi: &edipi, + }, + }, + { + Model: models.MTOShipment{ + RequestedPickupDate: &requestedMoveDate2, + }, + }, + }, nil) + factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + 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", + } + }) + + orderFetcher := NewOrderFetcher(waf) + + 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("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.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("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("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)) + }) +} diff --git a/pkg/services/order/order_updater.go b/pkg/services/order/order_updater.go index 12821783d7a..f2efe7f2a57 100644 --- a/pkg/services/order/order_updater.go +++ b/pkg/services/order/order_updater.go @@ -485,6 +485,11 @@ func allowanceFromTOOPayload(appCtx appcontext.AppContext, existingOrder models. order.Entitlement.GunSafe = *payload.GunSafe } + if payload.WeightRestriction != nil { + weightRestriction := int(*payload.WeightRestriction) + order.Entitlement.WeightRestriction = &weightRestriction + } + if payload.AccompaniedTour != nil { order.Entitlement.AccompaniedTour = payload.AccompaniedTour } @@ -531,7 +536,6 @@ func allowanceFromTOOPayload(appCtx appcontext.AppContext, existingOrder models. return order, nil } - func allowanceFromCounselingPayload(appCtx appcontext.AppContext, existingOrder models.Order, payload ghcmessages.CounselingUpdateAllowancePayload) (models.Order, error) { order := existingOrder waf := entitlements.NewWeightAllotmentFetcher() @@ -589,6 +593,11 @@ func allowanceFromCounselingPayload(appCtx appcontext.AppContext, existingOrder order.Entitlement.GunSafe = *payload.GunSafe } + if payload.WeightRestriction != nil { + weightRestriction := int(*payload.WeightRestriction) + order.Entitlement.WeightRestriction = &weightRestriction + } + if payload.AccompaniedTour != nil { order.Entitlement.AccompaniedTour = payload.AccompaniedTour } @@ -730,16 +739,27 @@ func updateOrderInTx(appCtx appcontext.AppContext, order models.Order, checks .. order.OriginDutyLocationID = &originDutyLocation.ID order.OriginDutyLocation = &originDutyLocation - dutyLocationGBLOC, err2 := models.FetchGBLOCForPostalCode(appCtx.DB(), originDutyLocation.Address.PostalCode) - if err2 != nil { - switch err2 { - case sql.ErrNoRows: - return nil, apperror.NewNotFoundError(originDutyLocation.ID, "while looking for Duty Location PostalCodeToGBLOC") - default: - return nil, apperror.NewQueryError("PostalCodeToGBLOC", err, "") + var originDutyLocationGBLOC *string + if *originDutyLocation.Address.IsOconus { + originDutyLocationGBLOCOconus, err := models.FetchAddressGbloc(appCtx.DB(), originDutyLocation.Address, order.ServiceMember) + if err != nil { + return nil, apperror.NewNotFoundError(originDutyLocation.ID, "while looking for Duty Location Oconus GBLOC") + } + originDutyLocationGBLOC = originDutyLocationGBLOCOconus + } else { + originDutyLocationGBLOCConus, err2 := models.FetchGBLOCForPostalCode(appCtx.DB(), originDutyLocation.Address.PostalCode) + if err2 != nil { + switch err2 { + case sql.ErrNoRows: + return nil, apperror.NewNotFoundError(originDutyLocation.ID, "while looking for Duty Location PostalCodeToGBLOC") + default: + return nil, apperror.NewQueryError("PostalCodeToGBLOC", err, "") + } } + originDutyLocationGBLOC = &originDutyLocationGBLOCConus.GBLOC } - order.OriginDutyLocationGBLOC = &dutyLocationGBLOC.GBLOC + + order.OriginDutyLocationGBLOC = originDutyLocationGBLOC } if order.Grade != nil || order.OriginDutyLocationID != nil { @@ -762,19 +782,29 @@ func updateOrderInTx(appCtx appcontext.AppContext, order models.Order, checks .. } } - newDestinationGBLOC, err := models.FetchGBLOCForPostalCode(appCtx.DB(), newDutyLocation.Address.PostalCode) - if err != nil { - switch err { - case sql.ErrNoRows: - return nil, apperror.NewNotFoundError(order.NewDutyLocationID, "while looking for DestinationGBLOC") - default: - return nil, apperror.NewQueryError("DestinationGBLOC", err, "") + var newDestinationGBLOC *string + if *newDutyLocation.Address.IsOconus { + newDestinationGBLOCOconus, err := models.FetchAddressGbloc(appCtx.DB(), newDutyLocation.Address, order.ServiceMember) + if err != nil { + return nil, apperror.NewNotFoundError(newDutyLocation.ID, "while looking for DestinationGBLOC Oconus") + } + newDestinationGBLOC = newDestinationGBLOCOconus + } else { + newDestinationGBLOCConus, err2 := models.FetchGBLOCForPostalCode(appCtx.DB(), newDutyLocation.Address.PostalCode) + if err2 != nil { + switch err { + case sql.ErrNoRows: + return nil, apperror.NewNotFoundError(order.NewDutyLocationID, "while looking for DestinationGBLOC") + default: + return nil, apperror.NewQueryError("DestinationGBLOC", err, "") + } } + newDestinationGBLOC = &newDestinationGBLOCConus.GBLOC } order.NewDutyLocationID = newDutyLocation.ID order.NewDutyLocation = newDutyLocation - order.DestinationGBLOC = &newDestinationGBLOC.GBLOC + order.DestinationGBLOC = newDestinationGBLOC } // Recalculate UB allowance of order entitlement diff --git a/pkg/services/order/order_updater_test.go b/pkg/services/order/order_updater_test.go index 1bf72866552..0fa72e8d904 100644 --- a/pkg/services/order/order_updater_test.go +++ b/pkg/services/order/order_updater_test.go @@ -775,6 +775,7 @@ func (suite *OrderServiceSuite) TestUpdateAllowanceAsCounselor() { proGearWeightSpouse := models.Int64Pointer(10) rmeWeight := models.Int64Pointer(10000) eTag := etag.GenerateEtag(order.UpdatedAt) + weightRestriction := models.Int64Pointer(5000) payload := ghcmessages.CounselingUpdateAllowancePayload{ Agency: &affiliation, @@ -784,6 +785,7 @@ func (suite *OrderServiceSuite) TestUpdateAllowanceAsCounselor() { ProGearWeight: proGearWeight, ProGearWeightSpouse: proGearWeightSpouse, RequiredMedicalEquipmentWeight: rmeWeight, + WeightRestriction: weightRestriction, } updatedOrder, _, err := orderUpdater.UpdateAllowanceAsCounselor(suite.AppContextForTest(), order.ID, payload, eTag) @@ -804,6 +806,7 @@ func (suite *OrderServiceSuite) TestUpdateAllowanceAsCounselor() { suite.Equal(*payload.OrganizationalClothingAndIndividualEquipment, updatedOrder.Entitlement.OrganizationalClothingAndIndividualEquipment) suite.EqualValues(payload.Agency, fetchedSM.Affiliation) suite.Equal(*updatedOrder.Entitlement.DBAuthorizedWeight, 16000) + suite.Equal(*payload.WeightRestriction, int64(*updatedOrder.Entitlement.WeightRestriction)) }) suite.Run("Updates the allowance when all fields are valid with dependents present and authorized", func() { diff --git a/pkg/services/payment_request/payment_request_creator_test.go b/pkg/services/payment_request/payment_request_creator_test.go index 3831f354b47..6c44677c2e8 100644 --- a/pkg/services/payment_request/payment_request_creator_test.go +++ b/pkg/services/payment_request/payment_request_creator_test.go @@ -328,7 +328,6 @@ func (suite *PaymentRequestServiceSuite) TestCreatePaymentRequest() { mock.Anything, mock.Anything, false, - false, ).Return(0, nil) }) @@ -571,7 +570,6 @@ func (suite *PaymentRequestServiceSuite) TestCreatePaymentRequest() { mock.Anything, mock.Anything, false, - false, ).Return(0, nil) failingCreator := NewPaymentRequestCreator(planner, failingServiceItemPricer) @@ -1464,7 +1462,6 @@ func (suite *PaymentRequestServiceSuite) TestCreatePaymentRequestCheckOnNTSRelea testStorageFacilityZip, testDestinationZip, false, - false, ).Return(testZip3Distance, nil) // Create an initial payment request. diff --git a/pkg/services/payment_request/payment_request_list_fetcher.go b/pkg/services/payment_request/payment_request_list_fetcher.go index 11f0c52805c..fc42b14dc63 100644 --- a/pkg/services/payment_request/payment_request_list_fetcher.go +++ b/pkg/services/payment_request/payment_request_list_fetcher.go @@ -65,6 +65,7 @@ func (f *paymentRequestListFetcher) FetchPaymentRequestList(appCtx appcontext.Ap "MoveTaskOrder.Orders.OriginDutyLocation.TransportationOffice", "MoveTaskOrder.Orders.OriginDutyLocation.Address", "MoveTaskOrder.TIOAssignedUser", + "MoveTaskOrder.TIOAssignedUser.TransportationOfficeID", "MoveTaskOrder.CounselingOffice", // See note further below about having to do this in a separate Load call due to a Pop issue. // "MoveTaskOrder.Orders.ServiceMember", diff --git a/pkg/services/payment_request/payment_request_recalculator_test.go b/pkg/services/payment_request/payment_request_recalculator_test.go index 7d5120dcd55..94b21d42e71 100644 --- a/pkg/services/payment_request/payment_request_recalculator_test.go +++ b/pkg/services/payment_request/payment_request_recalculator_test.go @@ -56,7 +56,6 @@ func (suite *PaymentRequestServiceSuite) TestRecalculatePaymentRequestSuccess() recalculateTestPickupZip, recalculateTestDestinationZip, false, - false, ).Return(recalculateTestZip3Distance, nil) // Create an initial payment request. @@ -298,7 +297,6 @@ func (suite *PaymentRequestServiceSuite) TestRecalculatePaymentRequestErrors() { recalculateTestPickupZip, recalculateTestDestinationZip, false, - false, ).Return(recalculateTestZip3Distance, nil) // Create an initial payment request. diff --git a/pkg/services/payment_request/payment_request_shipment_recalculate_test.go b/pkg/services/payment_request/payment_request_shipment_recalculate_test.go index d32beaa61b7..d308e237efb 100644 --- a/pkg/services/payment_request/payment_request_shipment_recalculate_test.go +++ b/pkg/services/payment_request/payment_request_shipment_recalculate_test.go @@ -28,7 +28,6 @@ func (suite *PaymentRequestServiceSuite) TestRecalculateShipmentPaymentRequestSu recalculateTestPickupZip, recalculateTestDestinationZip, false, - false, ).Return(recalculateTestZip3Distance, nil) // Create an initial payment request. @@ -139,7 +138,6 @@ func (suite *PaymentRequestServiceSuite) TestRecalculateShipmentPaymentRequestEr recalculateTestPickupZip, recalculateTestDestinationZip, false, - false, ).Return(recalculateTestZip3Distance, nil) creator := NewPaymentRequestCreator(mockPlanner, ghcrateengine.NewServiceItemPricer()) diff --git a/pkg/services/payment_service_item/payment_service_item_status_updater.go b/pkg/services/payment_service_item/payment_service_item_status_updater.go index b0d822ea7e2..0d064a6cc65 100644 --- a/pkg/services/payment_service_item/payment_service_item_status_updater.go +++ b/pkg/services/payment_service_item/payment_service_item_status_updater.go @@ -70,21 +70,24 @@ func (p *paymentServiceItemUpdater) updatePaymentServiceItem(appCtx appcontext.A return models.PaymentServiceItem{}, nil, verr } - // If we're denying this thing we want to make sure to update the DeniedAt field and nil out ApprovedAt. - if desiredStatus == models.PaymentServiceItemStatusDenied { + switch desiredStatus { + // when the user hits "clear selection" we want to clear all the fields + case models.PaymentServiceItemStatusRequested: + paymentServiceItem.RejectionReason = nil + paymentServiceItem.DeniedAt = nil + paymentServiceItem.ApprovedAt = nil + // if being denied, we want to nil out approvedAt and populate deniedAt + case models.PaymentServiceItemStatusDenied: paymentServiceItem.RejectionReason = rejectionReason paymentServiceItem.DeniedAt = models.TimePointer(time.Now()) paymentServiceItem.ApprovedAt = nil - paymentServiceItem.Status = desiredStatus - } - // If we're approving this thing then we don't want there to be a rejection reason - // We also will want to update the ApprovedAt field and nil out the DeniedAt field. - if desiredStatus == models.PaymentServiceItemStatusApproved { + // if being approved, populate approvedAt + case models.PaymentServiceItemStatusApproved: paymentServiceItem.RejectionReason = nil paymentServiceItem.DeniedAt = nil paymentServiceItem.ApprovedAt = models.TimePointer(time.Now()) - paymentServiceItem.Status = desiredStatus } + paymentServiceItem.Status = desiredStatus // Save the record verrs, err := appCtx.DB().ValidateAndSave(&paymentServiceItem) diff --git a/pkg/services/payment_service_item/payment_service_item_status_updater_test.go b/pkg/services/payment_service_item/payment_service_item_status_updater_test.go index f91bae07504..f35586c2cf8 100644 --- a/pkg/services/payment_service_item/payment_service_item_status_updater_test.go +++ b/pkg/services/payment_service_item/payment_service_item_status_updater_test.go @@ -26,7 +26,6 @@ func (suite *PaymentServiceItemSuite) TestUpdatePaymentServiceItemStatus() { suite.NotNil(updatedPaymentServiceItem.ApprovedAt) suite.Nil(updatedPaymentServiceItem.RejectionReason) suite.Nil(updatedPaymentServiceItem.DeniedAt) - }) suite.Run("Successfully rejects a payment service item", func() { @@ -44,7 +43,23 @@ func (suite *PaymentServiceItemSuite) TestUpdatePaymentServiceItemStatus() { suite.NotNil(updatedPaymentServiceItem.DeniedAt) suite.Equal("reasons", *updatedPaymentServiceItem.RejectionReason) suite.Nil(updatedPaymentServiceItem.ApprovedAt) + }) + + suite.Run("Successfully resets a payment service item to requested", func() { + paymentServiceItem := factory.BuildPaymentServiceItem(suite.DB(), nil, nil) + eTag := etag.GenerateEtag(paymentServiceItem.UpdatedAt) + updater := NewPaymentServiceItemStatusUpdater() + + updatedPaymentServiceItem, verrs, err := updater.UpdatePaymentServiceItemStatus(suite.AppContextForTest(), + paymentServiceItem.ID, models.PaymentServiceItemStatusRequested, models.StringPointer("reasons"), eTag) + suite.NoError(err) + suite.NoVerrs(verrs) + suite.Equal(paymentServiceItem.ID, updatedPaymentServiceItem.ID) + suite.Equal(models.PaymentServiceItemStatusRequested, updatedPaymentServiceItem.Status) + suite.Nil(updatedPaymentServiceItem.DeniedAt) + suite.Nil(updatedPaymentServiceItem.RejectionReason) + suite.Nil(updatedPaymentServiceItem.ApprovedAt) }) suite.Run("Fails if we can't find an existing paymentServiceItem", func() { diff --git a/pkg/services/ppm_closeout/ppm_closeout.go b/pkg/services/ppm_closeout/ppm_closeout.go index 925385d079b..7ced6a8c257 100644 --- a/pkg/services/ppm_closeout/ppm_closeout.go +++ b/pkg/services/ppm_closeout/ppm_closeout.go @@ -36,6 +36,9 @@ type serviceItemPrices struct { haulPrice *unit.Cents haulFSC *unit.Cents haulType models.HaulType + intlPackPrice *unit.Cents + intlUnpackPrice *unit.Cents + intlLinehaulPrice *unit.Cents } func NewPPMCloseoutFetcher(planner route.Planner, paymentRequestHelper paymentrequesthelper.Helper, estimator services.PPMEstimator) services.PPMCloseoutFetcher { @@ -60,11 +63,6 @@ func (p *ppmCloseoutFetcher) GetPPMCloseout(appCtx appcontext.AppContext, ppmShi proGearWeightCustomer, proGearWeightSpouse := p.GetProGearWeights(*ppmShipment) - serviceItems, err := p.getServiceItemPrices(appCtx, *ppmShipment) - if err != nil { - return nil, err - } - var remainingIncentive unit.Cents // Most moves generated by `make db_dev_e2e_populate` skip the part that generates the FinalIncentive for the move, so just return 0. // Moves created through the UI shouldn't be able to skip this part @@ -96,6 +94,11 @@ func (p *ppmCloseoutFetcher) GetPPMCloseout(appCtx appcontext.AppContext, ppmShi fullWeightGCCShipment.ProGearWeight = &proGearCustomerMax fullWeightGCCShipment.SpouseProGearWeight = &proGearSpouseMax gcc, _ := p.calculateGCC(appCtx, *fullWeightGCCShipment, fullAllowableWeight) + + serviceItems, err := p.getServiceItemPrices(appCtx, *ppmShipment) + if err != nil { + return nil, err + } if serviceItems.storageReimbursementCosts != nil { gcc = gcc.AddCents(*serviceItems.storageReimbursementCosts) } @@ -114,11 +117,14 @@ func (p *ppmCloseoutFetcher) GetPPMCloseout(appCtx appcontext.AppContext, ppmShi ppmCloseoutObj.RemainingIncentive = &remainingIncentive ppmCloseoutObj.HaulPrice = serviceItems.haulPrice ppmCloseoutObj.HaulFSC = serviceItems.haulFSC - ppmCloseoutObj.HaulType = serviceItems.haulType + ppmCloseoutObj.HaulType = &serviceItems.haulType ppmCloseoutObj.DOP = serviceItems.dop ppmCloseoutObj.DDP = serviceItems.ddp ppmCloseoutObj.PackPrice = serviceItems.packPrice ppmCloseoutObj.UnpackPrice = serviceItems.unpackPrice + ppmCloseoutObj.IntlLinehaulPrice = serviceItems.intlLinehaulPrice + ppmCloseoutObj.IntlUnpackPrice = serviceItems.intlUnpackPrice + ppmCloseoutObj.IntlPackPrice = serviceItems.intlPackPrice ppmCloseoutObj.SITReimbursement = serviceItems.storageReimbursementCosts return &ppmCloseoutObj, nil @@ -249,53 +255,6 @@ func (p *ppmCloseoutFetcher) GetExpenseStoragePrice(appCtx appcontext.AppContext return storageExpensePrice, err } -func (p *ppmCloseoutFetcher) GetEntitlement(appCtx appcontext.AppContext, moveID uuid.UUID) (*models.Entitlement, error) { - var moveModel models.Move - err := appCtx.DB().EagerPreload( - "OrdersID", - ).Find(&moveModel, moveID) - - if err != nil { - switch err { - case sql.ErrNoRows: - return nil, apperror.NewNotFoundError(moveID, "while looking for Move") - default: - return nil, apperror.NewQueryError("Move", err, "unable to find Move") - } - } - - var order models.Order - orderID := &moveModel.OrdersID - errOrder := appCtx.DB().EagerPreload( - "EntitlementID", - ).Find(&order, orderID) - - if errOrder != nil { - switch errOrder { - case sql.ErrNoRows: - return nil, apperror.NewNotFoundError(*orderID, "while looking for Order") - default: - return nil, apperror.NewQueryError("Order", errOrder, "unable to find Order") - } - } - - var entitlement models.Entitlement - entitlementID := order.EntitlementID - errEntitlement := appCtx.DB().EagerPreload( - "DBAuthorizedWeight", - ).Find(&entitlement, entitlementID) - - if errEntitlement != nil { - switch errEntitlement { - case sql.ErrNoRows: - return nil, apperror.NewNotFoundError(*entitlementID, "while looking for Entitlement") - default: - return nil, apperror.NewQueryError("Entitlement", errEntitlement, "unable to find Entitlement") - } - } - return &entitlement, nil -} - func paramsForServiceCode(code models.ReServiceCode, serviceParams models.ServiceParams) models.ServiceParams { var serviceItemParams models.ServiceParams for _, serviceParam := range serviceParams { @@ -312,17 +271,13 @@ func (p *ppmCloseoutFetcher) getServiceItemPrices(appCtx appcontext.AppContext, var returnPriceObj serviceItemPrices logger := appCtx.Logger() - err := appCtx.DB().Where("mto_shipment_id = ?", ppmShipment.ShipmentID).All(&serviceItemsToPrice) - if err != nil { - return serviceItemPrices{}, err - } + isInternationalShipment := ppmShipment.Shipment.MarketCode == models.MarketCodeInternational + serviceItemsToPrice = ppmshipment.BaseServiceItems(ppmShipment) - serviceItemsToPrice = ppmshipment.BaseServiceItems(ppmShipment.ShipmentID) - - // Change DLH to DSH if move within same Zip3 actualPickupPostal := *ppmShipment.ActualPickupPostalCode actualDestPostal := *ppmShipment.ActualDestinationPostalCode - if actualPickupPostal[0:3] == actualDestPostal[0:3] { + // Change DLH to DSH if move within same Zip3 (only for domestic shipments - intl uses ISLH) + if !isInternationalShipment && actualPickupPostal[0:3] == actualDestPostal[0:3] { serviceItemsToPrice[0] = models.MTOServiceItem{ReService: models.ReService{Code: models.ReServiceCodeDSH}, MTOShipmentID: &ppmShipment.ShipmentID} } contractDate := ppmShipment.ExpectedDepartureDate @@ -335,10 +290,12 @@ func (p *ppmCloseoutFetcher) getServiceItemPrices(appCtx appcontext.AppContext, if paramErr != nil { return serviceItemPrices{}, paramErr } - var totalPrice, packPrice, unpackPrice, destinationPrice, originPrice, haulPrice, haulFSC unit.Cents + + var totalPrice, packPrice, unpackPrice, destinationPrice, originPrice, haulPrice, haulFSC, intlPackPrice, intlUnpackPrice, intlLinehaulPrice unit.Cents var totalWeight unit.Pound var ppmToMtoShipment models.MTOShipment + // adding all the weight tickets together to get the total weight of the moved PPM if len(ppmShipment.WeightTickets) >= 1 { for _, weightTicket := range ppmShipment.WeightTickets { if weightTicket.Status != nil && *weightTicket.Status == models.PPMDocumentStatusRejected { @@ -373,14 +330,18 @@ func (p *ppmCloseoutFetcher) getServiceItemPrices(appCtx appcontext.AppContext, return serviceItemPrices{}, err } + // combo of domestic & int'l service items validCodes := map[models.ReServiceCode]string{ - models.ReServiceCodeDPK: "DPK", - models.ReServiceCodeDUPK: "DUPK", - models.ReServiceCodeDOP: "DOP", - models.ReServiceCodeDDP: "DDP", - models.ReServiceCodeDSH: "DSH", - models.ReServiceCodeDLH: "DLH", - models.ReServiceCodeFSC: "FSC", + models.ReServiceCodeDPK: "DPK", + models.ReServiceCodeDUPK: "DUPK", + models.ReServiceCodeDOP: "DOP", + models.ReServiceCodeDDP: "DDP", + models.ReServiceCodeDSH: "DSH", + models.ReServiceCodeDLH: "DLH", + models.ReServiceCodeFSC: "FSC", + models.ReServiceCodeISLH: "ISLH", + models.ReServiceCodeIHPK: "IHPK", + models.ReServiceCodeIHUPK: "IHUPK", } // If service item is of a type we need for a specific calculation, get its price @@ -402,11 +363,11 @@ func (p *ppmCloseoutFetcher) getServiceItemPrices(appCtx appcontext.AppContext, serviceItemLookups := serviceparamvaluelookups.InitializeLookups(appCtx, ppmToMtoShipment, serviceItem) // This is the struct that gets passed to every param lookup() method that was initialized above - keyData := serviceparamvaluelookups.NewServiceItemParamKeyData(p.planner, serviceItemLookups, serviceItem, ppmToMtoShipment, contract.Code) + keyData := serviceparamvaluelookups.NewServiceItemParamKeyData(p.planner, serviceItemLookups, serviceItem, ppmToMtoShipment, contract.Code, contract.ID) // The distance value gets saved to the mto shipment model to reduce repeated api calls. var shipmentWithDistance models.MTOShipment - err = appCtx.DB().Find(&shipmentWithDistance, ppmShipment.Shipment.ID) + err = appCtx.DB().Eager("PPMShipment").Find(&shipmentWithDistance, ppmShipment.Shipment.ID) if err != nil { logger.Error("could not find shipment in the database") return serviceItemPrices{}, err @@ -419,7 +380,7 @@ func (p *ppmCloseoutFetcher) getServiceItemPrices(appCtx appcontext.AppContext, for _, param := range paramsForServiceCode(serviceItem.ReService.Code, paramsForServiceItems) { paramKey := param.ServiceItemParamKey // This is where the lookup() method of each service item param is actually evaluated - paramValue, serviceParamErr := keyData.ServiceParamValue(appCtx, paramKey.Key) // Fails with "DistanceZip" param? + paramValue, serviceParamErr := keyData.ServiceParamValue(appCtx, paramKey.Key) if serviceParamErr != nil { logger.Error("could not calculate param value lookup", zap.Error(serviceParamErr)) return serviceItemPrices{}, serviceParamErr @@ -452,6 +413,12 @@ func (p *ppmCloseoutFetcher) getServiceItemPrices(appCtx appcontext.AppContext, totalPrice = totalPrice.AddCents(centsValue) switch serviceItem.ReService.Code { + case models.ReServiceCodeIHPK: // Int'l pack + intlPackPrice += centsValue + case models.ReServiceCodeIHUPK: // Int'l unpack + intlUnpackPrice += centsValue + case models.ReServiceCodeISLH: // Int'l shipping & linehaul + intlLinehaulPrice += centsValue case models.ReServiceCodeDPK: packPrice += centsValue case models.ReServiceCodeDUPK: @@ -488,6 +455,9 @@ func (p *ppmCloseoutFetcher) getServiceItemPrices(appCtx appcontext.AppContext, returnPriceObj.storageReimbursementCosts = &sitCosts returnPriceObj.haulPrice = &haulPrice returnPriceObj.haulFSC = &haulFSC + returnPriceObj.intlLinehaulPrice = &intlLinehaulPrice + returnPriceObj.intlPackPrice = &intlPackPrice + returnPriceObj.intlUnpackPrice = &intlUnpackPrice return returnPriceObj, nil } diff --git a/pkg/services/ppm_closeout/ppm_closeout_test.go b/pkg/services/ppm_closeout/ppm_closeout_test.go index c1479c140ea..cc0fff6a390 100644 --- a/pkg/services/ppm_closeout/ppm_closeout_test.go +++ b/pkg/services/ppm_closeout/ppm_closeout_test.go @@ -24,7 +24,7 @@ const ( ppmBuildWaitingOnCustomer = "waitingOnCustomer" ) -func (suite *PPMCloseoutSuite) TestPPMShipmentCreator() { +func (suite *PPMCloseoutSuite) TestPPMShipmentCloseout() { // One-time test setup mockedPlanner := &mocks.Planner{} @@ -337,7 +337,7 @@ func (suite *PPMCloseoutSuite) TestPPMShipmentCreator() { appCtx := suite.AppContextForTest() mockedPlanner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), - "50309", "30813", false, false).Return(2294, nil) + "50309", "30813", false).Return(2294, nil) mockedPaymentRequestHelper.On( "FetchServiceParamsForServiceItems", @@ -393,7 +393,7 @@ func (suite *PPMCloseoutSuite) TestPPMShipmentCreator() { appCtx := suite.AppContextForTest() mockedPlanner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), - "50309", "30813", false, false).Return(2294, nil) + "50309", "30813", false).Return(2294, nil) mockedPaymentRequestHelper.On( "FetchServiceParamsForServiceItems", diff --git a/pkg/services/ppmshipment/ppm_estimator.go b/pkg/services/ppmshipment/ppm_estimator.go index e49d7846bbe..cf77b16fb04 100644 --- a/pkg/services/ppmshipment/ppm_estimator.go +++ b/pkg/services/ppmshipment/ppm_estimator.go @@ -203,6 +203,12 @@ func (f *estimatePPM) estimateIncentive(appCtx appcontext.AppContext, oldPPMShip } } + contractDate := newPPMShipment.ExpectedDepartureDate + contract, err := serviceparamvaluelookups.FetchContract(appCtx, contractDate) + if err != nil { + return nil, nil, err + } + calculateSITEstimate := shouldCalculateSITCost(newPPMShipment, &oldPPMShipment) // Clear out any previously calculated SIT estimated costs, if SIT is no longer expected @@ -216,33 +222,66 @@ func (f *estimatePPM) estimateIncentive(appCtx appcontext.AppContext, oldPPMShip return oldPPMShipment.EstimatedIncentive, newPPMShipment.SITEstimatedCost, nil } - contractDate := newPPMShipment.ExpectedDepartureDate - contract, err := serviceparamvaluelookups.FetchContract(appCtx, contractDate) - if err != nil { - return nil, nil, err - } - estimatedIncentive := oldPPMShipment.EstimatedIncentive - if !skipCalculatingEstimatedIncentive { - // Clear out advance and advance requested fields when the estimated incentive is reset. - newPPMShipment.HasRequestedAdvance = nil - newPPMShipment.AdvanceAmountRequested = nil + estimatedSITCost := oldPPMShipment.SITEstimatedCost - estimatedIncentive, err = f.calculatePrice(appCtx, newPPMShipment, 0, contract, false) - if err != nil { - return nil, nil, err + // if the PPM is international, we will use a db func + if newPPMShipment.Shipment.MarketCode != models.MarketCodeInternational { + + if !skipCalculatingEstimatedIncentive { + // Clear out advance and advance requested fields when the estimated incentive is reset. + newPPMShipment.HasRequestedAdvance = nil + newPPMShipment.AdvanceAmountRequested = nil + + estimatedIncentive, err = f.calculatePrice(appCtx, newPPMShipment, 0, contract, false) + if err != nil { + return nil, nil, err + } } - } - estimatedSITCost := oldPPMShipment.SITEstimatedCost - if calculateSITEstimate { - estimatedSITCost, err = CalculateSITCost(appCtx, newPPMShipment, contract) - if err != nil { - return nil, nil, err + if calculateSITEstimate { + estimatedSITCost, err = CalculateSITCost(appCtx, newPPMShipment, contract) + if err != nil { + return nil, nil, err + } + } + + return estimatedIncentive, estimatedSITCost, nil + + } else { + pickupAddress := newPPMShipment.PickupAddress + destinationAddress := newPPMShipment.DestinationAddress + + if !skipCalculatingEstimatedIncentive { + // Clear out advance and advance requested fields when the estimated incentive is reset. + newPPMShipment.HasRequestedAdvance = nil + newPPMShipment.AdvanceAmountRequested = nil + + estimatedIncentive, err = f.CalculateOCONUSIncentive(appCtx, newPPMShipment.ID, *pickupAddress, *destinationAddress, contractDate, newPPMShipment.EstimatedWeight.Int(), true, false, false) + if err != nil { + return nil, nil, fmt.Errorf("failed to calculate estimated PPM incentive: %w", err) + } + } + + if calculateSITEstimate { + var sitAddress models.Address + isOrigin := *newPPMShipment.SITLocation == models.SITLocationTypeOrigin + if isOrigin { + sitAddress = *newPPMShipment.PickupAddress + } else if !isOrigin { + sitAddress = *newPPMShipment.DestinationAddress + } else { + return estimatedIncentive, estimatedSITCost, nil + } + daysInSIT := additionalDaysInSIT(*newPPMShipment.SITEstimatedEntryDate, *newPPMShipment.SITEstimatedDepartureDate) + estimatedSITCost, err = f.CalculateOCONUSSITCosts(appCtx, newPPMShipment.ID, sitAddress.ID, isOrigin, contractDate, newPPMShipment.EstimatedWeight.Int(), daysInSIT) + if err != nil { + return nil, nil, fmt.Errorf("failed to calculate estimated PPM incentive: %w", err) + } } - } - return estimatedIncentive, estimatedSITCost, nil + return estimatedIncentive, estimatedSITCost, nil + } } func (f *estimatePPM) maxIncentive(appCtx appcontext.AppContext, oldPPMShipment models.PPMShipment, newPPMShipment *models.PPMShipment, checks ...ppmShipmentValidator) (*unit.Cents, error) { @@ -261,7 +300,7 @@ func (f *estimatePPM) maxIncentive(appCtx appcontext.AppContext, oldPPMShipment // we have access to the MoveTaskOrderID in the ppmShipment object so we can use that to get the customer's maximum weight entitlement var move models.Move err = appCtx.DB().Q().Eager( - "Orders.Entitlement", + "Orders.Entitlement", "Orders.OriginDutyLocation.Address", "Orders.NewDutyLocation.Address", ).Where("show = TRUE").Find(&move, newPPMShipment.Shipment.MoveTaskOrderID) if err != nil { return nil, apperror.NewNotFoundError(newPPMShipment.ID, " error querying move") @@ -277,14 +316,27 @@ func (f *estimatePPM) maxIncentive(appCtx appcontext.AppContext, oldPPMShipment return nil, err } - // since the max incentive is based off of the authorized weight entitlement and that value CAN change - // we will calculate the max incentive each time it is called - maxIncentive, err := f.calculatePrice(appCtx, newPPMShipment, unit.Pound(*orders.Entitlement.DBAuthorizedWeight), contract, true) - if err != nil { - return nil, err - } + if newPPMShipment.Shipment.MarketCode != models.MarketCodeInternational { - return maxIncentive, nil + // since the max incentive is based off of the authorized weight entitlement and that value CAN change + // we will calculate the max incentive each time it is called + maxIncentive, err := f.calculatePrice(appCtx, newPPMShipment, unit.Pound(*orders.Entitlement.DBAuthorizedWeight), contract, true) + if err != nil { + return nil, err + } + + return maxIncentive, nil + } else { + pickupAddress := orders.OriginDutyLocation.Address + destinationAddress := orders.NewDutyLocation.Address + + maxIncentive, err := f.CalculateOCONUSIncentive(appCtx, newPPMShipment.ID, pickupAddress, destinationAddress, contractDate, *orders.Entitlement.DBAuthorizedWeight, false, false, true) + if err != nil { + return nil, fmt.Errorf("failed to calculate estimated PPM incentive: %w", err) + } + + return maxIncentive, nil + } } func (f *estimatePPM) finalIncentive(appCtx appcontext.AppContext, oldPPMShipment models.PPMShipment, newPPMShipment *models.PPMShipment, checks ...ppmShipmentValidator) (*unit.Cents, error) { @@ -307,32 +359,51 @@ func (f *estimatePPM) finalIncentive(appCtx appcontext.AppContext, oldPPMShipmen newTotalWeight = *newPPMShipment.AllowableWeight } - isMissingInfo := shouldSetFinalIncentiveToNil(newPPMShipment, newTotalWeight) - var skipCalculateFinalIncentive bool - finalIncentive := oldPPMShipment.FinalIncentive + contractDate := newPPMShipment.ExpectedDepartureDate + if newPPMShipment.ActualMoveDate != nil { + contractDate = *newPPMShipment.ActualMoveDate + } + contract, err := serviceparamvaluelookups.FetchContract(appCtx, contractDate) + if err != nil { + return nil, err + } + + if newPPMShipment.Shipment.MarketCode != models.MarketCodeInternational { + isMissingInfo := shouldSetFinalIncentiveToNil(newPPMShipment, newTotalWeight) + var skipCalculateFinalIncentive bool + finalIncentive := oldPPMShipment.FinalIncentive + if !isMissingInfo { + skipCalculateFinalIncentive = shouldSkipCalculatingFinalIncentive(newPPMShipment, &oldPPMShipment, originalTotalWeight, newTotalWeight) + if !skipCalculateFinalIncentive { - if !isMissingInfo { - skipCalculateFinalIncentive = shouldSkipCalculatingFinalIncentive(newPPMShipment, &oldPPMShipment, originalTotalWeight, newTotalWeight) - if !skipCalculateFinalIncentive { - contractDate := newPPMShipment.ExpectedDepartureDate - if newPPMShipment.ActualMoveDate != nil { - contractDate = *newPPMShipment.ActualMoveDate - } - contract, err := serviceparamvaluelookups.FetchContract(appCtx, contractDate) - if err != nil { - return nil, err + finalIncentive, err := f.calculatePrice(appCtx, newPPMShipment, newTotalWeight, contract, false) + if err != nil { + return nil, err + } + return finalIncentive, nil } + } else { + finalIncentive = nil - finalIncentive, err = f.calculatePrice(appCtx, newPPMShipment, newTotalWeight, contract, false) + return finalIncentive, nil + } + + return finalIncentive, nil + } else { + pickupAddress := newPPMShipment.PickupAddress + destinationAddress := newPPMShipment.DestinationAddress + + // we can't calculate actual incentive without the weight + if newTotalWeight != 0 { + finalIncentive, err := f.CalculateOCONUSIncentive(appCtx, newPPMShipment.ID, *pickupAddress, *destinationAddress, contractDate, newTotalWeight.Int(), false, true, false) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to calculate estimated PPM incentive: %w", err) } + return finalIncentive, nil + } else { + return nil, nil } - } else { - finalIncentive = nil } - - return finalIncentive, nil } // SumWeightTickets return the total weight of all weightTickets associated with a PPMShipment, returns 0 if there is no valid weight @@ -372,7 +443,7 @@ func (f estimatePPM) calculatePrice(appCtx appcontext.AppContext, ppmShipment *m logger := appCtx.Logger() zeroTotal := false - serviceItemsToPrice := BaseServiceItems(ppmShipment.ShipmentID) + serviceItemsToPrice := BaseServiceItems(*ppmShipment) var move models.Move err := appCtx.DB().Q().Eager( @@ -462,7 +533,7 @@ func (f estimatePPM) calculatePrice(appCtx appcontext.AppContext, ppmShipment *m serviceItemLookups := serviceparamvaluelookups.InitializeLookups(appCtx, mtoShipment, serviceItem) // This is the struct that gets passed to every param lookup() method that was initialized above - keyData := serviceparamvaluelookups.NewServiceItemParamKeyData(f.planner, serviceItemLookups, serviceItem, mtoShipment, contract.Code) + keyData := serviceparamvaluelookups.NewServiceItemParamKeyData(f.planner, serviceItemLookups, serviceItem, mtoShipment, contract.Code, contract.ID) // The distance value gets saved to the mto shipment model to reduce repeated api calls. var shipmentWithDistance models.MTOShipment @@ -540,7 +611,7 @@ func (f estimatePPM) priceBreakdown(appCtx appcontext.AppContext, ppmShipment *m var unpacking unit.Cents var storage unit.Cents - serviceItemsToPrice := BaseServiceItems(ppmShipment.ShipmentID) + serviceItemsToPrice := BaseServiceItems(*ppmShipment) // Replace linehaul pricer with shorthaul pricer if move is within the same Zip3 var pickupPostal, destPostal string @@ -625,7 +696,7 @@ func (f estimatePPM) priceBreakdown(appCtx appcontext.AppContext, ppmShipment *m serviceItemLookups := serviceparamvaluelookups.InitializeLookups(appCtx, mtoShipment, serviceItem) // This is the struct that gets passed to every param lookup() method that was initialized above - keyData := serviceparamvaluelookups.NewServiceItemParamKeyData(f.planner, serviceItemLookups, serviceItem, mtoShipment, contract.Code) + keyData := serviceparamvaluelookups.NewServiceItemParamKeyData(f.planner, serviceItemLookups, serviceItem, mtoShipment, contract.Code, contract.ID) // The distance value gets saved to the mto shipment model to reduce repeated api calls. var shipmentWithDistance models.MTOShipment @@ -693,40 +764,117 @@ func (f estimatePPM) priceBreakdown(appCtx appcontext.AppContext, ppmShipment *m return linehaul, fuel, origin, dest, packing, unpacking, storage, nil } -func CalculateSITCost(appCtx appcontext.AppContext, ppmShipment *models.PPMShipment, contract models.ReContract) (*unit.Cents, error) { - logger := appCtx.Logger() +// function for calculating incentives for OCONUS PPM shipments +// this uses a db function that takes in values needed to come up with the estimated/actual/max incentives +// this simulates the reimbursement for an iHHG move with ISLH, IHPK, IHUPK, and CONUS portion of FSC +func (f *estimatePPM) CalculateOCONUSIncentive(appCtx appcontext.AppContext, ppmShipmentID uuid.UUID, pickupAddress models.Address, destinationAddress models.Address, moveDate time.Time, weight int, isEstimated bool, isActual bool, isMax bool) (*unit.Cents, error) { + var mileage int + ppmPort, err := models.FetchPortLocationByCode(appCtx.DB(), "4E1") // Tacoma, WA port + if err != nil { + return nil, fmt.Errorf("failed to fetch port location: %w", err) + } + + // check if addresses are OCONUS or CONUS -> this determines how we check mileage to/from the authorized port + isPickupOconus := pickupAddress.IsOconus != nil && *pickupAddress.IsOconus + isDestinationOconus := destinationAddress.IsOconus != nil && *destinationAddress.IsOconus + + switch { + case isPickupOconus && isDestinationOconus: + // OCONUS -> OCONUS, we only reimburse for the CONUS mileage of the PPM + mileage = 0 + case isPickupOconus && !isDestinationOconus: + // OCONUS -> CONUS (port ZIP -> address ZIP) + mileage, err = f.planner.ZipTransitDistance(appCtx, ppmPort.UsPostRegionCity.UsprZipID, destinationAddress.PostalCode, true) + if err != nil { + return nil, fmt.Errorf("failed to calculate OCONUS to CONUS mileage: %w", err) + } + case !isPickupOconus && isDestinationOconus: + // CONUS -> OCONUS (address ZIP -> port ZIP) + mileage, err = f.planner.ZipTransitDistance(appCtx, pickupAddress.PostalCode, ppmPort.UsPostRegionCity.UsprZipID, true) + if err != nil { + return nil, fmt.Errorf("failed to calculate CONUS to OCONUS mileage: %w", err) + } + default: + // covering down on CONUS -> CONUS moves - they should not appear here + return nil, fmt.Errorf("invalid pickup and destination configuration: pickup isOconus=%v, destination isOconus=%v", isPickupOconus, isDestinationOconus) + } + + incentive, err := models.CalculatePPMIncentive(appCtx.DB(), ppmShipmentID, pickupAddress.ID, destinationAddress.ID, moveDate, mileage, weight, isEstimated, isActual, isMax) + if err != nil { + return nil, fmt.Errorf("failed to calculate PPM incentive: %w", err) + } + + return (*unit.Cents)(&incentive.TotalIncentive), nil +} + +func (f *estimatePPM) CalculateOCONUSSITCosts(appCtx appcontext.AppContext, ppmID uuid.UUID, addressID uuid.UUID, isOrigin bool, moveDate time.Time, weight int, sitDays int) (*unit.Cents, error) { + if sitDays <= 0 { + return nil, fmt.Errorf("SIT days must be greater than zero") + } + + if weight <= 0 { + return nil, fmt.Errorf("weight must be greater than zero") + } + sitCosts, err := models.CalculatePPMSITCost(appCtx.DB(), ppmID, addressID, isOrigin, moveDate, weight, sitDays) + if err != nil { + return nil, fmt.Errorf("failed to calculate SIT costs: %w", err) + } + + return (*unit.Cents)(&sitCosts.TotalSITCost), nil +} + +func CalculateSITCost(appCtx appcontext.AppContext, ppmShipment *models.PPMShipment, contract models.ReContract) (*unit.Cents, error) { additionalDaysInSIT := additionalDaysInSIT(*ppmShipment.SITEstimatedEntryDate, *ppmShipment.SITEstimatedDepartureDate) - serviceItemsToPrice := StorageServiceItems(ppmShipment.ShipmentID, *ppmShipment.SITLocation, additionalDaysInSIT) + if ppmShipment.Shipment.MarketCode != models.MarketCodeInternational { + logger := appCtx.Logger() - totalPrice := unit.Cents(0) - for _, serviceItem := range serviceItemsToPrice { - pricer, err := ghcrateengine.PricerForServiceItem(serviceItem.ReService.Code) - if err != nil { - logger.Error("unable to find pricer for service item", zap.Error(err)) - return nil, err + serviceItemsToPrice := StorageServiceItems(*ppmShipment, *ppmShipment.SITLocation, additionalDaysInSIT) + + totalPrice := unit.Cents(0) + for _, serviceItem := range serviceItemsToPrice { + pricer, err := ghcrateengine.PricerForServiceItem(serviceItem.ReService.Code) + if err != nil { + logger.Error("unable to find pricer for service item", zap.Error(err)) + return nil, err + } + + var price *unit.Cents + switch serviceItemPricer := pricer.(type) { + case services.DomesticOriginFirstDaySITPricer, services.DomesticDestinationFirstDaySITPricer: + price, _, err = priceFirstDaySIT(appCtx, serviceItemPricer, serviceItem, ppmShipment, contract) + case services.DomesticOriginAdditionalDaysSITPricer, services.DomesticDestinationAdditionalDaysSITPricer: + price, _, err = priceAdditionalDaySIT(appCtx, serviceItemPricer, serviceItem, ppmShipment, additionalDaysInSIT, contract) + default: + return nil, fmt.Errorf("unknown SIT pricer type found for service item code %s", serviceItem.ReService.Code) + } + + if err != nil { + return nil, err + } + + logger.Debug(fmt.Sprintf("Price of service item %s %d", serviceItem.ReService.Code, *price)) + totalPrice += *price } - var price *unit.Cents - switch serviceItemPricer := pricer.(type) { - case services.DomesticOriginFirstDaySITPricer, services.DomesticDestinationFirstDaySITPricer: - price, _, err = priceFirstDaySIT(appCtx, serviceItemPricer, serviceItem, ppmShipment, contract) - case services.DomesticOriginAdditionalDaysSITPricer, services.DomesticDestinationAdditionalDaysSITPricer: - price, _, err = priceAdditionalDaySIT(appCtx, serviceItemPricer, serviceItem, ppmShipment, additionalDaysInSIT, contract) - default: - return nil, fmt.Errorf("unknown SIT pricer type found for service item code %s", serviceItem.ReService.Code) + return &totalPrice, nil + } else { + var sitAddress models.Address + isOrigin := *ppmShipment.SITLocation == models.SITLocationTypeOrigin + if isOrigin { + sitAddress = *ppmShipment.PickupAddress + } else { + sitAddress = *ppmShipment.DestinationAddress } + contractDate := ppmShipment.ExpectedDepartureDate + totalSITCost, err := models.CalculatePPMSITCost(appCtx.DB(), ppmShipment.ID, sitAddress.ID, isOrigin, contractDate, ppmShipment.SITEstimatedWeight.Int(), additionalDaysInSIT) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to calculate PPM SIT incentive: %w", err) } - - logger.Debug(fmt.Sprintf("Price of service item %s %d", serviceItem.ReService.Code, *price)) - totalPrice += *price + return (*unit.Cents)(&totalSITCost.TotalSITCost), nil } - - return &totalPrice, nil } func CalculateSITCostBreakdown(appCtx appcontext.AppContext, ppmShipment *models.PPMShipment, contract models.ReContract) (*models.PPMSITEstimatedCostInfo, error) { @@ -736,7 +884,7 @@ func CalculateSITCostBreakdown(appCtx appcontext.AppContext, ppmShipment *models additionalDaysInSIT := additionalDaysInSIT(*ppmShipment.SITEstimatedEntryDate, *ppmShipment.SITEstimatedDepartureDate) - serviceItemsToPrice := StorageServiceItems(ppmShipment.ShipmentID, *ppmShipment.SITLocation, additionalDaysInSIT) + serviceItemsToPrice := StorageServiceItems(*ppmShipment, *ppmShipment.SITLocation, additionalDaysInSIT) totalPrice := unit.Cents(0) for _, serviceItem := range serviceItemsToPrice { @@ -750,8 +898,12 @@ func CalculateSITCostBreakdown(appCtx appcontext.AppContext, ppmShipment *models switch serviceItemPricer := pricer.(type) { case services.DomesticOriginFirstDaySITPricer, services.DomesticDestinationFirstDaySITPricer: price, ppmSITEstimatedCostInfoData, err = calculateFirstDaySITCostBreakdown(appCtx, serviceItemPricer, serviceItem, ppmShipment, contract, ppmSITEstimatedCostInfoData, logger) + case services.IntlOriginFirstDaySITPricer, services.IntlDestinationFirstDaySITPricer: + price, ppmSITEstimatedCostInfoData, err = calculateIntlFirstDaySITCostBreakdown(appCtx, serviceItemPricer, serviceItem, ppmShipment, contract, ppmSITEstimatedCostInfoData, logger) case services.DomesticOriginAdditionalDaysSITPricer, services.DomesticDestinationAdditionalDaysSITPricer: price, ppmSITEstimatedCostInfoData, err = calculateAdditionalDaySITCostBreakdown(appCtx, serviceItemPricer, serviceItem, ppmShipment, contract, additionalDaysInSIT, ppmSITEstimatedCostInfoData, logger) + case services.IntlOriginAdditionalDaySITPricer, services.IntlDestinationAdditionalDaySITPricer: + price, ppmSITEstimatedCostInfoData, err = calculateIntlAdditionalDaySITCostBreakdown(appCtx, serviceItemPricer, serviceItem, ppmShipment, contract, additionalDaysInSIT, ppmSITEstimatedCostInfoData, logger) default: return nil, fmt.Errorf("unknown SIT pricer type found for service item code %s", serviceItem.ReService.Code) } @@ -795,6 +947,33 @@ func calculateFirstDaySITCostBreakdown(appCtx appcontext.AppContext, serviceItem return price, ppmSITEstimatedCostInfoData, nil } +func calculateIntlFirstDaySITCostBreakdown(appCtx appcontext.AppContext, serviceItemPricer services.ParamsPricer, serviceItem models.MTOServiceItem, ppmShipment *models.PPMShipment, contract models.ReContract, ppmSITEstimatedCostInfoData *models.PPMSITEstimatedCostInfo, logger *zap.Logger) (*unit.Cents, *models.PPMSITEstimatedCostInfo, error) { + price, priceParams, err := priceFirstDaySIT(appCtx, serviceItemPricer, serviceItem, ppmShipment, contract) + if err != nil { + return nil, nil, err + } + ppmSITEstimatedCostInfoData.PriceFirstDaySIT = price + for _, param := range priceParams { + switch param.Key { + case models.ServiceItemParamNameServiceAreaOrigin: + ppmSITEstimatedCostInfoData.ParamsFirstDaySIT.ServiceAreaOrigin = param.Value + case models.ServiceItemParamNameServiceAreaDest: + ppmSITEstimatedCostInfoData.ParamsFirstDaySIT.ServiceAreaDestination = param.Value + case models.ServiceItemParamNameIsPeak: + ppmSITEstimatedCostInfoData.ParamsFirstDaySIT.IsPeak = param.Value + case models.ServiceItemParamNameContractYearName: + ppmSITEstimatedCostInfoData.ParamsFirstDaySIT.ContractYearName = param.Value + case models.ServiceItemParamNamePriceRateOrFactor: + ppmSITEstimatedCostInfoData.ParamsFirstDaySIT.PriceRateOrFactor = param.Value + case models.ServiceItemParamNameEscalationCompounded: + ppmSITEstimatedCostInfoData.ParamsFirstDaySIT.EscalationCompounded = param.Value + default: + logger.Debug(fmt.Sprintf("Unexpected ServiceItemParam in PPM First Day SIT: %s, %s", param.Key, param.Value)) + } + } + return price, ppmSITEstimatedCostInfoData, nil +} + func calculateAdditionalDaySITCostBreakdown(appCtx appcontext.AppContext, serviceItemPricer services.ParamsPricer, serviceItem models.MTOServiceItem, ppmShipment *models.PPMShipment, contract models.ReContract, additionalDaysInSIT int, ppmSITEstimatedCostInfoData *models.PPMSITEstimatedCostInfo, logger *zap.Logger) (*unit.Cents, *models.PPMSITEstimatedCostInfo, error) { price, priceParams, err := priceAdditionalDaySIT(appCtx, serviceItemPricer, serviceItem, ppmShipment, additionalDaysInSIT, contract) if err != nil { @@ -824,56 +1003,119 @@ func calculateAdditionalDaySITCostBreakdown(appCtx appcontext.AppContext, servic return price, ppmSITEstimatedCostInfoData, nil } -func priceFirstDaySIT(appCtx appcontext.AppContext, pricer services.ParamsPricer, serviceItem models.MTOServiceItem, ppmShipment *models.PPMShipment, contract models.ReContract) (*unit.Cents, services.PricingDisplayParams, error) { - firstDayPricer, ok := pricer.(services.DomesticFirstDaySITPricer) - if !ok { - return nil, nil, errors.New("ppm estimate pricer for SIT service item does not implement the first day pricer interface") - } - - // Need to declare if origin or destination for the serviceAreaLookup, otherwise we already have it - serviceAreaPostalCode := ppmShipment.PickupAddress.PostalCode - serviceAreaKey := models.ServiceItemParamNameServiceAreaOrigin - if serviceItem.ReService.Code == models.ReServiceCodeDDFSIT { - serviceAreaPostalCode = ppmShipment.DestinationAddress.PostalCode - serviceAreaKey = models.ServiceItemParamNameServiceAreaDest - } - - serviceAreaLookup := serviceparamvaluelookups.ServiceAreaLookup{ - Address: models.Address{PostalCode: serviceAreaPostalCode}, - } - serviceArea, err := serviceAreaLookup.ParamValue(appCtx, contract.Code) +func calculateIntlAdditionalDaySITCostBreakdown(appCtx appcontext.AppContext, serviceItemPricer services.ParamsPricer, serviceItem models.MTOServiceItem, ppmShipment *models.PPMShipment, contract models.ReContract, additionalDaysInSIT int, ppmSITEstimatedCostInfoData *models.PPMSITEstimatedCostInfo, logger *zap.Logger) (*unit.Cents, *models.PPMSITEstimatedCostInfo, error) { + price, priceParams, err := priceAdditionalDaySIT(appCtx, serviceItemPricer, serviceItem, ppmShipment, additionalDaysInSIT, contract) if err != nil { return nil, nil, err } - - serviceAreaParam := services.PricingDisplayParam{ - Key: serviceAreaKey, - Value: serviceArea, + ppmSITEstimatedCostInfoData.PriceAdditionalDaySIT = price + for _, param := range priceParams { + switch param.Key { + case models.ServiceItemParamNameServiceAreaOrigin: + ppmSITEstimatedCostInfoData.ParamsAdditionalDaySIT.ServiceAreaOrigin = param.Value + case models.ServiceItemParamNameServiceAreaDest: + ppmSITEstimatedCostInfoData.ParamsAdditionalDaySIT.ServiceAreaDestination = param.Value + case models.ServiceItemParamNameIsPeak: + ppmSITEstimatedCostInfoData.ParamsAdditionalDaySIT.IsPeak = param.Value + case models.ServiceItemParamNameContractYearName: + ppmSITEstimatedCostInfoData.ParamsAdditionalDaySIT.ContractYearName = param.Value + case models.ServiceItemParamNamePriceRateOrFactor: + ppmSITEstimatedCostInfoData.ParamsAdditionalDaySIT.PriceRateOrFactor = param.Value + case models.ServiceItemParamNameEscalationCompounded: + ppmSITEstimatedCostInfoData.ParamsAdditionalDaySIT.EscalationCompounded = param.Value + case models.ServiceItemParamNameNumberDaysSIT: + ppmSITEstimatedCostInfoData.ParamsAdditionalDaySIT.NumberDaysSIT = param.Value + default: + logger.Debug(fmt.Sprintf("Unexpected ServiceItemParam in PPM Additional Day SIT: %s, %s", param.Key, param.Value)) + } } + return price, ppmSITEstimatedCostInfoData, nil +} - // Since this function may be ran before closeout, we need to account for if there's no actual move date yet. - if ppmShipment.ActualMoveDate != nil { - price, pricingParams, err := firstDayPricer.Price(appCtx, contract.Code, *ppmShipment.ActualMoveDate, *ppmShipment.SITEstimatedWeight, serviceArea, true) +func priceFirstDaySIT(appCtx appcontext.AppContext, pricer services.ParamsPricer, serviceItem models.MTOServiceItem, ppmShipment *models.PPMShipment, contract models.ReContract) (*unit.Cents, services.PricingDisplayParams, error) { + if serviceItem.ReService.Code == models.ReServiceCodeIOFSIT || serviceItem.ReService.Code == models.ReServiceCodeIDFSIT { + var addressID uuid.UUID + if serviceItem.ReService.Code == models.ReServiceCodeIOFSIT { + addressID = *ppmShipment.PickupAddressID + } else { + addressID = *ppmShipment.DestinationAddressID + } + reServiceID, _ := models.FetchReServiceByCode(appCtx.DB(), serviceItem.ReService.Code) + intlOtherPrice, _ := models.FetchReIntlOtherPrice(appCtx.DB(), addressID, reServiceID.ID, contract.ID, &ppmShipment.ExpectedDepartureDate) + firstDayPricer, ok := pricer.(services.IntlOriginFirstDaySITPricer) + if !ok { + return nil, nil, errors.New("ppm estimate pricer for SIT service item does not implement the first day pricer interface") + } + if ppmShipment.ActualMoveDate != nil { + price, pricingParams, err := firstDayPricer.Price(appCtx, contract.Code, *ppmShipment.ActualMoveDate, *ppmShipment.SITEstimatedWeight, intlOtherPrice.PerUnitCents.Int()) + if err != nil { + return nil, nil, err + } + + appCtx.Logger().Debug(fmt.Sprintf("Pricing params for first day SIT %+v", pricingParams), zap.String("shipmentId", ppmShipment.ShipmentID.String())) + + return &price, pricingParams, nil + } + + price, pricingParams, err := firstDayPricer.Price(appCtx, contract.Code, ppmShipment.ExpectedDepartureDate, *ppmShipment.SITEstimatedWeight, intlOtherPrice.PerUnitCents.Int()) if err != nil { return nil, nil, err } - pricingParams = append(pricingParams, serviceAreaParam) - appCtx.Logger().Debug(fmt.Sprintf("Pricing params for first day SIT %+v", pricingParams), zap.String("shipmentId", ppmShipment.ShipmentID.String())) return &price, pricingParams, nil - } - price, pricingParams, err := firstDayPricer.Price(appCtx, contract.Code, ppmShipment.ExpectedDepartureDate, *ppmShipment.SITEstimatedWeight, serviceArea, true) - if err != nil { - return nil, nil, err - } + } else { + firstDayPricer, ok := pricer.(services.DomesticFirstDaySITPricer) + if !ok { + return nil, nil, errors.New("ppm estimate pricer for SIT service item does not implement the first day pricer interface") + } + + // Need to declare if origin or destination for the serviceAreaLookup, otherwise we already have it + serviceAreaPostalCode := ppmShipment.PickupAddress.PostalCode + serviceAreaKey := models.ServiceItemParamNameServiceAreaOrigin + if serviceItem.ReService.Code == models.ReServiceCodeDDFSIT { + serviceAreaPostalCode = ppmShipment.DestinationAddress.PostalCode + serviceAreaKey = models.ServiceItemParamNameServiceAreaDest + } + + serviceAreaLookup := serviceparamvaluelookups.ServiceAreaLookup{ + Address: models.Address{PostalCode: serviceAreaPostalCode}, + } + serviceArea, err := serviceAreaLookup.ParamValue(appCtx, contract.Code) + if err != nil { + return nil, nil, err + } + + serviceAreaParam := services.PricingDisplayParam{ + Key: serviceAreaKey, + Value: serviceArea, + } + + // Since this function may be ran before closeout, we need to account for if there's no actual move date yet. + if ppmShipment.ActualMoveDate != nil { + price, pricingParams, err := firstDayPricer.Price(appCtx, contract.Code, *ppmShipment.ActualMoveDate, *ppmShipment.SITEstimatedWeight, serviceArea, true) + if err != nil { + return nil, nil, err + } + + pricingParams = append(pricingParams, serviceAreaParam) + + appCtx.Logger().Debug(fmt.Sprintf("Pricing params for first day SIT %+v", pricingParams), zap.String("shipmentId", ppmShipment.ShipmentID.String())) + + return &price, pricingParams, nil + } + price, pricingParams, err := firstDayPricer.Price(appCtx, contract.Code, ppmShipment.ExpectedDepartureDate, *ppmShipment.SITEstimatedWeight, serviceArea, true) + if err != nil { + return nil, nil, err + } - pricingParams = append(pricingParams, serviceAreaParam) + pricingParams = append(pricingParams, serviceAreaParam) - appCtx.Logger().Debug(fmt.Sprintf("Pricing params for first day SIT %+v", pricingParams), zap.String("shipmentId", ppmShipment.ShipmentID.String())) + appCtx.Logger().Debug(fmt.Sprintf("Pricing params for first day SIT %+v", pricingParams), zap.String("shipmentId", ppmShipment.ShipmentID.String())) - return &price, pricingParams, nil + return &price, pricingParams, nil + } } func additionalDaysInSIT(sitEntryDate time.Time, sitDepartureDate time.Time) int { @@ -887,69 +1129,116 @@ func additionalDaysInSIT(sitEntryDate time.Time, sitDepartureDate time.Time) int } func priceAdditionalDaySIT(appCtx appcontext.AppContext, pricer services.ParamsPricer, serviceItem models.MTOServiceItem, ppmShipment *models.PPMShipment, additionalDaysInSIT int, contract models.ReContract) (*unit.Cents, services.PricingDisplayParams, error) { - additionalDaysPricer, ok := pricer.(services.DomesticAdditionalDaysSITPricer) - if !ok { - return nil, nil, errors.New("ppm estimate pricer for SIT service item does not implement the additional days pricer interface") - } + // international shipment logic + if serviceItem.ReService.Code == models.ReServiceCodeIOASIT || serviceItem.ReService.Code == models.ReServiceCodeIDASIT { + // address we need for the per_unit_cents is dependent on if it's origin/destination SIT + var addressID uuid.UUID + if serviceItem.ReService.Code == models.ReServiceCodeIOASIT { + addressID = *ppmShipment.PickupAddressID + } else { + addressID = *ppmShipment.DestinationAddressID + } - // Need to declare if origin or destination for the serviceAreaLookup, otherwise we already have it - serviceAreaPostalCode := ppmShipment.PickupAddress.PostalCode - serviceAreaKey := models.ServiceItemParamNameServiceAreaOrigin - if serviceItem.ReService.Code == models.ReServiceCodeDDASIT { - serviceAreaPostalCode = ppmShipment.DestinationAddress.PostalCode - serviceAreaKey = models.ServiceItemParamNameServiceAreaDest - } - serviceAreaLookup := serviceparamvaluelookups.ServiceAreaLookup{ - Address: models.Address{PostalCode: serviceAreaPostalCode}, - } + var moveDate time.Time + if ppmShipment.ActualMoveDate != nil { + moveDate = *ppmShipment.ActualMoveDate + } else { + moveDate = ppmShipment.ExpectedDepartureDate + } - serviceArea, err := serviceAreaLookup.ParamValue(appCtx, contract.Code) - if err != nil { - return nil, nil, err - } + reServiceID, _ := models.FetchReServiceByCode(appCtx.DB(), serviceItem.ReService.Code) + intlOtherPrice, _ := models.FetchReIntlOtherPrice(appCtx.DB(), addressID, reServiceID.ID, contract.ID, &moveDate) - serviceAreaParam := services.PricingDisplayParam{ - Key: serviceAreaKey, - Value: serviceArea, - } + sitDaysParam := services.PricingDisplayParam{ + Key: models.ServiceItemParamNameNumberDaysSIT, + Value: strconv.Itoa(additionalDaysInSIT), + } - sitDaysParam := services.PricingDisplayParam{ - Key: models.ServiceItemParamNameNumberDaysSIT, - Value: strconv.Itoa(additionalDaysInSIT), - } + additionalDayPricer, ok := pricer.(services.IntlOriginAdditionalDaySITPricer) + if !ok { + return nil, nil, errors.New("ppm estimate pricer for SIT service item does not implement the first day pricer interface") + } - // Since this function may be ran before closeout, we need to account for if there's no actual move date yet. - if ppmShipment.ActualMoveDate != nil { - price, pricingParams, err := additionalDaysPricer.Price(appCtx, contract.Code, *ppmShipment.ActualMoveDate, *ppmShipment.SITEstimatedWeight, serviceArea, additionalDaysInSIT, true) + price, pricingParams, err := additionalDayPricer.Price(appCtx, contract.Code, moveDate, additionalDaysInSIT, *ppmShipment.SITEstimatedWeight, intlOtherPrice.PerUnitCents.Int()) if err != nil { return nil, nil, err } - pricingParams = append(pricingParams, serviceAreaParam, sitDaysParam) + pricingParams = append(pricingParams, sitDaysParam) appCtx.Logger().Debug(fmt.Sprintf("Pricing params for additional day SIT %+v", pricingParams), zap.String("shipmentId", ppmShipment.ShipmentID.String())) return &price, pricingParams, nil - } - price, pricingParams, err := additionalDaysPricer.Price(appCtx, contract.Code, ppmShipment.ExpectedDepartureDate, *ppmShipment.SITEstimatedWeight, serviceArea, additionalDaysInSIT, true) - if err != nil { - return nil, nil, err - } + } else { + // domestic PPMs + additionalDaysPricer, ok := pricer.(services.DomesticAdditionalDaysSITPricer) + if !ok { + return nil, nil, errors.New("ppm estimate pricer for SIT service item does not implement the additional days pricer interface") + } - pricingParams = append(pricingParams, serviceAreaParam, sitDaysParam) + // Need to declare if origin or destination for the serviceAreaLookup, otherwise we already have it + serviceAreaPostalCode := ppmShipment.PickupAddress.PostalCode + serviceAreaKey := models.ServiceItemParamNameServiceAreaOrigin + if serviceItem.ReService.Code == models.ReServiceCodeDDASIT { + serviceAreaPostalCode = ppmShipment.DestinationAddress.PostalCode + serviceAreaKey = models.ServiceItemParamNameServiceAreaDest + } + serviceAreaLookup := serviceparamvaluelookups.ServiceAreaLookup{ + Address: models.Address{PostalCode: serviceAreaPostalCode}, + } - appCtx.Logger().Debug(fmt.Sprintf("Pricing params for additional day SIT %+v", pricingParams), zap.String("shipmentId", ppmShipment.ShipmentID.String())) + serviceArea, err := serviceAreaLookup.ParamValue(appCtx, contract.Code) + if err != nil { + return nil, nil, err + } + + serviceAreaParam := services.PricingDisplayParam{ + Key: serviceAreaKey, + Value: serviceArea, + } + + sitDaysParam := services.PricingDisplayParam{ + Key: models.ServiceItemParamNameNumberDaysSIT, + Value: strconv.Itoa(additionalDaysInSIT), + } + + // Since this function may be ran before closeout, we need to account for if there's no actual move date yet. + if ppmShipment.ActualMoveDate != nil { + price, pricingParams, err := additionalDaysPricer.Price(appCtx, contract.Code, *ppmShipment.ActualMoveDate, *ppmShipment.SITEstimatedWeight, serviceArea, additionalDaysInSIT, true) + if err != nil { + return nil, nil, err + } - return &price, pricingParams, nil + pricingParams = append(pricingParams, serviceAreaParam, sitDaysParam) + + appCtx.Logger().Debug(fmt.Sprintf("Pricing params for additional day SIT %+v", pricingParams), zap.String("shipmentId", ppmShipment.ShipmentID.String())) + + return &price, pricingParams, nil + } + price, pricingParams, err := additionalDaysPricer.Price(appCtx, contract.Code, ppmShipment.ExpectedDepartureDate, *ppmShipment.SITEstimatedWeight, serviceArea, additionalDaysInSIT, true) + if err != nil { + return nil, nil, err + } + + pricingParams = append(pricingParams, serviceAreaParam, sitDaysParam) + + appCtx.Logger().Debug(fmt.Sprintf("Pricing params for additional day SIT %+v", pricingParams), zap.String("shipmentId", ppmShipment.ShipmentID.String())) + + return &price, pricingParams, nil + } } // mapPPMShipmentEstimatedFields remaps our PPMShipment specific information into the fields where the service param lookups // expect to find them on the MTOShipment model. This is only in-memory and shouldn't get saved to the database. func MapPPMShipmentEstimatedFields(appCtx appcontext.AppContext, ppmShipment models.PPMShipment) (models.MTOShipment, error) { + ppmShipment.Shipment.PPMShipment = &ppmShipment + ppmShipment.Shipment.ShipmentType = models.MTOShipmentTypePPM ppmShipment.Shipment.ActualPickupDate = &ppmShipment.ExpectedDepartureDate ppmShipment.Shipment.RequestedPickupDate = &ppmShipment.ExpectedDepartureDate + ppmShipment.Shipment.PickupAddress = ppmShipment.PickupAddress ppmShipment.Shipment.PickupAddress = &models.Address{PostalCode: ppmShipment.PickupAddress.PostalCode} + ppmShipment.Shipment.DestinationAddress = ppmShipment.DestinationAddress ppmShipment.Shipment.DestinationAddress = &models.Address{PostalCode: ppmShipment.DestinationAddress.PostalCode} ppmShipment.Shipment.PrimeActualWeight = ppmShipment.EstimatedWeight @@ -986,9 +1275,13 @@ func MapPPMShipmentMaxIncentiveFields(appCtx appcontext.AppContext, ppmShipment // expect to find them on the MTOShipment model. This is only in-memory and shouldn't get saved to the database. func MapPPMShipmentFinalFields(ppmShipment models.PPMShipment, totalWeight unit.Pound) models.MTOShipment { + ppmShipment.Shipment.PPMShipment = &ppmShipment + ppmShipment.Shipment.ShipmentType = models.MTOShipmentTypePPM ppmShipment.Shipment.ActualPickupDate = ppmShipment.ActualMoveDate ppmShipment.Shipment.RequestedPickupDate = ppmShipment.ActualMoveDate + ppmShipment.Shipment.PickupAddress = ppmShipment.PickupAddress ppmShipment.Shipment.PickupAddress = &models.Address{PostalCode: *ppmShipment.ActualPickupPostalCode} + ppmShipment.Shipment.DestinationAddress = ppmShipment.DestinationAddress ppmShipment.Shipment.DestinationAddress = &models.Address{PostalCode: *ppmShipment.ActualDestinationPostalCode} ppmShipment.Shipment.PrimeActualWeight = &totalWeight @@ -997,19 +1290,35 @@ func MapPPMShipmentFinalFields(ppmShipment models.PPMShipment, totalWeight unit. // baseServiceItems returns a list of the MTOServiceItems that makeup the price of the estimated incentive. These // are the same non-accesorial service items that get auto-created and approved when the TOO approves an HHG shipment. -func BaseServiceItems(mtoShipmentID uuid.UUID) []models.MTOServiceItem { - return []models.MTOServiceItem{ - {ReService: models.ReService{Code: models.ReServiceCodeDLH}, MTOShipmentID: &mtoShipmentID}, - {ReService: models.ReService{Code: models.ReServiceCodeFSC}, MTOShipmentID: &mtoShipmentID}, - {ReService: models.ReService{Code: models.ReServiceCodeDOP}, MTOShipmentID: &mtoShipmentID}, - {ReService: models.ReService{Code: models.ReServiceCodeDDP}, MTOShipmentID: &mtoShipmentID}, - {ReService: models.ReService{Code: models.ReServiceCodeDPK}, MTOShipmentID: &mtoShipmentID}, - {ReService: models.ReService{Code: models.ReServiceCodeDUPK}, MTOShipmentID: &mtoShipmentID}, +func BaseServiceItems(ppmShipment models.PPMShipment) []models.MTOServiceItem { + mtoShipmentID := ppmShipment.ShipmentID + isInternationalShipment := ppmShipment.Shipment.MarketCode == models.MarketCodeInternational + + if isInternationalShipment { + return []models.MTOServiceItem{ + {ReService: models.ReService{Code: models.ReServiceCodeFSC}, MTOShipmentID: &mtoShipmentID}, + {ReService: models.ReService{Code: models.ReServiceCodeIHPK}, MTOShipmentID: &mtoShipmentID}, + {ReService: models.ReService{Code: models.ReServiceCodeIHUPK}, MTOShipmentID: &mtoShipmentID}, + {ReService: models.ReService{Code: models.ReServiceCodeISLH}, MTOShipmentID: &mtoShipmentID}, + } + } else { + return []models.MTOServiceItem{ + {ReService: models.ReService{Code: models.ReServiceCodeDLH}, MTOShipmentID: &mtoShipmentID}, + {ReService: models.ReService{Code: models.ReServiceCodeFSC}, MTOShipmentID: &mtoShipmentID}, + {ReService: models.ReService{Code: models.ReServiceCodeDOP}, MTOShipmentID: &mtoShipmentID}, + {ReService: models.ReService{Code: models.ReServiceCodeDDP}, MTOShipmentID: &mtoShipmentID}, + {ReService: models.ReService{Code: models.ReServiceCodeDPK}, MTOShipmentID: &mtoShipmentID}, + {ReService: models.ReService{Code: models.ReServiceCodeDUPK}, MTOShipmentID: &mtoShipmentID}, + } } } -func StorageServiceItems(mtoShipmentID uuid.UUID, locationType models.SITLocationType, additionalDaysInSIT int) []models.MTOServiceItem { - if locationType == models.SITLocationTypeOrigin { +func StorageServiceItems(ppmShipment models.PPMShipment, locationType models.SITLocationType, additionalDaysInSIT int) []models.MTOServiceItem { + mtoShipmentID := ppmShipment.ShipmentID + isInternationalShipment := ppmShipment.Shipment.MarketCode == models.MarketCodeInternational + + // domestic shipments + if locationType == models.SITLocationTypeOrigin && !isInternationalShipment { if additionalDaysInSIT > 0 { return []models.MTOServiceItem{ {ReService: models.ReService{Code: models.ReServiceCodeDOFSIT}, MTOShipmentID: &mtoShipmentID}, @@ -1020,15 +1329,41 @@ func StorageServiceItems(mtoShipmentID uuid.UUID, locationType models.SITLocatio {ReService: models.ReService{Code: models.ReServiceCodeDOFSIT}, MTOShipmentID: &mtoShipmentID}} } - if additionalDaysInSIT > 0 { + if locationType == models.SITLocationTypeDestination && !isInternationalShipment { + if additionalDaysInSIT > 0 { + return []models.MTOServiceItem{ + {ReService: models.ReService{Code: models.ReServiceCodeDDFSIT}, MTOShipmentID: &mtoShipmentID}, + {ReService: models.ReService{Code: models.ReServiceCodeDDASIT}, MTOShipmentID: &mtoShipmentID}, + } + } + return []models.MTOServiceItem{ + {ReService: models.ReService{Code: models.ReServiceCodeDDFSIT}, MTOShipmentID: &mtoShipmentID}} + } + + // international shipments + if locationType == models.SITLocationTypeOrigin && isInternationalShipment { + if additionalDaysInSIT > 0 { + return []models.MTOServiceItem{ + {ReService: models.ReService{Code: models.ReServiceCodeIOFSIT}, MTOShipmentID: &mtoShipmentID}, + {ReService: models.ReService{Code: models.ReServiceCodeIOASIT}, MTOShipmentID: &mtoShipmentID}, + } + } return []models.MTOServiceItem{ - {ReService: models.ReService{Code: models.ReServiceCodeDDFSIT}, MTOShipmentID: &mtoShipmentID}, - {ReService: models.ReService{Code: models.ReServiceCodeDDASIT}, MTOShipmentID: &mtoShipmentID}, + {ReService: models.ReService{Code: models.ReServiceCodeIOFSIT}, MTOShipmentID: &mtoShipmentID}} + } + + if locationType == models.SITLocationTypeDestination && isInternationalShipment { + if additionalDaysInSIT > 0 { + return []models.MTOServiceItem{ + {ReService: models.ReService{Code: models.ReServiceCodeIDFSIT}, MTOShipmentID: &mtoShipmentID}, + {ReService: models.ReService{Code: models.ReServiceCodeIDASIT}, MTOShipmentID: &mtoShipmentID}, + } } + return []models.MTOServiceItem{ + {ReService: models.ReService{Code: models.ReServiceCodeIDFSIT}, MTOShipmentID: &mtoShipmentID}} } - return []models.MTOServiceItem{ - {ReService: models.ReService{Code: models.ReServiceCodeDDFSIT}, MTOShipmentID: &mtoShipmentID}} + return nil } // paramsForServiceCode filters the list of all service params for service items, to only those matching the service diff --git a/pkg/services/ppmshipment/ppm_estimator_test.go b/pkg/services/ppmshipment/ppm_estimator_test.go index 71b0749a7ea..f384223e3ba 100644 --- a/pkg/services/ppmshipment/ppm_estimator_test.go +++ b/pkg/services/ppmshipment/ppm_estimator_test.go @@ -492,13 +492,13 @@ func (suite *PPMShipmentSuite) TestPPMEstimator() { // DTOD distance is going to be less than the HHG Rand McNally distance of 2361 miles mockedPlanner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), - "50309", "30813", false, false).Return(2294, nil) + "50309", "30813", false).Return(2294, nil) linehaul, fuel, origin, dest, packing, unpacking, _, err := ppmEstimator.PriceBreakdown(suite.AppContextForTest(), &ppmShipment) suite.NilOrNoVerrs(err) mockedPlanner.AssertCalled(suite.T(), "ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), - "50309", "30813", false, false) + "50309", "30813", false) mockedPaymentRequestHelper.AssertCalled(suite.T(), "FetchServiceParamsForServiceItems", mock.AnythingOfType("*appcontext.appContext"), mock.AnythingOfType("[]models.MTOServiceItem")) suite.Equal(unit.Pound(4000), *ppmShipment.EstimatedWeight) @@ -537,13 +537,13 @@ func (suite *PPMShipmentSuite) TestPPMEstimator() { // DTOD distance is going to be less than the HHG Rand McNally distance of 2361 miles mockedPlanner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), - "50309", "30813", false, false).Return(2294, nil) + "50309", "30813", false).Return(2294, nil) ppmEstimate, _, err := ppmEstimator.EstimateIncentiveWithDefaultChecks(suite.AppContextForTest(), oldPPMShipment, &newPPM) suite.NilOrNoVerrs(err) mockedPlanner.AssertCalled(suite.T(), "ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), - "50309", "30813", false, false) + "50309", "30813", false) mockedPaymentRequestHelper.AssertCalled(suite.T(), "FetchServiceParamsForServiceItems", mock.AnythingOfType("*appcontext.appContext"), mock.AnythingOfType("[]models.MTOServiceItem")) suite.Equal(oldPPMShipment.PickupAddress.PostalCode, newPPM.PickupAddress.PostalCode) @@ -574,13 +574,13 @@ func (suite *PPMShipmentSuite) TestPPMEstimator() { // DTOD distance is going to be less than the HHG Rand McNally distance of 2361 miles mockedPlanner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), - "50309", "30813", false, false).Return(2294, nil) + "50309", "30813", false).Return(2294, nil) ppmEstimate, _, err := ppmEstimator.EstimateIncentiveWithDefaultChecks(suite.AppContextForTest(), oldPPMShipment, &newPPM) suite.NilOrNoVerrs(err) mockedPlanner.AssertCalled(suite.T(), "ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), - "50309", "30813", false, false) + "50309", "30813", false) mockedPaymentRequestHelper.AssertCalled(suite.T(), "FetchServiceParamsForServiceItems", mock.AnythingOfType("*appcontext.appContext"), mock.AnythingOfType("[]models.MTOServiceItem")) suite.Equal(oldPPMShipment.PickupAddress.PostalCode, newPPM.PickupAddress.PostalCode) @@ -608,13 +608,13 @@ func (suite *PPMShipmentSuite) TestPPMEstimator() { // DTOD distance is going to be less than the HHG Rand McNally distance of 2361 miles mockedPlanner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), - "50309", "30813", false, false).Return(2294, nil) + "50309", "30813", false).Return(2294, nil) ppmEstimate, _, err := ppmEstimator.EstimateIncentiveWithDefaultChecks(suite.AppContextForTest(), oldPPMShipment, &newPPM) suite.NilOrNoVerrs(err) mockedPlanner.AssertCalled(suite.T(), "ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), - "50309", "30813", false, false) + "50309", "30813", false) mockedPaymentRequestHelper.AssertCalled(suite.T(), "FetchServiceParamsForServiceItems", mock.AnythingOfType("*appcontext.appContext"), mock.AnythingOfType("[]models.MTOServiceItem")) suite.Equal(oldPPMShipment.PickupAddress.PostalCode, newPPM.PickupAddress.PostalCode) @@ -642,13 +642,13 @@ func (suite *PPMShipmentSuite) TestPPMEstimator() { // DTOD distance is going to be less than the HHG Rand McNally distance of 2361 miles mockedPlanner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), - "50309", "30813", false, false).Return(2294, nil) + "50309", "30813", false).Return(2294, nil) ppmEstimate, _, err := ppmEstimator.EstimateIncentiveWithDefaultChecks(suite.AppContextForTest(), oldPPMShipment, &newPPM) suite.NilOrNoVerrs(err) mockedPlanner.AssertCalled(suite.T(), "ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), - "50309", "30813", false, false) + "50309", "30813", false) mockedPaymentRequestHelper.AssertCalled(suite.T(), "FetchServiceParamsForServiceItems", mock.AnythingOfType("*appcontext.appContext"), mock.AnythingOfType("[]models.MTOServiceItem")) suite.Equal(oldPPMShipment.PickupAddress.PostalCode, newPPM.PickupAddress.PostalCode) @@ -678,7 +678,7 @@ func (suite *PPMShipmentSuite) TestPPMEstimator() { // DTOD distance is going to be less than the HHG Rand McNally distance of 2361 miles mockedPlanner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), - "50309", "30813", false, false).Return(2294, nil).Once() + "50309", "30813", false).Return(2294, nil).Once() ppmEstimate, _, err := ppmEstimator.EstimateIncentiveWithDefaultChecks(suite.AppContextForTest(), oldPPMShipment, &newPPM) suite.NilOrNoVerrs(err) @@ -696,6 +696,7 @@ func (suite *PPMShipmentSuite) TestPPMEstimator() { }, }, }, nil) + setupPricerData() newPPM := oldPPMShipment newPPM.HasProGear = models.BoolPointer(false) @@ -764,13 +765,13 @@ func (suite *PPMShipmentSuite) TestPPMEstimator() { mock.AnythingOfType("[]models.MTOServiceItem")).Return(serviceParams, nil) mockedPlanner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), - "50309", "30813", false, false).Return(2294, nil) + "50309", "30813", false).Return(2294, nil) maxIncentive, err := ppmEstimator.MaxIncentive(suite.AppContextForTest(), oldPPMShipment, &newPPM) suite.NilOrNoVerrs(err) mockedPlanner.AssertCalled(suite.T(), "ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), - "50309", "30813", false, false) + "50309", "30813", false) mockedPaymentRequestHelper.AssertCalled(suite.T(), "FetchServiceParamsForServiceItems", mock.AnythingOfType("*appcontext.appContext"), mock.AnythingOfType("[]models.MTOServiceItem")) suite.Equal(unit.Cents(128398858), *maxIncentive) @@ -825,13 +826,13 @@ func (suite *PPMShipmentSuite) TestPPMEstimator() { // DTOD distance is going to be less than the HHG Rand McNally distance of 2361 miles mockedPlanner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), - "50309", "30813", false, false).Return(2294, nil) + "50309", "30813", false).Return(2294, nil) ppmFinal, err := ppmEstimator.FinalIncentiveWithDefaultChecks(suite.AppContextForTest(), oldPPMShipment, &newPPM) suite.NilOrNoVerrs(err) mockedPlanner.AssertCalled(suite.T(), "ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), - "50309", "30813", false, false) + "50309", "30813", false) mockedPaymentRequestHelper.AssertCalled(suite.T(), "FetchServiceParamsForServiceItems", mock.AnythingOfType("*appcontext.appContext"), mock.AnythingOfType("[]models.MTOServiceItem")) suite.Equal(oldPPMShipment.ActualPickupPostalCode, newPPM.ActualPickupPostalCode) @@ -876,12 +877,12 @@ func (suite *PPMShipmentSuite) TestPPMEstimator() { mock.AnythingOfType("[]models.MTOServiceItem")).Return(serviceParams, nil) // DTOD distance is going to be less than the HHG Rand McNally distance of 2361 miles - mockedPlanner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), "50309", "30813", false, false).Return(2294, nil) + mockedPlanner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), "50309", "30813", false).Return(2294, nil) ppmFinal, err := ppmEstimator.FinalIncentiveWithDefaultChecks(suite.AppContextForTest(), oldPPMShipment, &newPPM) suite.NilOrNoVerrs(err) - mockedPlanner.AssertCalled(suite.T(), "ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), "50309", "30813", false, false) + mockedPlanner.AssertCalled(suite.T(), "ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), "50309", "30813", false) mockedPaymentRequestHelper.AssertCalled(suite.T(), "FetchServiceParamsForServiceItems", mock.AnythingOfType("*appcontext.appContext"), mock.AnythingOfType("[]models.MTOServiceItem")) suite.Equal(oldPPMShipment.ActualPickupPostalCode, newPPM.ActualPickupPostalCode) @@ -927,12 +928,12 @@ func (suite *PPMShipmentSuite) TestPPMEstimator() { // DTOD distance is going to be less than the HHG Rand McNally distance of 2361 miles mockedPlanner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), - "50309", "30813", false, false).Return(2294, nil) + "50309", "30813", false).Return(2294, nil) ppmFinalIncentiveLimitedByAllowableWeight, err := ppmEstimator.FinalIncentiveWithDefaultChecks(suite.AppContextForTest(), oldPPMShipment, &newPPM) suite.NilOrNoVerrs(err) - mockedPlanner.AssertCalled(suite.T(), "ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), "50309", "30813", false, false) + mockedPlanner.AssertCalled(suite.T(), "ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), "50309", "30813", false) mockedPaymentRequestHelper.AssertCalled(suite.T(), "FetchServiceParamsForServiceItems", mock.AnythingOfType("*appcontext.appContext"), mock.AnythingOfType("[]models.MTOServiceItem")) suite.Equal(oldPPMShipment.ActualPickupPostalCode, newPPM.ActualPickupPostalCode) @@ -982,13 +983,13 @@ func (suite *PPMShipmentSuite) TestPPMEstimator() { // DTOD distance is going to be less than the HHG Rand McNally distance of 2361 miles mockedPlanner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), - "50309", "30813", false, false).Return(2294, nil) + "50309", "30813", false).Return(2294, nil) ppmFinal, err := ppmEstimator.FinalIncentiveWithDefaultChecks(suite.AppContextForTest(), oldPPMShipment, &newPPM) suite.NilOrNoVerrs(err) mockedPlanner.AssertCalled(suite.T(), "ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), - "50309", "30813", false, false) + "50309", "30813", false) mockedPaymentRequestHelper.AssertCalled(suite.T(), "FetchServiceParamsForServiceItems", mock.AnythingOfType("*appcontext.appContext"), mock.AnythingOfType("[]models.MTOServiceItem")) suite.Equal(oldPPMShipment.ActualPickupPostalCode, newPPM.ActualPickupPostalCode) @@ -1039,13 +1040,13 @@ func (suite *PPMShipmentSuite) TestPPMEstimator() { // DTOD distance is going to be less than the HHG Rand McNally distance of 2361 miles mockedPlanner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), - "50309", "30813", false, false).Return(2294, nil) + "50309", "30813", false).Return(2294, nil) ppmFinal, err := ppmEstimator.FinalIncentiveWithDefaultChecks(suite.AppContextForTest(), oldPPMShipment, &newPPM) suite.NilOrNoVerrs(err) mockedPlanner.AssertCalled(suite.T(), "ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), - "50309", "30813", false, false) + "50309", "30813", false) mockedPaymentRequestHelper.AssertCalled(suite.T(), "FetchServiceParamsForServiceItems", mock.AnythingOfType("*appcontext.appContext"), mock.AnythingOfType("[]models.MTOServiceItem")) originalWeight, newWeight := SumWeightTickets(oldPPMShipment, newPPM) @@ -1101,13 +1102,13 @@ func (suite *PPMShipmentSuite) TestPPMEstimator() { // DTOD distance is going to be less than the HHG Rand McNally distance of 2361 miles mockedPlanner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), - "50309", "30813", false, false).Return(2294, nil) + "50309", "30813", false).Return(2294, nil) ppmFinal, err := ppmEstimator.FinalIncentiveWithDefaultChecks(suite.AppContextForTest(), oldPPMShipment, &newPPM) suite.NilOrNoVerrs(err) mockedPlanner.AssertCalled(suite.T(), "ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), - "50309", "30813", false, false) + "50309", "30813", false) mockedPaymentRequestHelper.AssertCalled(suite.T(), "FetchServiceParamsForServiceItems", mock.AnythingOfType("*appcontext.appContext"), mock.AnythingOfType("[]models.MTOServiceItem")) originalWeight, newWeight := SumWeightTickets(oldPPMShipment, newPPM) @@ -1171,13 +1172,13 @@ func (suite *PPMShipmentSuite) TestPPMEstimator() { // DTOD distance is going to be less than the HHG Rand McNally distance of 2361 miles mockedPlanner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), - "50309", "30813", false, false).Return(2294, nil) + "50309", "30813", false).Return(2294, nil) ppmFinal, err := ppmEstimator.FinalIncentiveWithDefaultChecks(suite.AppContextForTest(), oldPPMShipment, &newPPM) suite.NilOrNoVerrs(err) mockedPlanner.AssertCalled(suite.T(), "ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), - "50309", "30813", false, false) + "50309", "30813", false) mockedPaymentRequestHelper.AssertCalled(suite.T(), "FetchServiceParamsForServiceItems", mock.AnythingOfType("*appcontext.appContext"), mock.AnythingOfType("[]models.MTOServiceItem")) originalWeight, newWeight := SumWeightTickets(oldPPMShipment, newPPM) @@ -1559,6 +1560,7 @@ func (suite *PPMShipmentSuite) TestPPMEstimator() { }) suite.Run("Final Incentive - does not change when required fields are the same", func() { + setupPricerData() oldPPMShipment := factory.BuildPPMShipment(suite.DB(), []factory.Customization{ { Model: models.PPMShipment{ @@ -1607,6 +1609,7 @@ func (suite *PPMShipmentSuite) TestPPMEstimator() { }) suite.Run("Final Incentive - set to nil when missing info", func() { + setupPricerData() oldPPMShipment := factory.BuildPPMShipment(suite.DB(), []factory.Customization{ { Model: models.PPMShipment{ @@ -1666,7 +1669,7 @@ func (suite *PPMShipmentSuite) TestPPMEstimator() { }, nil) mockedPlanner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), - "50309", "30813", false, false).Return(2294, nil) + "50309", "30813", false).Return(2294, nil) _, estimatedSITCost, err := ppmEstimator.EstimateIncentiveWithDefaultChecks(suite.AppContextForTest(), models.PPMShipment{}, &shipmentOriginSIT) @@ -1698,7 +1701,7 @@ func (suite *PPMShipmentSuite) TestPPMEstimator() { }, }, { - Model: &models.Address{ + Model: models.Address{ StreetAddress1: "987 Other Avenue", StreetAddress2: models.StringPointer("P.O. Box 1234"), StreetAddress3: models.StringPointer("c/o Another Person"), @@ -1710,7 +1713,7 @@ func (suite *PPMShipmentSuite) TestPPMEstimator() { Type: &factory.Addresses.PickupAddress, }, { - Model: &models.Address{ + Model: models.Address{ StreetAddress1: "987 Other Avenue", StreetAddress2: models.StringPointer("P.O. Box 12345"), StreetAddress3: models.StringPointer("c/o Another Person"), @@ -1728,7 +1731,7 @@ func (suite *PPMShipmentSuite) TestPPMEstimator() { }, nil) mockedPlanner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), - "50309", "30813", false, false).Return(2294, nil) + "50309", "30813", false).Return(2294, nil) _, estimatedSITCost, err := ppmEstimator.EstimateIncentiveWithDefaultChecks(suite.AppContextForTest(), models.PPMShipment{}, &shipmentDestinationSIT) @@ -1760,37 +1763,13 @@ func (suite *PPMShipmentSuite) TestPPMEstimator() { SITEstimatedDepartureDate: &entryDate, }, }, - { - Model: models.Address{ - StreetAddress1: "987 Other Avenue", - StreetAddress2: models.StringPointer("P.O. Box 1234"), - StreetAddress3: models.StringPointer("c/o Another Person"), - City: "Des Moines", - State: "IA", - PostalCode: "50309", - County: models.StringPointer("POLK"), - }, - Type: &factory.Addresses.PickupAddress, - }, - { - Model: models.Address{ - StreetAddress1: "987 Other Avenue", - StreetAddress2: models.StringPointer("P.O. Box 12345"), - StreetAddress3: models.StringPointer("c/o Another Person"), - City: "Fort Eisenhower", - State: "GA", - PostalCode: "50309", - County: models.StringPointer("COLUMBIA"), - }, - Type: &factory.Addresses.DeliveryAddress, - }, { Model: mtoShipment, LinkOnly: true, }, }, nil) mockedPlanner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), - "50309", "30813", false, false).Return(2294, nil) + "50309", "30813", false).Return(2294, nil) _, estimatedSITCost, err := ppmEstimator.EstimateIncentiveWithDefaultChecks(suite.AppContextForTest(), models.PPMShipment{}, &shipmentOriginSIT) @@ -1980,10 +1959,10 @@ func (suite *PPMShipmentSuite) TestPPMEstimator() { shipmentDifferentDeparture.ExpectedDepartureDate = originalShipment.ExpectedDepartureDate.Add(time.Hour * 24 * 70) mockedPlanner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), - "90211", "30813", false, false).Return(2294, nil) + "90211", "30813", false).Return(2294, nil) mockedPlanner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), - "50309", "30814", false, false).Return(2290, nil) + "50309", "30814", false).Return(2290, nil) // SIT specific field changes will likely cause the price to change, although adjusting dates may not change // the total number of days in SIT. @@ -2049,3 +2028,623 @@ func (suite *PPMShipmentSuite) TestPPMEstimator() { }) }) } + +func (suite *PPMShipmentSuite) TestInternationalPPMEstimator() { + planner := &mocks.Planner{} + paymentRequestHelper := &prhelpermocks.Helper{} + ppmEstimator := NewEstimatePPM(planner, paymentRequestHelper) + + setupPricerData := func() { + contract := testdatagen.FetchOrMakeReContract(suite.DB(), testdatagen.Assertions{}) + startDate := time.Date(2020, time.January, 1, 12, 0, 0, 0, time.UTC) + endDate := time.Date(2020, time.December, 31, 12, 0, 0, 0, time.UTC) + testdatagen.FetchOrMakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + Contract: contract, + ContractID: contract.ID, + StartDate: startDate, + EndDate: endDate, + Escalation: 1.0, + EscalationCompounded: 1.0, + }, + }) + } + + suite.Run("Estimated Incentive", func() { + suite.Run("Estimated Incentive - Success using estimated weight and not db authorized weight for CONUS -> OCONUS", func() { + ppm := factory.BuildPPMShipment(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + MarketCode: models.MarketCodeInternational, + }, + }, + { + Model: models.Address{ + StreetAddress1: "Tester Address", + City: "Tulsa", + State: "OK", + PostalCode: "74133", + }, + Type: &factory.Addresses.PickupAddress, + }, + { + Model: models.Address{ + StreetAddress1: "JBER", + City: "JBER", + State: "AK", + PostalCode: "99505", + IsOconus: models.BoolPointer(true), + }, + Type: &factory.Addresses.DeliveryAddress, + }, + }, nil) + + setupPricerData() + + estimatedWeight := unit.Pound(5000) + newPPM := ppm + newPPM.EstimatedWeight = &estimatedWeight + + planner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), + "74133", "98421", true).Return(3000, nil) + + ppmEstimate, _, err := ppmEstimator.EstimateIncentiveWithDefaultChecks(suite.AppContextForTest(), ppm, &newPPM) + suite.NilOrNoVerrs(err) + suite.NotNil(ppmEstimate) + + // it should've called from the pickup -> port and NOT pickup -> dest + planner.AssertCalled(suite.T(), "ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), + "74133", "98421", true) + suite.Equal(unit.Cents(459178), *ppmEstimate) + }) + + suite.Run("Estimated Incentive - Success using estimated weight and not db authorized weight for OCONUS -> CONUS", func() { + ppm := factory.BuildPPMShipment(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + MarketCode: models.MarketCodeInternational, + }, + }, + { + Model: models.Address{ + StreetAddress1: "Tester Address", + City: "Tulsa", + State: "OK", + PostalCode: "74133", + }, + Type: &factory.Addresses.DeliveryAddress, + }, + { + Model: models.Address{ + StreetAddress1: "JBER", + City: "JBER", + State: "AK", + PostalCode: "99505", + IsOconus: models.BoolPointer(true), + }, + Type: &factory.Addresses.PickupAddress, + }, + }, nil) + + setupPricerData() + + estimatedWeight := unit.Pound(5000) + newPPM := ppm + newPPM.EstimatedWeight = &estimatedWeight + + planner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), + "98421", "74133", true).Return(3000, nil) + + ppmEstimate, _, err := ppmEstimator.EstimateIncentiveWithDefaultChecks(suite.AppContextForTest(), ppm, &newPPM) + suite.NilOrNoVerrs(err) + suite.NotNil(ppmEstimate) + + // it should've called from the pickup -> port and NOT pickup -> dest + planner.AssertCalled(suite.T(), "ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), + "98421", "74133", true) + suite.Equal(unit.Cents(423178), *ppmEstimate) + }) + }) + + suite.Run("Max Incentive", func() { + suite.Run("Max Incentive - Success using db authorized weight and not estimated for CONUS -> OCONUS", func() { + oconusAddress := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "JBER", + City: "JBER", + State: "AK", + PostalCode: "99505", + IsOconus: models.BoolPointer(true), + }, + }, + }, nil) + destDutyLocation := factory.BuildDutyLocation(suite.DB(), []factory.Customization{ + { + Model: models.DutyLocation{ + Name: "Test OCONUS Duty Location", + AddressID: oconusAddress.ID, + }, + }, + }, nil) + order := factory.BuildOrder(suite.DB(), []factory.Customization{ + { + Model: models.Order{ + NewDutyLocationID: destDutyLocation.ID, + }, + }, + }, nil) + // when the PPM shipment is in draft, we use the estimated weight and not the db authorized weight + ppm := factory.BuildPPMShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + OrdersID: order.ID, + }, + }, + { + Model: models.MTOShipment{ + MarketCode: models.MarketCodeInternational, + }, + }, + { + Model: models.Address{ + StreetAddress1: "Tester Address", + City: "Tulsa", + State: "OK", + PostalCode: "74133", + }, + Type: &factory.Addresses.PickupAddress, + }, + { + Model: models.Address{ + StreetAddress1: "JBER", + City: "JBER", + State: "AK", + PostalCode: "99505", + IsOconus: models.BoolPointer(true), + }, + Type: &factory.Addresses.DeliveryAddress, + }, + }, nil) + + setupPricerData() + + estimatedWeight := unit.Pound(5000) + newPPM := ppm + newPPM.EstimatedWeight = &estimatedWeight + + // DTOD will be called to get the distance between the origin duty location & the Tacoma Port ZIP + planner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), + "50309", "98421", true).Return(3000, nil) + + ppmMaxIncentive, err := ppmEstimator.MaxIncentive(suite.AppContextForTest(), ppm, &newPPM) + suite.NilOrNoVerrs(err) + suite.NotNil(ppmMaxIncentive) + + // it should've called from the pickup -> port and NOT pickup -> dest + planner.AssertCalled(suite.T(), "ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), + "50309", "98421", true) + suite.Equal(unit.Cents(656532), *ppmMaxIncentive) + }) + + suite.Run("Max Incentive - Success using db authorized weight and not estimated for OCONUS -> CONUS", func() { + oconusAddress := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "JBER", + City: "JBER", + State: "AK", + PostalCode: "99505", + IsOconus: models.BoolPointer(true), + }, + }, + }, nil) + pickupDutyLocation := factory.BuildDutyLocation(suite.DB(), []factory.Customization{ + { + Model: models.DutyLocation{ + Name: "Test OCONUS Duty Location", + AddressID: oconusAddress.ID, + }, + }, + }, nil) + order := factory.BuildOrder(suite.DB(), []factory.Customization{ + { + Model: models.Order{ + OriginDutyLocationID: &pickupDutyLocation.ID, + }, + }, + }, nil) + // when the PPM shipment is in draft, we use the estimated weight and not the db authorized weight + ppm := factory.BuildPPMShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + OrdersID: order.ID, + }, + }, + { + Model: models.MTOShipment{ + MarketCode: models.MarketCodeInternational, + }, + }, + { + Model: models.Address{ + StreetAddress1: "Tester Address", + City: "Tulsa", + State: "OK", + PostalCode: "74133", + }, + Type: &factory.Addresses.PickupAddress, + }, + { + Model: models.Address{ + StreetAddress1: "JBER", + City: "JBER", + State: "AK", + PostalCode: "99505", + IsOconus: models.BoolPointer(true), + }, + Type: &factory.Addresses.DeliveryAddress, + }, + }, nil) + + setupPricerData() + + estimatedWeight := unit.Pound(5000) + newPPM := ppm + newPPM.EstimatedWeight = &estimatedWeight + + // DTOD will be called to get the distance between the origin duty location & the Tacoma Port ZIP + planner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), + "98421", "30813", true).Return(3000, nil) + + ppmMaxIncentive, err := ppmEstimator.MaxIncentive(suite.AppContextForTest(), ppm, &newPPM) + suite.NilOrNoVerrs(err) + suite.NotNil(ppmMaxIncentive) + + // it should've called from the pickup -> port and NOT pickup -> dest + planner.AssertCalled(suite.T(), "ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), + "98421", "30813", true) + suite.Equal(unit.Cents(676692), *ppmMaxIncentive) + }) + }) + + suite.Run("Final Incentive", func() { + suite.Run("Final Incentive - Success using estimated weight for CONUS -> OCONUS", func() { + updatedMoveDate := time.Date(2020, time.March, 15, 0, 0, 0, 0, time.UTC) + ppm := factory.BuildPPMShipment(suite.DB(), []factory.Customization{ + { + Model: models.PPMShipment{ + ActualMoveDate: models.TimePointer(updatedMoveDate), + Status: models.PPMShipmentStatusWaitingOnCustomer, + EstimatedWeight: models.PoundPointer(4000), + }, + }, + { + Model: models.MTOShipment{ + MarketCode: models.MarketCodeInternational, + }, + }, + { + Model: models.Address{ + StreetAddress1: "Tester Address", + City: "Tulsa", + State: "OK", + PostalCode: "74133", + }, + Type: &factory.Addresses.PickupAddress, + }, + { + Model: models.Address{ + StreetAddress1: "JBER", + City: "JBER", + State: "AK", + PostalCode: "99505", + IsOconus: models.BoolPointer(true), + }, + Type: &factory.Addresses.DeliveryAddress, + }, + }, nil) + + newPPM := ppm + newFullWeight := unit.Pound(8000) + newEmptyWeight := unit.Pound(3000) + newPPM.WeightTickets = models.WeightTickets{ + factory.BuildWeightTicket(suite.DB(), []factory.Customization{ + { + Model: models.WeightTicket{ + FullWeight: &newFullWeight, + EmptyWeight: &newEmptyWeight, + }, + }, + }, nil), + } + + setupPricerData() + + planner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), + "74133", "98421", true).Return(3000, nil) + + ppmFinalIncentive, err := ppmEstimator.FinalIncentiveWithDefaultChecks(suite.AppContextForTest(), ppm, &newPPM) + suite.NilOrNoVerrs(err) + suite.NotNil(ppmFinalIncentive) + + // it should've called from the pickup -> port and NOT pickup -> dest + planner.AssertCalled(suite.T(), "ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), + "74133", "98421", true) + suite.Equal(unit.Cents(459178), *ppmFinalIncentive) + }) + + suite.Run("Final Incentive - Success using estimated weight for OCONUS -> CONUS", func() { + updatedMoveDate := time.Date(2020, time.March, 15, 0, 0, 0, 0, time.UTC) + ppm := factory.BuildPPMShipment(suite.DB(), []factory.Customization{ + { + Model: models.PPMShipment{ + ActualMoveDate: models.TimePointer(updatedMoveDate), + Status: models.PPMShipmentStatusWaitingOnCustomer, + EstimatedWeight: models.PoundPointer(4000), + }, + }, + { + Model: models.MTOShipment{ + MarketCode: models.MarketCodeInternational, + }, + }, + { + Model: models.Address{ + StreetAddress1: "Tester Address", + City: "Tulsa", + State: "OK", + PostalCode: "74133", + }, + Type: &factory.Addresses.DeliveryAddress, + }, + { + Model: models.Address{ + StreetAddress1: "JBER", + City: "JBER", + State: "AK", + PostalCode: "99505", + IsOconus: models.BoolPointer(true), + }, + Type: &factory.Addresses.PickupAddress, + }, + }, nil) + + newPPM := ppm + newFullWeight := unit.Pound(8000) + newEmptyWeight := unit.Pound(3000) + newPPM.WeightTickets = models.WeightTickets{ + factory.BuildWeightTicket(suite.DB(), []factory.Customization{ + { + Model: models.WeightTicket{ + FullWeight: &newFullWeight, + EmptyWeight: &newEmptyWeight, + }, + }, + }, nil), + } + + setupPricerData() + + planner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), + "98421", "74133", true).Return(3000, nil) + + ppmFinalIncentive, err := ppmEstimator.FinalIncentiveWithDefaultChecks(suite.AppContextForTest(), ppm, &newPPM) + suite.NilOrNoVerrs(err) + suite.NotNil(ppmFinalIncentive) + + // it should've called from the pickup -> port and NOT pickup -> dest + planner.AssertCalled(suite.T(), "ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), + "98421", "74133", true) + suite.Equal(unit.Cents(423178), *ppmFinalIncentive) + }) + }) + + suite.Run("SIT Costs for OCONUS PPMs", func() { + suite.Run("CalculateSITCost - Success using estimated weight for CONUS -> OCONUS", func() { + originLocation := models.SITLocationTypeOrigin + entryDate := time.Date(2020, time.March, 15, 0, 0, 0, 0, time.UTC) + ppm := factory.BuildPPMShipment(suite.DB(), []factory.Customization{ + { + Model: models.PPMShipment{ + EstimatedWeight: models.PoundPointer(4000), + SITExpected: models.BoolPointer(true), + SITLocation: &originLocation, + SITEstimatedWeight: models.PoundPointer(unit.Pound(2000)), + SITEstimatedEntryDate: &entryDate, + SITEstimatedDepartureDate: models.TimePointer(entryDate.Add(time.Hour * 24 * 30)), + }, + }, + { + Model: models.MTOShipment{ + MarketCode: models.MarketCodeInternational, + }, + }, + { + Model: models.Address{ + StreetAddress1: "Tester Address", + City: "Tulsa", + State: "OK", + PostalCode: "74133", + }, + Type: &factory.Addresses.PickupAddress, + }, + { + Model: models.Address{ + StreetAddress1: "JBER", + City: "JBER", + State: "AK", + PostalCode: "99505", + IsOconus: models.BoolPointer(true), + }, + Type: &factory.Addresses.DeliveryAddress, + }, + }, nil) + + newPPM := ppm + newEstimatedWeight := models.PoundPointer(5500) + newPPM.SITEstimatedWeight = newEstimatedWeight + setupPricerData() + + _, estimatedSITCost, err := ppmEstimator.EstimateIncentiveWithDefaultChecks(suite.AppContextForTest(), ppm, &newPPM) + suite.NilOrNoVerrs(err) + suite.NotNil(estimatedSITCost) + suite.Equal(unit.Cents(24360), *estimatedSITCost) + }) + + suite.Run("CalculateSITCost - Success using estimated weight for CONUS -> OCONUS", func() { + originLocation := models.SITLocationTypeDestination + entryDate := time.Date(2020, time.March, 15, 0, 0, 0, 0, time.UTC) + ppm := factory.BuildPPMShipment(suite.DB(), []factory.Customization{ + { + Model: models.PPMShipment{ + EstimatedWeight: models.PoundPointer(4000), + SITExpected: models.BoolPointer(true), + SITLocation: &originLocation, + SITEstimatedWeight: models.PoundPointer(unit.Pound(2000)), + SITEstimatedEntryDate: &entryDate, + SITEstimatedDepartureDate: models.TimePointer(entryDate.Add(time.Hour * 24 * 30)), + }, + }, + { + Model: models.MTOShipment{ + MarketCode: models.MarketCodeInternational, + }, + }, + { + Model: models.Address{ + StreetAddress1: "Tester Address", + City: "Tulsa", + State: "OK", + PostalCode: "74133", + }, + Type: &factory.Addresses.PickupAddress, + }, + { + Model: models.Address{ + StreetAddress1: "JBER", + City: "JBER", + State: "AK", + PostalCode: "99505", + IsOconus: models.BoolPointer(true), + }, + Type: &factory.Addresses.DeliveryAddress, + }, + }, nil) + + newPPM := ppm + newEstimatedWeight := models.PoundPointer(5500) + newPPM.SITEstimatedWeight = newEstimatedWeight + setupPricerData() + + _, estimatedSITCost, err := ppmEstimator.EstimateIncentiveWithDefaultChecks(suite.AppContextForTest(), ppm, &newPPM) + suite.NilOrNoVerrs(err) + suite.NotNil(estimatedSITCost) + suite.Equal(unit.Cents(41080), *estimatedSITCost) + }) + + suite.Run("CalculatePPMSITEstimatedCost - Success for OCONUS PPM", func() { + originLocation := models.SITLocationTypeDestination + entryDate := time.Date(2020, time.March, 15, 0, 0, 0, 0, time.UTC) + ppm := factory.BuildPPMShipment(suite.DB(), []factory.Customization{ + { + Model: models.PPMShipment{ + EstimatedWeight: models.PoundPointer(4000), + SITExpected: models.BoolPointer(true), + SITLocation: &originLocation, + SITEstimatedWeight: models.PoundPointer(unit.Pound(2000)), + SITEstimatedEntryDate: &entryDate, + SITEstimatedDepartureDate: models.TimePointer(entryDate.Add(time.Hour * 24 * 30)), + }, + }, + { + Model: models.MTOShipment{ + MarketCode: models.MarketCodeInternational, + }, + }, + { + Model: models.Address{ + StreetAddress1: "Tester Address", + City: "Tulsa", + State: "OK", + PostalCode: "74133", + }, + Type: &factory.Addresses.PickupAddress, + }, + { + Model: models.Address{ + StreetAddress1: "JBER", + City: "JBER", + State: "AK", + PostalCode: "99505", + IsOconus: models.BoolPointer(true), + }, + Type: &factory.Addresses.DeliveryAddress, + }, + }, nil) + + newPPM := ppm + newEstimatedWeight := models.PoundPointer(5500) + newPPM.SITEstimatedWeight = newEstimatedWeight + setupPricerData() + + estimatedSITCost, err := ppmEstimator.CalculatePPMSITEstimatedCost(suite.AppContextForTest(), &ppm) + suite.NilOrNoVerrs(err) + suite.NotNil(estimatedSITCost) + suite.Equal(unit.Cents(20540), *estimatedSITCost) + }) + + suite.Run("CalculatePPMSITEstimatedCostBreakdown - Success for OCONUS PPM", func() { + originLocation := models.SITLocationTypeDestination + entryDate := time.Date(2020, time.March, 15, 0, 0, 0, 0, time.UTC) + ppm := factory.BuildPPMShipment(suite.DB(), []factory.Customization{ + { + Model: models.PPMShipment{ + EstimatedWeight: models.PoundPointer(4000), + SITExpected: models.BoolPointer(true), + SITLocation: &originLocation, + SITEstimatedWeight: models.PoundPointer(unit.Pound(2000)), + SITEstimatedEntryDate: &entryDate, + SITEstimatedDepartureDate: models.TimePointer(entryDate.Add(time.Hour * 24 * 30)), + }, + }, + { + Model: models.MTOShipment{ + MarketCode: models.MarketCodeInternational, + }, + }, + { + Model: models.Address{ + StreetAddress1: "Tester Address", + City: "Tulsa", + State: "OK", + PostalCode: "74133", + }, + Type: &factory.Addresses.PickupAddress, + }, + { + Model: models.Address{ + StreetAddress1: "JBER", + City: "JBER", + State: "AK", + PostalCode: "99505", + IsOconus: models.BoolPointer(true), + }, + Type: &factory.Addresses.DeliveryAddress, + }, + }, nil) + + newPPM := ppm + newEstimatedWeight := models.PoundPointer(5500) + newPPM.SITEstimatedWeight = newEstimatedWeight + setupPricerData() + + sitCosts, err := ppmEstimator.CalculatePPMSITEstimatedCostBreakdown(suite.AppContextForTest(), &ppm) + suite.NilOrNoVerrs(err) + suite.NotNil(sitCosts) + suite.Equal(unit.Cents(20540), *sitCosts.EstimatedSITCost) + suite.Equal(unit.Cents(12140), *sitCosts.PriceFirstDaySIT) + suite.Equal(unit.Cents(8400), *sitCosts.PriceAdditionalDaySIT) + }) + }) +} diff --git a/pkg/services/ppmshipment/ppm_shipment_updater.go b/pkg/services/ppmshipment/ppm_shipment_updater.go index f8cc99b8d30..c52936e9e9f 100644 --- a/pkg/services/ppmshipment/ppm_shipment_updater.go +++ b/pkg/services/ppmshipment/ppm_shipment_updater.go @@ -117,66 +117,6 @@ func (f *ppmShipmentUpdater) updatePPMShipment(appCtx appcontext.AppContext, ppm } transactionError := appCtx.NewTransaction(func(txnAppCtx appcontext.AppContext) error { - // This potentially updates the MTOShipment.Distance field so include it in the transaction - estimatedIncentive, estimatedSITCost, err := f.estimator.EstimateIncentiveWithDefaultChecks(appCtx, *oldPPMShipment, updatedPPMShipment) - if err != nil { - return err - } - - updatedPPMShipment.EstimatedIncentive = estimatedIncentive - updatedPPMShipment.SITEstimatedCost = estimatedSITCost - - // if the PPM shipment is past closeout then we should not calculate the max incentive, it is already set in stone - if oldPPMShipment.Status != models.PPMShipmentStatusWaitingOnCustomer && - oldPPMShipment.Status != models.PPMShipmentStatusCloseoutComplete && - oldPPMShipment.Status != models.PPMShipmentStatusComplete && - oldPPMShipment.Status != models.PPMShipmentStatusNeedsCloseout { - maxIncentive, err := f.estimator.MaxIncentive(appCtx, *oldPPMShipment, updatedPPMShipment) - if err != nil { - return err - } - updatedPPMShipment.MaxIncentive = maxIncentive - } - - if appCtx.Session() != nil { - if appCtx.Session().IsOfficeUser() { - edited := models.PPMAdvanceStatusEdited - if oldPPMShipment.HasRequestedAdvance != nil && updatedPPMShipment.HasRequestedAdvance != nil { - if !*oldPPMShipment.HasRequestedAdvance && *updatedPPMShipment.HasRequestedAdvance { - updatedPPMShipment.AdvanceStatus = &edited - } else if *oldPPMShipment.HasRequestedAdvance && !*updatedPPMShipment.HasRequestedAdvance { - updatedPPMShipment.AdvanceStatus = &edited - } - } - if oldPPMShipment.AdvanceAmountRequested != nil && updatedPPMShipment.AdvanceAmountRequested != nil { - if *oldPPMShipment.AdvanceAmountRequested != *updatedPPMShipment.AdvanceAmountRequested { - updatedPPMShipment.AdvanceStatus = &edited - } - } - } - if appCtx.Session().IsMilApp() { - if isPrimeCounseled && updatedPPMShipment.HasRequestedAdvance != nil { - received := models.PPMAdvanceStatusReceived - notReceived := models.PPMAdvanceStatusNotReceived - - if updatedPPMShipment.HasReceivedAdvance != nil && *updatedPPMShipment.HasRequestedAdvance { - if *updatedPPMShipment.HasReceivedAdvance { - updatedPPMShipment.AdvanceStatus = &received - } - if !*updatedPPMShipment.HasReceivedAdvance { - updatedPPMShipment.AdvanceStatus = ¬Received - } - } - } - } - } - - finalIncentive, err := f.estimator.FinalIncentiveWithDefaultChecks(appCtx, *oldPPMShipment, updatedPPMShipment) - if err != nil { - return err - } - updatedPPMShipment.FinalIncentive = finalIncentive - if updatedPPMShipment.W2Address != nil { var updatedAddress *models.Address var createOrUpdateErr error @@ -282,6 +222,69 @@ func (f *ppmShipmentUpdater) updatePPMShipment(appCtx appcontext.AppContext, ppm updatedPPMShipment.TertiaryDestinationAddress = updatedAddress } + // if the actual move date is being provided, we no longer need to calculate the estimate - it has already happened + if updatedPPMShipment.ActualMoveDate == nil { + estimatedIncentive, estimatedSITCost, err := f.estimator.EstimateIncentiveWithDefaultChecks(appCtx, *oldPPMShipment, updatedPPMShipment) + if err != nil { + return err + } + updatedPPMShipment.EstimatedIncentive = estimatedIncentive + updatedPPMShipment.SITEstimatedCost = estimatedSITCost + } + + // if the PPM shipment is past closeout then we should not calculate the max incentive, it is already set in stone + if oldPPMShipment.Status != models.PPMShipmentStatusWaitingOnCustomer && + oldPPMShipment.Status != models.PPMShipmentStatusCloseoutComplete && + oldPPMShipment.Status != models.PPMShipmentStatusComplete && + oldPPMShipment.Status != models.PPMShipmentStatusNeedsCloseout { + maxIncentive, err := f.estimator.MaxIncentive(appCtx, *oldPPMShipment, updatedPPMShipment) + if err != nil { + return err + } + updatedPPMShipment.MaxIncentive = maxIncentive + } + + if appCtx.Session() != nil { + if appCtx.Session().IsOfficeUser() { + edited := models.PPMAdvanceStatusEdited + if oldPPMShipment.HasRequestedAdvance != nil && updatedPPMShipment.HasRequestedAdvance != nil { + if !*oldPPMShipment.HasRequestedAdvance && *updatedPPMShipment.HasRequestedAdvance { + updatedPPMShipment.AdvanceStatus = &edited + } else if *oldPPMShipment.HasRequestedAdvance && !*updatedPPMShipment.HasRequestedAdvance { + updatedPPMShipment.AdvanceStatus = &edited + } + } + if oldPPMShipment.AdvanceAmountRequested != nil && updatedPPMShipment.AdvanceAmountRequested != nil { + if *oldPPMShipment.AdvanceAmountRequested != *updatedPPMShipment.AdvanceAmountRequested { + updatedPPMShipment.AdvanceStatus = &edited + } + } + } + if appCtx.Session().IsMilApp() { + if isPrimeCounseled && updatedPPMShipment.HasRequestedAdvance != nil { + received := models.PPMAdvanceStatusReceived + notReceived := models.PPMAdvanceStatusNotReceived + + if updatedPPMShipment.HasReceivedAdvance != nil && *updatedPPMShipment.HasRequestedAdvance { + if *updatedPPMShipment.HasReceivedAdvance { + updatedPPMShipment.AdvanceStatus = &received + } + if !*updatedPPMShipment.HasReceivedAdvance { + updatedPPMShipment.AdvanceStatus = ¬Received + } + } + } + } + } + + if updatedPPMShipment.ActualMoveDate != nil { + finalIncentive, err := f.estimator.FinalIncentiveWithDefaultChecks(appCtx, *oldPPMShipment, updatedPPMShipment) + if err != nil { + return err + } + updatedPPMShipment.FinalIncentive = finalIncentive + } + verrs, err := appCtx.DB().ValidateAndUpdate(updatedPPMShipment) if verrs != nil && verrs.HasAny() { return apperror.NewInvalidInputError(updatedPPMShipment.ID, err, verrs, "Invalid input found while updating the PPMShipments.") diff --git a/pkg/services/ppmshipment/ppm_shipment_updater_test.go b/pkg/services/ppmshipment/ppm_shipment_updater_test.go index 607491af64c..3ee017878b4 100644 --- a/pkg/services/ppmshipment/ppm_shipment_updater_test.go +++ b/pkg/services/ppmshipment/ppm_shipment_updater_test.go @@ -1504,7 +1504,7 @@ func (suite *PPMShipmentSuite) TestUpdatePPMShipment() { originalPPM.DestinationAddress = destinationAddress mockedPlanner := &routemocks.Planner{} mockedPlanner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), - "90210", "30813", false, false).Return(2294, nil) + "90210", "30813", false).Return(2294, nil) updatedPPM, err := subtestData.ppmShipmentUpdater.UpdatePPMShipmentSITEstimatedCost(appCtx, &originalPPM) @@ -1560,7 +1560,7 @@ func (suite *PPMShipmentSuite) TestUpdatePPMShipment() { originalPPM.DestinationAddress = destinationAddress mockedPlanner := &routemocks.Planner{} mockedPlanner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), - "90210", "30813", false, false).Return(2294, nil) + "90210", "30813", false).Return(2294, nil) updatedPPM, err := subtestData.ppmShipmentUpdater.UpdatePPMShipmentSITEstimatedCost(appCtx, &originalPPM) diff --git a/pkg/services/query/query_builder.go b/pkg/services/query/query_builder.go index ca88a1fd656..01d9fd424e3 100644 --- a/pkg/services/query/query_builder.go +++ b/pkg/services/query/query_builder.go @@ -447,6 +447,35 @@ func (p *Builder) DeleteOne(appCtx appcontext.AppContext, model interface{}) err return nil } +func (p *Builder) DeleteMany(appCtx appcontext.AppContext, model interface{}, filters []services.QueryFilter) error { + t := reflect.TypeOf(model) + if t.Kind() != reflect.Ptr { + return errors.New("DeleteMany: model must be a pointer to a slice") + } + t = t.Elem() + if t.Kind() != reflect.Slice { + return errors.New("DeleteMany: model must be a pointer to a slice") + } + t = t.Elem() + if t.Kind() != reflect.Struct { + return errors.New("DeleteMany: model must be a slice of structs") + } + + query := appCtx.DB().Q() + + query, err := filteredQuery(query, filters, t) + if err != nil { + return err + } + + err = query.Delete(model) + if err != nil { + return err + } + + return nil +} + // FetchCategoricalCountsFromOneModel returns categorical counts from exactly one model func (p *Builder) FetchCategoricalCountsFromOneModel(appCtx appcontext.AppContext, model interface{}, filters []services.QueryFilter, andFilters *[]services.QueryFilter) (map[interface{}]int, error) { t := reflect.TypeOf(model) diff --git a/pkg/services/query/query_builder_test.go b/pkg/services/query/query_builder_test.go index 5ffe8fbbb12..af80a6e1477 100644 --- a/pkg/services/query/query_builder_test.go +++ b/pkg/services/query/query_builder_test.go @@ -796,3 +796,56 @@ func (suite *QueryBuilderSuite) TestDeleteOne() { suite.Error(err, "Model should be a pointer to a struct") }) } + +func (suite *QueryBuilderSuite) TestDeleteMany() { + builder := NewQueryBuilder() + + suite.Run("successfully deletes multiple records with uuid filter", func() { + user1 := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) + user2 := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) + + filters := []services.QueryFilter{ + NewQueryFilter("id", equals, user1.ID.String()), + } + + err := builder.DeleteMany(suite.AppContextForTest(), &[]models.OfficeUser{}, filters) + suite.NoError(err) + + // make sure the user is deleted + var remainingUsers models.OfficeUsers + err = suite.DB().All(&remainingUsers) + suite.NoError(err) + suite.Len(remainingUsers, 1) + suite.Equal(user2.ID, remainingUsers[0].ID) + }) + + suite.Run("rejects input that is not a pointer to a slice", func() { + err := builder.DeleteMany(suite.AppContextForTest(), models.OfficeUser{}, nil) + suite.Error(err) + suite.Equal("DeleteMany: model must be a pointer to a slice", err.Error()) + + err = builder.DeleteMany(suite.AppContextForTest(), []models.OfficeUser{}, nil) + suite.Error(err) + suite.Equal("DeleteMany: model must be a pointer to a slice", err.Error()) + }) + + suite.Run("fails with invalid column filter", func() { + user := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) + + filters := []services.QueryFilter{ + NewQueryFilter("invalid_column", equals, user.ID.String()), + } + + err := builder.DeleteMany(suite.AppContextForTest(), &[]models.OfficeUser{}, filters) + suite.Error(err) + suite.Contains(err.Error(), "invalid_column") + }) + + suite.Run("fails when not a pointer to slice of structs", func() { + var intSlice []int + + err := builder.DeleteMany(suite.AppContextForTest(), &intSlice, []services.QueryFilter{}) + suite.Error(err) + suite.Equal("DeleteMany: model must be a slice of structs", err.Error()) + }) +} diff --git a/pkg/services/requested_office_users.go b/pkg/services/requested_office_users.go index 7ce3228300d..574d5915c83 100644 --- a/pkg/services/requested_office_users.go +++ b/pkg/services/requested_office_users.go @@ -24,13 +24,6 @@ type RequestedOfficeUserFetcher interface { FetchRequestedOfficeUser(appCtx appcontext.AppContext, filters []QueryFilter) (models.OfficeUser, error) } -// RequestedOfficeUserFetcherPop is the exported interface for fetching a single office user -// -//go:generate mockery --name RequestedOfficeUserFetcherPop -type RequestedOfficeUserFetcherPop interface { - FetchRequestedOfficeUserByID(appCtx appcontext.AppContext, id uuid.UUID) (models.OfficeUser, error) -} - // RequestedOfficeUserFetcher is the exported interface for updating a requested office user // //go:generate mockery --name RequestedOfficeUserUpdater diff --git a/pkg/services/requested_office_users/requested_office_user_fetcher.go b/pkg/services/requested_office_users/requested_office_user_fetcher.go index 998a5db1857..714a7ad4604 100644 --- a/pkg/services/requested_office_users/requested_office_user_fetcher.go +++ b/pkg/services/requested_office_users/requested_office_user_fetcher.go @@ -1,13 +1,9 @@ package adminuser import ( - "database/sql" - "github.com/gobuffalo/validate/v3" - "github.com/gofrs/uuid" "github.com/transcom/mymove/pkg/appcontext" - "github.com/transcom/mymove/pkg/apperror" "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/services" ) @@ -15,6 +11,8 @@ import ( type requestedOfficeUserQueryBuilder interface { FetchOne(appCtx appcontext.AppContext, model interface{}, filters []services.QueryFilter) error UpdateOne(appCtx appcontext.AppContext, model interface{}, eTag *string) (*validate.Errors, error) + DeleteOne(appCtx appcontext.AppContext, model interface{}) error + DeleteMany(appCtx appcontext.AppContext, model interface{}, filters []services.QueryFilter) error } type requestedOfficeUserFetcher struct { @@ -32,27 +30,3 @@ func (o *requestedOfficeUserFetcher) FetchRequestedOfficeUser(appCtx appcontext. func NewRequestedOfficeUserFetcher(builder requestedOfficeUserQueryBuilder) services.RequestedOfficeUserFetcher { return &requestedOfficeUserFetcher{builder} } - -type requestedOfficeUserFetcherPop struct { -} - -// FetchOfficeUserByID fetches an office user given an ID -func (o *requestedOfficeUserFetcherPop) FetchRequestedOfficeUserByID(appCtx appcontext.AppContext, id uuid.UUID) (models.OfficeUser, error) { - var officeUser models.OfficeUser - err := appCtx.DB().Eager("TransportationOffice").Find(&officeUser, id) - if err != nil { - switch err { - case sql.ErrNoRows: - return models.OfficeUser{}, apperror.NewNotFoundError(id, "looking for OfficeUser") - default: - return models.OfficeUser{}, apperror.NewQueryError("OfficeUser", err, "") - } - } - - return officeUser, err -} - -// NewOfficeUserFetcherPop return an implementation of the OfficeUserFetcherPop interface -func NewRequestedOfficeUserFetcherPop() services.RequestedOfficeUserFetcherPop { - return &requestedOfficeUserFetcherPop{} -} diff --git a/pkg/services/requested_office_users/requested_office_user_fetcher_test.go b/pkg/services/requested_office_users/requested_office_user_fetcher_test.go index 7008b9409b5..0860718b1ca 100644 --- a/pkg/services/requested_office_users/requested_office_user_fetcher_test.go +++ b/pkg/services/requested_office_users/requested_office_user_fetcher_test.go @@ -8,10 +8,7 @@ import ( "github.com/gofrs/uuid" "github.com/transcom/mymove/pkg/appcontext" - "github.com/transcom/mymove/pkg/apperror" - "github.com/transcom/mymove/pkg/factory" "github.com/transcom/mymove/pkg/models" - "github.com/transcom/mymove/pkg/models/roles" "github.com/transcom/mymove/pkg/services" "github.com/transcom/mymove/pkg/services/query" ) @@ -29,6 +26,14 @@ func (t *testRequestedOfficeUsersQueryBuilder) UpdateOne(_ appcontext.AppContext return nil, nil } +func (t *testRequestedOfficeUsersQueryBuilder) DeleteOne(_ appcontext.AppContext, _ interface{}) error { + return nil +} + +func (t *testRequestedOfficeUsersQueryBuilder) DeleteMany(_ appcontext.AppContext, _ interface{}, _ []services.QueryFilter) error { + return nil +} + func (suite *RequestedOfficeUsersServiceSuite) TestFetchRequestedOfficeUser() { suite.Run("if the requested office user is fetched, it should be returned", func() { id, err := uuid.NewV4() @@ -67,24 +72,3 @@ func (suite *RequestedOfficeUsersServiceSuite) TestFetchRequestedOfficeUser() { suite.Equal(models.OfficeUser{}, requestedOfficeUser) }) } - -func (suite *RequestedOfficeUsersServiceSuite) TestFetchRequestedOfficeUserPop() { - suite.Run("returns office user on success", func() { - officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) - fetcher := NewRequestedOfficeUserFetcherPop() - - fetchedUser, err := fetcher.FetchRequestedOfficeUserByID(suite.AppContextForTest(), officeUser.ID) - - suite.NoError(err) - suite.Equal(officeUser.ID, fetchedUser.ID) - }) - - suite.Run("returns zero value office user on error", func() { - fetcher := NewRequestedOfficeUserFetcherPop() - officeUser, err := fetcher.FetchRequestedOfficeUserByID(suite.AppContextForTest(), uuid.Nil) - - suite.Error(err) - suite.IsType(apperror.NotFoundError{}, err) - suite.Equal(uuid.Nil, officeUser.ID) - }) -} diff --git a/pkg/services/requested_office_users/requested_office_user_service_test.go b/pkg/services/requested_office_users/requested_office_user_service_test.go index 8512b3ba57c..391b82451a4 100644 --- a/pkg/services/requested_office_users/requested_office_user_service_test.go +++ b/pkg/services/requested_office_users/requested_office_user_service_test.go @@ -12,10 +12,10 @@ type RequestedOfficeUsersServiceSuite struct { *testingsuite.PopTestSuite } -func TestUserSuite(t *testing.T) { +func TestRequestedOfficeUserSuite(t *testing.T) { ts := &RequestedOfficeUsersServiceSuite{ - PopTestSuite: testingsuite.NewPopTestSuite(testingsuite.CurrentPackage(), testingsuite.WithPerTestTransaction()), + testingsuite.NewPopTestSuite(testingsuite.CurrentPackage(), testingsuite.WithPerTestTransaction()), } suite.Run(t, ts) ts.PopTestSuite.TearDown() diff --git a/pkg/services/reweigh/reweigh_updater_test.go b/pkg/services/reweigh/reweigh_updater_test.go index 77805ecf4ac..c1501906a98 100644 --- a/pkg/services/reweigh/reweigh_updater_test.go +++ b/pkg/services/reweigh/reweigh_updater_test.go @@ -32,7 +32,6 @@ func (suite *ReweighSuite) TestReweighUpdater() { recalculateTestPickupZip, recalculateTestDestinationZip, false, - false, ).Return(recalculateTestZip3Distance, nil) // Get shipment payment request recalculator service 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 1a80f27538c..0cb8ba3c123 100644 --- a/pkg/services/shipment_address_update/shipment_address_update_requester.go +++ b/pkg/services/shipment_address_update/shipment_address_update_requester.go @@ -41,7 +41,7 @@ func NewShipmentAddressUpdateRequester(planner route.Planner, addressCreator ser func (f *shipmentAddressUpdateRequester) isAddressChangeDistanceOver50(appCtx appcontext.AppContext, addressUpdate models.ShipmentAddressUpdate, isInternationalShipment bool) (bool, error) { // We calculate and set the distance between the old and new address - distance, err := f.planner.ZipTransitDistance(appCtx, addressUpdate.OriginalAddress.PostalCode, addressUpdate.NewAddress.PostalCode, false, isInternationalShipment) + distance, err := f.planner.ZipTransitDistance(appCtx, addressUpdate.OriginalAddress.PostalCode, addressUpdate.NewAddress.PostalCode, isInternationalShipment) if err != nil { return false, err } @@ -122,11 +122,11 @@ func (f *shipmentAddressUpdateRequester) doesDeliveryAddressUpdateChangeMileageB return false, nil } - previousDistance, err := f.planner.ZipTransitDistance(appCtx, originalPickupAddress.PostalCode, originalDeliveryAddress.PostalCode, false, false) + previousDistance, err := f.planner.ZipTransitDistance(appCtx, originalPickupAddress.PostalCode, originalDeliveryAddress.PostalCode, false) if err != nil { return false, err } - newDistance, err := f.planner.ZipTransitDistance(appCtx, originalPickupAddress.PostalCode, newDeliveryAddress.PostalCode, false, false) + newDistance, err := f.planner.ZipTransitDistance(appCtx, originalPickupAddress.PostalCode, newDeliveryAddress.PostalCode, false) if err != nil { return false, err } @@ -339,14 +339,14 @@ func (f *shipmentAddressUpdateRequester) RequestShipmentDeliveryAddressUpdate(ap if addressUpdate.NewSitDistanceBetween != nil { distanceBetweenOld = *addressUpdate.NewSitDistanceBetween } else { - distanceBetweenOld, err = f.planner.ZipTransitDistance(appCtx, addressUpdate.SitOriginalAddress.PostalCode, addressUpdate.OriginalAddress.PostalCode, false, false) + distanceBetweenOld, err = f.planner.ZipTransitDistance(appCtx, addressUpdate.SitOriginalAddress.PostalCode, addressUpdate.OriginalAddress.PostalCode, false) } if err != nil { return nil, err } // calculating distance between the new address update & the SIT - distanceBetweenNew, err = f.planner.ZipTransitDistance(appCtx, addressUpdate.SitOriginalAddress.PostalCode, addressUpdate.NewAddress.PostalCode, false, false) + distanceBetweenNew, err = f.planner.ZipTransitDistance(appCtx, addressUpdate.SitOriginalAddress.PostalCode, addressUpdate.NewAddress.PostalCode, false) if err != nil { return nil, err } @@ -499,7 +499,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 +523,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 +531,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 +573,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 @@ -659,7 +684,7 @@ func (f *shipmentAddressUpdateRequester) ReviewShipmentAddressChange(appCtx appc destZip = shipment.DestinationAddress.PostalCode } // we need to get the mileage from DTOD first, the db proc will consume that - mileage, err := f.planner.ZipTransitDistance(appCtx, pickupZip, destZip, true, true) + mileage, err := f.planner.ZipTransitDistance(appCtx, pickupZip, destZip, true) if err != nil { return err } 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 b7579c61d16..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{ @@ -72,14 +108,12 @@ func (suite *ShipmentAddressUpdateServiceSuite) TestCreateApprovedShipmentAddres "90210", "94535", false, - false, ).Return(2500, nil).Twice() mockPlanner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), "94535", "94535", false, - false, ).Return(2500, nil).Once() move := setupTestData() shipment := factory.BuildMTOShipmentWithMove(&move, suite.DB(), nil, nil) @@ -114,14 +148,12 @@ func (suite *ShipmentAddressUpdateServiceSuite) TestCreateApprovedShipmentAddres "90210", "94535", false, - false, ).Return(2500, nil).Twice() mockPlanner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), "94535", "94535", false, - false, ).Return(2500, nil).Once() move := setupTestData() @@ -184,7 +216,6 @@ func (suite *ShipmentAddressUpdateServiceSuite) TestCreateApprovedShipmentAddres mock.AnythingOfType("*appcontext.appContext"), "99505", "99506", - false, true, ).Return(49, nil) move := setupTestData() @@ -267,7 +298,6 @@ func (suite *ShipmentAddressUpdateServiceSuite) TestCreateApprovedShipmentAddres mock.AnythingOfType("string"), mock.AnythingOfType("string"), false, - false, ).Return(0, fmt.Errorf("error calculating distance 2")).Once() testdatagen.FetchOrMakeReContractYear(suite.DB(), testdatagen.Assertions{ @@ -408,14 +438,12 @@ func (suite *ShipmentAddressUpdateServiceSuite) TestCreateApprovedShipmentAddres "90210", "94535", false, - false, ).Return(2500, nil).Times(4) mockPlanner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), "94535", "94535", false, - false, ).Return(2500, nil).Twice() update, err := addressUpdateRequester.RequestShipmentDeliveryAddressUpdate(suite.AppContextForTest(), shipment.ID, newAddress, "we really need to change the address", etag.GenerateEtag(shipment.UpdatedAt)) @@ -442,14 +470,12 @@ func (suite *ShipmentAddressUpdateServiceSuite) TestCreateApprovedShipmentAddres "89523", "89503", false, - false, ).Return(2500, nil).Once() mockPlanner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), "89523", "90210", false, - false, ).Return(2500, nil).Once() newAddress := models.Address{ StreetAddress1: "123 Any St", @@ -491,14 +517,12 @@ func (suite *ShipmentAddressUpdateServiceSuite) TestCreateApprovedShipmentAddres "89523", "89503", false, - false, ).Return(2500, nil).Once() mockPlanner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), "89523", "90210", false, - false, ).Return(2500, nil).Once() move := setupTestData() shipment := factory.BuildMTOShipmentWithMove(&move, suite.DB(), []factory.Customization{ @@ -540,14 +564,12 @@ func (suite *ShipmentAddressUpdateServiceSuite) TestCreateApprovedShipmentAddres "90210", "94535", false, - false, ).Return(0, nil).Once() mockPlanner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), "90210", "89503", false, - false, ).Return(200, nil).Once() testdatagen.MakeReContractYear(suite.DB(), testdatagen.Assertions{ ReContractYear: models.ReContractYear{ @@ -671,14 +693,12 @@ func (suite *ShipmentAddressUpdateServiceSuite) TestCreateApprovedShipmentAddres mock.AnythingOfType("string"), "87108", false, - false, ).Return(500, nil).Once() mockPlanner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), mock.AnythingOfType("string"), "87053", false, - false, ).Return(501, nil).Once() suite.NotEmpty(move.MTOShipments) update, err := addressUpdateRequester.RequestShipmentDeliveryAddressUpdate(suite.AppContextForTest(), shipment.ID, newAddress, "we really need to change the address", etag.GenerateEtag(shipment.UpdatedAt)) @@ -733,21 +753,18 @@ func (suite *ShipmentAddressUpdateServiceSuite) TestCreateApprovedShipmentAddres "94535", "94535", false, - false, ).Return(0, nil).Once() mockPlanner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), "94523", "90210", false, - false, ).Return(500, nil).Once() mockPlanner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), "94535", "90210", false, - false, ).Return(501, nil).Once() // request the update @@ -774,12 +791,13 @@ func (suite *ShipmentAddressUpdateServiceSuite) TestTOOApprovedShipmentAddressUp mock.Anything, mock.Anything, false, - false, ).Return(400, nil) addressUpdateRequester := NewShipmentAddressUpdateRequester(mockPlanner, addressCreator, moveRouter) suite.Run("TOO approves address change", func() { + suite.setupServiceItemData() + addressChange := factory.BuildShipmentAddressUpdate(suite.DB(), nil, []factory.Trait{ factory.GetTraitAvailableToPrimeMove, }) @@ -1150,7 +1168,6 @@ func (suite *ShipmentAddressUpdateServiceSuite) TestTOOApprovedShipmentAddressUp mock.Anything, mock.Anything, false, - false, ).Return(300, nil) mockPlanner.On("ZipTransitDistance", @@ -1158,7 +1175,6 @@ func (suite *ShipmentAddressUpdateServiceSuite) TestTOOApprovedShipmentAddressUp mock.Anything, mock.Anything, true, - true, ).Return(300, nil) newDestUSPRC, err := models.FindByZipCode(suite.AppContextForTest().DB(), "99703") @@ -1370,7 +1386,6 @@ func (suite *ShipmentAddressUpdateServiceSuite) TestTOOApprovedShipmentAddressUp mock.Anything, mock.Anything, false, - false, ).Return(300, nil) newDestUSPRC, err := models.FindByZipCode(suite.AppContextForTest().DB(), "99703") @@ -1475,16 +1490,17 @@ func (suite *ShipmentAddressUpdateServiceSuite) TestTOOApprovedShipmentAddressUp "89523", "89503", false, - false, ).Return(2500, nil).Once() mockPlanner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), "89523", "90210", false, - false, ).Return(2500, nil).Once() move := setupTestData() + + suite.setupServiceItemData() + shipment := factory.BuildMTOShipmentWithMove(&move, suite.DB(), []factory.Customization{ { Model: models.Address{ @@ -1545,16 +1561,17 @@ func (suite *ShipmentAddressUpdateServiceSuite) TestTOOApprovedShipmentAddressUp "89523", "89503", false, - false, ).Return(2500, nil).Once() mockPlanner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), "89523", "90210", false, - false, ).Return(2500, nil).Once() move := setupTestData() + + suite.setupServiceItemData() + shipment := factory.BuildMTOShipmentWithMove(&move, suite.DB(), []factory.Customization{ { Model: models.Address{ @@ -1610,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 @@ -1627,14 +1647,12 @@ func (suite *ShipmentAddressUpdateServiceSuite) TestTOOApprovedShipmentAddressUp "90210", "94535", false, - false, ).Return(2500, nil).Once() mockPlanner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), "94535", "94535", false, - false, ).Return(2500, nil).Once() addressChange, _ := addressUpdateRequester.RequestShipmentDeliveryAddressUpdate(suite.AppContextForTest(), shipment.ID, newAddress, "we really need to change the address", etag.GenerateEtag(shipment.UpdatedAt)) @@ -1657,16 +1675,17 @@ func (suite *ShipmentAddressUpdateServiceSuite) TestTOOApprovedShipmentAddressUp "89523", "89503", false, - false, ).Return(2500, nil).Once() mockPlanner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), "89523", "90210", false, - false, ).Return(2500, nil).Once() move := setupTestData() + + suite.setupServiceItemData() + shipment := factory.BuildMTOShipmentWithMove(&move, suite.DB(), []factory.Customization{ { Model: models.Address{ @@ -1722,14 +1741,12 @@ func (suite *ShipmentAddressUpdateServiceSuite) TestTOOApprovedShipmentAddressUp "89523", "89503", false, - false, ).Return(2500, nil).Once() mockPlanner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), "89523", "90210", false, - false, ).Return(2500, nil).Once() newAddress := models.Address{ StreetAddress1: "123 Any St", @@ -1738,6 +1755,9 @@ func (suite *ShipmentAddressUpdateServiceSuite) TestTOOApprovedShipmentAddressUp PostalCode: "90210", } move := setupTestData() + + suite.setupServiceItemData() + shipment := factory.BuildMTOShipmentWithMove(&move, suite.DB(), []factory.Customization{ { Model: models.Address{ @@ -1786,7 +1806,6 @@ func (suite *ShipmentAddressUpdateServiceSuite) TestTOOApprovedShipmentAddressUp "94535", "94535", false, - false, ).Return(30, nil) move := setupTestData() shipment := factory.BuildMTOShipmentWithMove(&move, suite.DB(), nil, nil) diff --git a/pkg/services/sit_extension/sit_extension_denier.go b/pkg/services/sit_extension/sit_extension_denier.go index cc8fc66d2a8..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" @@ -34,9 +35,8 @@ func NewSITExtensionDenier(moveRouter services.MoveRouter) services.SITExtension mock.Anything, mock.Anything, false, - 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/support/move_task_order/move_task_order_creator.go b/pkg/services/support/move_task_order/move_task_order_creator.go index 85f3500c2de..d0bc162d54c 100644 --- a/pkg/services/support/move_task_order/move_task_order_creator.go +++ b/pkg/services/support/move_task_order/move_task_order_creator.go @@ -132,17 +132,27 @@ func createOrder(appCtx appcontext.AppContext, customer *models.ServiceMember, o order.OriginDutyLocation = originDutyLocation order.OriginDutyLocationID = &originDutyLocationID - var originDutyLocationGBLOC models.PostalCodeToGBLOC - originDutyLocationGBLOC, err = models.FetchGBLOCForPostalCode(appCtx.DB(), originDutyLocation.Address.PostalCode) - if err != nil { - switch err { - case sql.ErrNoRows: - return nil, apperror.NewNotFoundError(originDutyLocationID, "while looking for Duty Location PostalCodeToGBLOC") - default: - return nil, apperror.NewQueryError("PostalCodeToGBLOC", err, "") + var originDutyLocationGBLOC *string + if *originDutyLocation.Address.IsOconus { + originDutyLocationGBLOCOconus, err := models.FetchAddressGbloc(appCtx.DB(), originDutyLocation.Address, order.ServiceMember) + if err != nil { + return nil, apperror.NewNotFoundError(originDutyLocation.ID, "while looking for Duty Location Oconus GBLOC") + } + originDutyLocationGBLOC = originDutyLocationGBLOCOconus + } else { + originDutyLocationGBLOCConus, err := models.FetchGBLOCForPostalCode(appCtx.DB(), originDutyLocation.Address.PostalCode) + if err != nil { + switch err { + case sql.ErrNoRows: + return nil, apperror.NewNotFoundError(originDutyLocation.ID, "while looking for Duty Location PostalCodeToGBLOC") + default: + return nil, apperror.NewQueryError("PostalCodeToGBLOC", err, "") + } } + originDutyLocationGBLOC = &originDutyLocationGBLOCConus.GBLOC } - order.OriginDutyLocationGBLOC = &originDutyLocationGBLOC.GBLOC + + order.OriginDutyLocationGBLOC = originDutyLocationGBLOC } // Check that the uploaded orders document exists var uploadedOrders *models.Document diff --git a/pkg/services/transportation_office.go b/pkg/services/transportation_office.go index 307f0d0fef5..7255991f376 100644 --- a/pkg/services/transportation_office.go +++ b/pkg/services/transportation_office.go @@ -9,8 +9,8 @@ import ( //go:generate mockery --name TransportationOfficesFetcher type TransportationOfficesFetcher interface { - GetTransportationOffices(appCtx appcontext.AppContext, search string, forPpm bool) (*models.TransportationOffices, error) + GetTransportationOffices(appCtx appcontext.AppContext, search string, forPpm bool, forAdminOfficeUserReqFilter bool) (*models.TransportationOffices, error) GetTransportationOffice(appCtx appcontext.AppContext, transportationOfficeID uuid.UUID, includeOnlyPPMCloseoutOffices bool) (*models.TransportationOffice, error) GetAllGBLOCs(appCtx appcontext.AppContext) (*models.GBLOCs, error) - GetCounselingOffices(appCtx appcontext.AppContext, dutyLocationID uuid.UUID) (*models.TransportationOffices, error) + GetCounselingOffices(appCtx appcontext.AppContext, dutyLocationID uuid.UUID, serviceMemberID uuid.UUID) (*models.TransportationOffices, error) } diff --git a/pkg/services/transportation_office/transportation_office_fetcher.go b/pkg/services/transportation_office/transportation_office_fetcher.go index be942890c75..8fde2e98093 100644 --- a/pkg/services/transportation_office/transportation_office_fetcher.go +++ b/pkg/services/transportation_office/transportation_office_fetcher.go @@ -46,8 +46,8 @@ func (o transportationOfficesFetcher) GetTransportationOffice(appCtx appcontext. return &transportationOffice, nil } -func (o transportationOfficesFetcher) GetTransportationOffices(appCtx appcontext.AppContext, search string, forPpm bool) (*models.TransportationOffices, error) { - officeList, err := FindTransportationOffice(appCtx, search, forPpm) +func (o transportationOfficesFetcher) GetTransportationOffices(appCtx appcontext.AppContext, search string, forPpm bool, forAdminOfficeUserReqFilter bool) (*models.TransportationOffices, error) { + officeList, err := FindTransportationOffice(appCtx, search, forPpm, forAdminOfficeUserReqFilter) if err != nil { switch err { @@ -61,9 +61,15 @@ func (o transportationOfficesFetcher) GetTransportationOffices(appCtx appcontext return &officeList, nil } -func FindTransportationOffice(appCtx appcontext.AppContext, search string, forPpm bool) (models.TransportationOffices, error) { +func FindTransportationOffice(appCtx appcontext.AppContext, search string, forPpm bool, forAdminOfficeUserReqFilter bool) (models.TransportationOffices, error) { var officeList []models.TransportationOffice + // Changing return limit for Admin Requested Office Users Transportation Office Filter implementation + var limit = 5 + if forAdminOfficeUserReqFilter { + limit = 50 + } + // The % operator filters out strings that are below this similarity threshold err := appCtx.DB().Q().RawQuery("SET pg_trgm.similarity_threshold = 0.03").Exec() if err != nil { @@ -80,13 +86,13 @@ func FindTransportationOffice(appCtx appcontext.AppContext, search string, forPp } sqlQuery += ` order by sim desc - limit 5) + limit $2) select office.* from names n inner join transportation_offices office on n.transportation_office_id = office.id group by office.id order by max(n.sim) desc, office.name - limit 5` - query := appCtx.DB().Q().RawQuery(sqlQuery, search) + limit $2` + query := appCtx.DB().Q().RawQuery(sqlQuery, search, limit) if err := query.All(&officeList); err != nil { if errors.Cause(err).Error() != models.RecordNotFoundErrorString { return officeList, err @@ -127,8 +133,8 @@ func ListDistinctGBLOCs(appCtx appcontext.AppContext) (models.GBLOCs, error) { return gblocList, err } -func (o transportationOfficesFetcher) GetCounselingOffices(appCtx appcontext.AppContext, dutyLocationID uuid.UUID) (*models.TransportationOffices, error) { - officeList, err := findCounselingOffice(appCtx, dutyLocationID) +func (o transportationOfficesFetcher) GetCounselingOffices(appCtx appcontext.AppContext, dutyLocationID uuid.UUID, serviceMemberID uuid.UUID) (*models.TransportationOffices, error) { + officeList, err := findCounselingOffice(appCtx, dutyLocationID, serviceMemberID) if err != nil { switch err { @@ -143,7 +149,8 @@ func (o transportationOfficesFetcher) GetCounselingOffices(appCtx appcontext.App } // return all the transportation offices in the GBLOC of the given duty location where provides_services_counseling = true -func findCounselingOffice(appCtx appcontext.AppContext, dutyLocationID uuid.UUID) (models.TransportationOffices, error) { +// serviceMemberID is only provided when this function is called by the office handler +func findCounselingOffice(appCtx appcontext.AppContext, dutyLocationID uuid.UUID, serviceMemberID uuid.UUID) (models.TransportationOffices, error) { var officeList []models.TransportationOffice duty_location, err := models.FetchDutyLocation(appCtx.DB(), dutyLocationID) @@ -157,7 +164,7 @@ func findCounselingOffice(appCtx appcontext.AppContext, dutyLocationID uuid.UUID // Find for oconus duty location // ******************************** if *duty_location.Address.IsOconus { - gblocDepartmentIndicator, err := findOconusGblocDepartmentIndicator(appCtx, duty_location) + gblocDepartmentIndicator, err := findOconusGblocDepartmentIndicator(appCtx, duty_location, serviceMemberID) if err != nil { return officeList, err } @@ -238,8 +245,8 @@ func findCounselingOffice(appCtx appcontext.AppContext, dutyLocationID uuid.UUID return officeList, nil } -func findOconusGblocDepartmentIndicator(appCtx appcontext.AppContext, dutyLocation models.DutyLocation) (*oconusGblocDepartmentIndicator, error) { - serviceMember, err := models.FetchServiceMember(appCtx.DB(), appCtx.Session().ServiceMemberID) +func findOconusGblocDepartmentIndicator(appCtx appcontext.AppContext, dutyLocation models.DutyLocation, serviceMemberID uuid.UUID) (*oconusGblocDepartmentIndicator, error) { + serviceMember, err := models.FetchServiceMember(appCtx.DB(), serviceMemberID) if err != nil { return nil, err } diff --git a/pkg/services/transportation_office/transportation_office_fetcher_test.go b/pkg/services/transportation_office/transportation_office_fetcher_test.go index 9ce5bab765c..532ab017ede 100644 --- a/pkg/services/transportation_office/transportation_office_fetcher_test.go +++ b/pkg/services/transportation_office/transportation_office_fetcher_test.go @@ -3,16 +3,15 @@ package transportationoffice import ( "fmt" "testing" - "time" "github.com/gofrs/uuid" "github.com/stretchr/testify/suite" - "github.com/transcom/mymove/pkg/appcontext" "github.com/transcom/mymove/pkg/auth" "github.com/transcom/mymove/pkg/factory" "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/testdatagen" "github.com/transcom/mymove/pkg/testingsuite" ) @@ -43,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) @@ -54,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) } @@ -88,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) @@ -181,7 +180,16 @@ func (suite *TransportationOfficeServiceSuite) Test_FindCounselingOffices() { }, }, nil) - offices, err := findCounselingOffice(suite.AppContextForTest(), origDutyLocation.ID) + armyAffliation := models.AffiliationARMY + serviceMember := factory.BuildServiceMember(suite.DB(), []factory.Customization{ + { + Model: models.ServiceMember{ + Affiliation: &armyAffliation, + }, + }, + }, nil) + + offices, err := findCounselingOffice(suite.AppContextForTest(), origDutyLocation.ID, serviceMember.ID) suite.NoError(err) suite.Len(offices, 2) @@ -190,19 +198,6 @@ func (suite *TransportationOfficeServiceSuite) Test_FindCounselingOffices() { } func (suite *TransportationOfficeServiceSuite) Test_Oconus_AK_FindCounselingOffices() { - testContractName := "Test_findOconusGblocDepartmentIndicator" - testContractCode := "Test_findOconusGblocDepartmentIndicator_Code" - testPostalCode := "99790" - testPostalCode2 := "99701" - testGbloc := "ABCD" - testGbloc2 := "EFGH" - testTransportationName := "TEST - PPO" - testTransportationName2 := "TEST - PPO2" - - serviceAffiliations := []models.ServiceMemberAffiliation{models.AffiliationARMY, - models.AffiliationNAVY, models.AffiliationMARINES, models.AffiliationAIRFORCE, models.AffiliationCOASTGUARD, - models.AffiliationSPACEFORCE} - setupServiceMember := func(serviceMemberAffiliation models.ServiceMemberAffiliation) models.ServiceMember { customServiceMember := models.ServiceMember{ FirstName: models.StringPointer("Gregory"), @@ -234,48 +229,19 @@ func (suite *TransportationOfficeServiceSuite) Test_Oconus_AK_FindCounselingOffi return serviceMember } - createContract := func(appCtx appcontext.AppContext, contractCode string, contractName string) (*models.ReContract, error) { - // See if contract code already exists. - exists, err := appCtx.DB().Where("code = ?", contractCode).Exists(&models.ReContract{}) - if err != nil { - return nil, fmt.Errorf("could not determine if contract code [%s] existed: %w", contractCode, err) - } - if exists { - return nil, fmt.Errorf("the provided contract code [%s] already exists", contractCode) - } + setupDataForOconusSearchCounselingOffice := func(postalCode string, gbloc string) (models.ReRateArea, models.OconusRateArea, models.UsPostRegionCity, models.DutyLocation) { + contract := testdatagen.FetchOrMakeReContract(suite.DB(), testdatagen.Assertions{}) - // Contract code is new; insert it. - contract := models.ReContract{ - Code: contractCode, - Name: contractName, - } - verrs, err := appCtx.DB().ValidateAndSave(&contract) - if verrs.HasAny() { - return nil, fmt.Errorf("validation errors when saving contract [%+v]: %w", contract, verrs) - } - if err != nil { - return nil, fmt.Errorf("could not save contract [%+v]: %w", contract, err) - } - return &contract, nil - } - - setupDataForOconusSearchCounselingOffice := func(contract models.ReContract, postalCode string, gbloc string, transportationName string) (models.ReRateArea, models.OconusRateArea, models.UsPostRegionCity, models.DutyLocation) { rateAreaCode := uuid.Must(uuid.NewV4()).String()[0:5] - rateArea := models.ReRateArea{ - ID: uuid.Must(uuid.NewV4()), - ContractID: contract.ID, - IsOconus: true, - Code: rateAreaCode, - Name: fmt.Sprintf("Alaska-%s", rateAreaCode), - Contract: contract, - } - verrs, err := suite.DB().ValidateAndCreate(&rateArea) - if verrs.HasAny() { - suite.Fail(verrs.Error()) - } - if err != nil { - suite.Fail(err.Error()) - } + rateArea := testdatagen.FetchOrMakeReRateArea(suite.DB(), testdatagen.Assertions{ + ReRateArea: models.ReRateArea{ + ContractID: contract.ID, + IsOconus: true, + Name: fmt.Sprintf("Alaska-%s", rateAreaCode), + Contract: contract, + }, + }) + suite.NotNil(rateArea) us_country, err := models.FetchCountryByCode(suite.DB(), "US") suite.NotNil(us_country) @@ -285,32 +251,18 @@ func (suite *TransportationOfficeServiceSuite) Test_Oconus_AK_FindCounselingOffi suite.NotNil(usprc) suite.FatalNoError(err) - oconusRateArea := models.OconusRateArea{ - ID: uuid.Must(uuid.NewV4()), - RateAreaId: rateArea.ID, - CountryId: us_country.ID, - UsPostRegionCityId: usprc.ID, - Active: true, - } - verrs, err = suite.DB().ValidateAndCreate(&oconusRateArea) - if verrs.HasAny() { - suite.Fail(verrs.Error()) - } - if err != nil { - suite.Fail(err.Error()) - } + oconusRateArea, err := models.FetchOconusRateAreaByCityId(suite.DB(), usprc.ID.String()) + suite.NotNil(oconusRateArea) + suite.Nil(err) - address := models.Address{ - StreetAddress1: "n/a", - City: "SomeCity", - State: "AK", - PostalCode: postalCode, - County: models.StringPointer("SomeCounty"), - IsOconus: models.BoolPointer(true), - UsPostRegionCityID: &usprc.ID, - CountryId: models.UUIDPointer(us_country.ID), - } - suite.MustSave(&address) + address := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + IsOconus: models.BoolPointer(true), + UsPostRegionCityID: &usprc.ID, + }, + }, + }, nil) origDutyLocation := factory.BuildDutyLocation(suite.DB(), []factory.Customization{ { @@ -321,7 +273,7 @@ func (suite *TransportationOfficeServiceSuite) Test_Oconus_AK_FindCounselingOffi }, { Model: models.TransportationOffice{ - Name: transportationName, + Name: "TEST - PPO", Gbloc: gbloc, ProvidesCloseout: true, }, @@ -331,149 +283,69 @@ func (suite *TransportationOfficeServiceSuite) Test_Oconus_AK_FindCounselingOffi found_duty_location, _ := models.FetchDutyLocation(suite.DB(), origDutyLocation.ID) - return rateArea, oconusRateArea, *usprc, found_duty_location + return rateArea, *oconusRateArea, *usprc, found_duty_location } - suite.Run("success - findOconusGblocDepartmentIndicator - returns default GLOC for departmentAffiliation if no specific departmentAffilation mapping is defined", func() { - contract, err := createContract(suite.AppContextForTest(), testContractCode, testContractName) - suite.NotNil(contract) - suite.FatalNoError(err) - + suite.Run("success - findOconusGblocDepartmentIndicator - returns default GBLOC for departmentAffiliation if no specific departmentAffilation mapping is defined", func() { const fairbanksAlaskaPostalCode = "99790" - _, oconusRateArea, _, dutylocation := setupDataForOconusSearchCounselingOffice(*contract, fairbanksAlaskaPostalCode, testGbloc, testTransportationName) + _, oconusRateArea, _, dutylocation := setupDataForOconusSearchCounselingOffice(fairbanksAlaskaPostalCode, "JEAT") // setup department affiliation to GBLOC mappings - expected_gbloc := "TEST-GBLOC" - jppsoRegion := models.JppsoRegions{ - Code: expected_gbloc, - Name: "TEST PPM", - } - suite.MustSave(&jppsoRegion) - - gblocAors := models.GblocAors{ - JppsoRegionID: jppsoRegion.ID, - OconusRateAreaID: oconusRateArea.ID, - // DepartmentIndicator is nil, - } - suite.MustSave(&gblocAors) - - serviceAffiliations := []models.ServiceMemberAffiliation{models.AffiliationARMY, - models.AffiliationNAVY, models.AffiliationMARINES, models.AffiliationAIRFORCE, models.AffiliationCOASTGUARD, - models.AffiliationSPACEFORCE} - - // loop through and make sure all branches are using expected default GBLOC - for _, affiliation := range serviceAffiliations { - serviceMember := setupServiceMember(affiliation) - appCtx := suite.AppContextWithSessionForTest(&auth.Session{ - ServiceMemberID: serviceMember.ID, - }) - suite.Nil(err) - departmentIndictor, err := findOconusGblocDepartmentIndicator(appCtx, dutylocation) - suite.NotNil(departmentIndictor) - suite.Nil(err) - suite.Nil(departmentIndictor.DepartmentIndicator) - suite.Equal(expected_gbloc, departmentIndictor.Gbloc) - } - }) - - suite.Run("success - findOconusGblocDepartmentIndicator - returns specific GLOC for departmentAffiliation when a specific departmentAffilation mapping is defined", func() { - contract, err := createContract(suite.AppContextForTest(), testContractCode, testContractName) - suite.NotNil(contract) - suite.FatalNoError(err) + jppsoRegion, err := models.FetchJppsoRegionByCode(suite.DB(), "JEAT") + suite.NotNil(jppsoRegion) + suite.Nil(err) - _, oconusRateArea, _, dutylocation := setupDataForOconusSearchCounselingOffice(*contract, testPostalCode, testGbloc, testTransportationName) + gblocAors, err := models.FetchGblocAorsByJppsoCodeRateAreaDept(suite.DB(), jppsoRegion.ID, oconusRateArea.ID, models.DepartmentIndicatorARMY.String()) + suite.NotNil(gblocAors) + suite.Nil(err) - departmentIndicators := []models.DepartmentIndicator{models.DepartmentIndicatorARMY, - models.DepartmentIndicatorARMYCORPSOFENGINEERS, models.DepartmentIndicatorCOASTGUARD, - models.DepartmentIndicatorNAVYANDMARINES, models.DepartmentIndicatorAIRANDSPACEFORCE} + serviceMember := setupServiceMember(models.AffiliationARMY) + appCtx := suite.AppContextWithSessionForTest(&auth.Session{ + ServiceMemberID: serviceMember.ID, + }) + departmentIndictor, err := findOconusGblocDepartmentIndicator(appCtx, dutylocation, serviceMember.ID) + suite.NotNil(departmentIndictor) + suite.Nil(err) + suite.Nil(departmentIndictor.DepartmentIndicator) + suite.Equal("JEAT", departmentIndictor.Gbloc) + }) - expectedAffiliationToDepartmentIndicatorMap := make(map[string]string, 0) - expectedAffiliationToDepartmentIndicatorMap[models.AffiliationARMY.String()] = models.DepartmentIndicatorARMY.String() - expectedAffiliationToDepartmentIndicatorMap[models.AffiliationNAVY.String()] = models.DepartmentIndicatorNAVYANDMARINES.String() - expectedAffiliationToDepartmentIndicatorMap[models.AffiliationMARINES.String()] = models.DepartmentIndicatorNAVYANDMARINES.String() - expectedAffiliationToDepartmentIndicatorMap[models.AffiliationAIRFORCE.String()] = models.DepartmentIndicatorAIRANDSPACEFORCE.String() - expectedAffiliationToDepartmentIndicatorMap[models.AffiliationCOASTGUARD.String()] = models.DepartmentIndicatorCOASTGUARD.String() - expectedAffiliationToDepartmentIndicatorMap[models.AffiliationSPACEFORCE.String()] = models.DepartmentIndicatorAIRANDSPACEFORCE.String() + suite.Run("success - findOconusGblocDepartmentIndicator - returns specific GBLOC for departmentAffiliation when a specific departmentAffilation mapping is defined -- simulate Zone 2 scenerio", func() { + const fairbanksAlaskaPostalCode = "99790" + _, oconusRateArea, _, dutylocation := setupDataForOconusSearchCounselingOffice(fairbanksAlaskaPostalCode, "MBFL") // setup department affiliation to GBLOC mappings - expected_gbloc := "TEST-GBLOC" - jppsoRegion := models.JppsoRegions{ - Code: expected_gbloc, - Name: "TEST PPM", - } - suite.MustSave(&jppsoRegion) + jppsoRegion, err := models.FetchJppsoRegionByCode(suite.DB(), "MBFL") + suite.NotNil(jppsoRegion) + suite.Nil(err) - defaultGblocAors := models.GblocAors{ - JppsoRegionID: jppsoRegion.ID, - OconusRateAreaID: oconusRateArea.ID, - //DepartmentIndicator is nil, - } - suite.MustSave(&defaultGblocAors) - - // setup specific departmentAffiliation mapping for each branch - for _, departmentIndicator := range departmentIndicators { - gblocAors := models.GblocAors{ - JppsoRegionID: jppsoRegion.ID, - OconusRateAreaID: oconusRateArea.ID, - DepartmentIndicator: models.StringPointer(departmentIndicator.String()), - } - suite.MustSave(&gblocAors) - } + gblocAors, err := models.FetchGblocAorsByJppsoCodeRateAreaDept(suite.DB(), jppsoRegion.ID, oconusRateArea.ID, models.DepartmentIndicatorAIRANDSPACEFORCE.String()) + suite.NotNil(gblocAors) + suite.Nil(err) // loop through and make sure all branches are using it's own dedicated GBLOC and not default - for _, affiliation := range serviceAffiliations { - serviceMember := setupServiceMember(affiliation) - appCtx := suite.AppContextWithSessionForTest(&auth.Session{ - ServiceMemberID: serviceMember.ID, - }) - suite.Nil(err) - departmentIndictor, err := findOconusGblocDepartmentIndicator(appCtx, dutylocation) - suite.NotNil(departmentIndictor) - suite.Nil(err) - suite.NotNil(departmentIndictor.DepartmentIndicator) - if match, ok := expectedAffiliationToDepartmentIndicatorMap[affiliation.String()]; ok { - // verify service member's affiliation matches on specific departmentIndicator mapping record - suite.Equal(match, *departmentIndictor.DepartmentIndicator) - } else { - suite.Fail(fmt.Sprintf("key does not exist for %s", affiliation.String())) - } - suite.Equal(expected_gbloc, departmentIndictor.Gbloc) - } - }) - - suite.Run("failure -- returns error when there are default and no department specific GBLOC", func() { - contract, err := createContract(suite.AppContextForTest(), testContractCode, testContractName) - suite.NotNil(contract) - suite.FatalNoError(err) - - _, _, _, dutylocation := setupDataForOconusSearchCounselingOffice(*contract, testPostalCode, testGbloc, testTransportationName) - - // No specific departmentAffiliation mapping or default were created. - // Expect an error response when nothing is found. serviceMember := setupServiceMember(models.AffiliationAIRFORCE) appCtx := suite.AppContextWithSessionForTest(&auth.Session{ ServiceMemberID: serviceMember.ID, }) suite.Nil(err) - departmentIndictor, err := findOconusGblocDepartmentIndicator(appCtx, dutylocation) - suite.Nil(departmentIndictor) - suite.NotNil(err) + departmentIndictor, err := findOconusGblocDepartmentIndicator(appCtx, dutylocation, serviceMember.ID) + suite.NotNil(departmentIndictor) + suite.Nil(err) + suite.NotNil(departmentIndictor.DepartmentIndicator) + suite.Equal(models.DepartmentIndicatorAIRANDSPACEFORCE.String(), *departmentIndictor.DepartmentIndicator) + suite.Equal("MBFL", departmentIndictor.Gbloc) }) suite.Run("failure - findOconusGblocDepartmentIndicator - returns error when find service member ID fails", func() { - contract, err := createContract(suite.AppContextForTest(), testContractCode, testContractName) - suite.NotNil(contract) - suite.FatalNoError(err) - - _, _, _, dutylocation := setupDataForOconusSearchCounselingOffice(*contract, testPostalCode, testGbloc, testTransportationName) + _, _, _, dutylocation := setupDataForOconusSearchCounselingOffice("99714", "JEAT") appCtx := suite.AppContextWithSessionForTest(&auth.Session{ // create fake service member ID to raise NOT found error ServiceMemberID: uuid.Must(uuid.NewV4()), }) - suite.Nil(err) - departmentIndictor, err := findOconusGblocDepartmentIndicator(appCtx, dutylocation) + departmentIndictor, err := findOconusGblocDepartmentIndicator(appCtx, dutylocation, appCtx.Session().ServiceMemberID) suite.Nil(departmentIndictor) suite.NotNil(err) }) @@ -483,39 +355,22 @@ func (suite *TransportationOfficeServiceSuite) Test_Oconus_AK_FindCounselingOffi ServiceMemberID: uuid.Must(uuid.NewV4()), }) unknown_duty_location_id := uuid.Must(uuid.NewV4()) - offices, err := findCounselingOffice(appCtx, unknown_duty_location_id) + offices, err := findCounselingOffice(appCtx, unknown_duty_location_id, appCtx.Session().ServiceMemberID) suite.Nil(offices) suite.NotNil(err) }) suite.Run("success - offices using default departmentIndicator mapping", func() { - contract, err := createContract(suite.AppContextForTest(), testContractCode, testContractName) - suite.NotNil(contract) - suite.FatalNoError(err) - - _, oconusRateArea, _, dutylocation := setupDataForOconusSearchCounselingOffice(*contract, testPostalCode, testGbloc, testTransportationName) + _, oconusRateArea, _, dutylocation := setupDataForOconusSearchCounselingOffice("99619", "MAPS") // setup department affiliation to GBLOC mappings - jppsoRegion := models.JppsoRegions{ - Code: testGbloc, - Name: "TEST PPM", - } - suite.MustSave(&jppsoRegion) - - gblocAors := models.GblocAors{ - JppsoRegionID: jppsoRegion.ID, - OconusRateAreaID: oconusRateArea.ID, - // DepartmentIndicator is nil, - } - suite.MustSave(&gblocAors) + jppsoRegion, err := models.FetchJppsoRegionByCode(suite.DB(), "MAPS") + suite.NotNil(jppsoRegion) + suite.Nil(err) - postalCodeToGBLOC := models.PostalCodeToGBLOC{ - PostalCode: testPostalCode, - GBLOC: testGbloc, - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - } - suite.MustSave(&postalCodeToGBLOC) + gblocAors, err := models.FetchGblocAorsByJppsoCodeRateAreaDept(suite.DB(), jppsoRegion.ID, oconusRateArea.ID, models.DepartmentIndicatorARMY.String()) + suite.NotNil(gblocAors) + suite.Nil(err) serviceMember := setupServiceMember(models.AffiliationAIRFORCE) appCtx := suite.AppContextWithSessionForTest(&auth.Session{ @@ -523,122 +378,28 @@ func (suite *TransportationOfficeServiceSuite) Test_Oconus_AK_FindCounselingOffi }) suite.Nil(err) - offices, err := findCounselingOffice(appCtx, dutylocation.ID) + offices, err := findCounselingOffice(appCtx, dutylocation.ID, appCtx.Session().ServiceMemberID) suite.NotNil(offices) suite.Nil(err) suite.Equal(1, len(offices)) - suite.Equal(testTransportationName, offices[0].Name) + suite.Equal("TEST - PPO", offices[0].Name) // add another transportation office factory.BuildTransportationOffice(suite.DB(), []factory.Customization{ { Model: models.TransportationOffice{ - Name: testTransportationName2, + Name: "TEST - PPO2", ProvidesCloseout: true, - Gbloc: testGbloc, + Gbloc: "MAPS", }, }, }, nil) - offices, err = findCounselingOffice(appCtx, dutylocation.ID) + offices, err = findCounselingOffice(appCtx, dutylocation.ID, appCtx.Session().ServiceMemberID) suite.NotNil(offices) suite.Nil(err) suite.Equal(2, len(offices)) }) - suite.Run("success - returns correct office based on service affiliation -- simulate Zone 2 scenerio", func() { - contract, err := createContract(suite.AppContextForTest(), testContractCode, testContractName) - suite.NotNil(contract) - suite.FatalNoError(err) - - _, oconusRateArea, _, dutylocation := setupDataForOconusSearchCounselingOffice(*contract, testPostalCode, testGbloc, testTransportationName) - - // ****************************************************************************** - // setup department affiliation to GBLOC mappings for AF/SF - // ****************************************************************************** - jppsoRegion_AFSF := models.JppsoRegions{ - Code: testGbloc, - Name: "TEST PPO", - } - suite.MustSave(&jppsoRegion_AFSF) - - gblocAors_AFSF := models.GblocAors{ - JppsoRegionID: jppsoRegion_AFSF.ID, - OconusRateAreaID: oconusRateArea.ID, - DepartmentIndicator: models.StringPointer(models.DepartmentIndicatorAIRANDSPACEFORCE.String()), - } - suite.MustSave(&gblocAors_AFSF) - - postalCodeToGBLOC_AFSF := models.PostalCodeToGBLOC{ - PostalCode: testPostalCode, - GBLOC: testGbloc, - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - } - suite.MustSave(&postalCodeToGBLOC_AFSF) - // ****************************************************************************** - - // ****************************************************************************** - // setup department affiliation to GBLOC mappings for other branches NOT AF/SF - // ****************************************************************************** - jppsoRegion_not_AFSF := models.JppsoRegions{ - Code: testGbloc2, - Name: "TEST PPO 2", - } - suite.MustSave(&jppsoRegion_not_AFSF) - - gblocAors_not_AFSF := models.GblocAors{ - JppsoRegionID: jppsoRegion_not_AFSF.ID, - OconusRateAreaID: oconusRateArea.ID, - } - suite.MustSave(&gblocAors_not_AFSF) - - postalCodeToGBLOC_not_AFSF := models.PostalCodeToGBLOC{ - PostalCode: testPostalCode2, - GBLOC: testGbloc2, - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - } - suite.MustSave(&postalCodeToGBLOC_not_AFSF) - - // add transportation office for other branches not AF/SF - factory.BuildTransportationOffice(suite.DB(), []factory.Customization{ - { - Model: models.TransportationOffice{ - Name: testTransportationName2, - ProvidesCloseout: true, - Gbloc: testGbloc2, - }, - }, - }, nil) - // ****************************************************************************** - - for _, affiliation := range serviceAffiliations { - serviceMember := setupServiceMember(affiliation) - if affiliation == models.AffiliationAIRFORCE || affiliation == models.AffiliationSPACEFORCE { - appCtx := suite.AppContextWithSessionForTest(&auth.Session{ - ServiceMemberID: serviceMember.ID, - }) - offices, err := findCounselingOffice(appCtx, dutylocation.ID) - suite.NotNil(offices) - suite.Nil(err) - suite.Equal(1, len(offices)) - // verify expected office is for AF/SF and not for Navy ..etc.. - suite.Equal(testTransportationName, offices[0].Name) - suite.NotEqual(testTransportationName2, offices[0].Name) - } else { - appCtx := suite.AppContextWithSessionForTest(&auth.Session{ - ServiceMemberID: serviceMember.ID, - }) - offices, err := findCounselingOffice(appCtx, dutylocation.ID) - suite.NotNil(offices) - suite.Nil(err) - suite.Equal(1, len(offices)) - // verify expected office is for Navy ..etc.. and not AF/SF - suite.Equal(testTransportationName2, offices[0].Name) - suite.NotEqual(testTransportationName, offices[0].Name) - } - } - }) } func (suite *TransportationOfficeServiceSuite) Test_GetTransportationOffice() { diff --git a/pkg/services/users_roles/rules_test.go b/pkg/services/users_roles/rules_test.go index 6574daa969a..ec2f1b10095 100644 --- a/pkg/services/users_roles/rules_test.go +++ b/pkg/services/users_roles/rules_test.go @@ -1,8 +1,6 @@ package usersroles import ( - "github.com/gofrs/uuid" - "github.com/transcom/mymove/pkg/factory" "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/models/roles" @@ -16,20 +14,6 @@ func (suite *UsersRolesServiceSuite) TestCheckTransportationOfficerPolicyViolati setupTestData := func() models.OfficeUser { // Setup officeUser := factory.BuildOfficeUser(suite.DB(), nil, nil) - tioID, _ := uuid.NewV4() - tio := roles.Role{ - ID: tioID, - RoleType: roles.RoleTypeTIO, - } - tooID, _ := uuid.NewV4() - too := roles.Role{ - ID: tooID, - RoleType: roles.RoleTypeTOO, - } - // Insert TIO and TOO into db - rs := roles.Roles{tio, too} - err := suite.DB().Create(rs) - suite.NoError(err) return officeUser } suite.Run("Cannot add both TOO and TIO at the same time", func() { diff --git a/pkg/testdatagen/scenario/shared.go b/pkg/testdatagen/scenario/shared.go index 451f444ade5..8d77d361e00 100644 --- a/pkg/testdatagen/scenario/shared.go +++ b/pkg/testdatagen/scenario/shared.go @@ -4212,7 +4212,6 @@ func createHHGWithOriginSITServiceItems( mock.Anything, mock.Anything, false, - false, ).Return(400, nil) queryBuilder := query.NewQueryBuilder() @@ -4240,7 +4239,7 @@ func createHHGWithOriginSITServiceItems( // called for zip 3 domestic linehaul service item planner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), - "90210", "30813", false, false).Return(2361, nil) + "90210", "30813", false).Return(2361, nil) shipmentUpdater := mtoshipment.NewMTOShipmentStatusUpdater(queryBuilder, serviceItemCreator, planner) _, updateErr := shipmentUpdater.UpdateMTOShipmentStatus(appCtx, shipment.ID, models.MTOShipmentStatusApproved, nil, nil, etag.GenerateEtag(shipment.UpdatedAt)) @@ -4292,9 +4291,8 @@ func createHHGWithOriginSITServiceItems( mock.Anything, mock.Anything, false, - 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 @@ -4485,7 +4483,6 @@ func createHHGWithDestinationSITServiceItems(appCtx appcontext.AppContext, prime mock.Anything, mock.Anything, false, - false, ).Return(400, nil) serviceItemCreator := mtoserviceitem.NewMTOServiceItemCreator(planner, queryBuilder, moveRouter, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticPackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticShorthaulPricer(), ghcrateengine.NewDomesticOriginPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) @@ -4513,7 +4510,7 @@ func createHHGWithDestinationSITServiceItems(appCtx appcontext.AppContext, prime // called for zip 3 domestic linehaul service item planner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), - "90210", "30813", false, false).Return(2361, nil) + "90210", "30813", false).Return(2361, nil) shipmentUpdater := mtoshipment.NewMTOShipmentStatusUpdater(queryBuilder, serviceItemCreator, planner) _, updateErr := shipmentUpdater.UpdateMTOShipmentStatus(appCtx, shipment.ID, models.MTOShipmentStatusApproved, nil, nil, etag.GenerateEtag(shipment.UpdatedAt)) @@ -4560,9 +4557,8 @@ func createHHGWithDestinationSITServiceItems(appCtx appcontext.AppContext, prime mock.Anything, mock.Anything, false, - 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 @@ -4897,7 +4893,7 @@ func createHHGWithPaymentServiceItems( queryBuilder := query.NewQueryBuilder() planner := &routemocks.Planner{} - planner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), mock.Anything, mock.Anything, false, false).Return(123, nil).Once() + planner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), mock.Anything, mock.Anything, false).Return(123, nil).Once() serviceItemCreator := mtoserviceitem.NewMTOServiceItemCreator(planner, queryBuilder, moveRouter, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticPackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticShorthaulPricer(), ghcrateengine.NewDomesticOriginPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) @@ -4922,33 +4918,33 @@ func createHHGWithPaymentServiceItems( logger.Fatal("Error approving move") } // called using the addresses with origin zip of 90210 and destination zip of 94535 - planner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), mock.Anything, mock.Anything, false, false).Return(348, nil).Times(2) + planner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), mock.Anything, mock.Anything, false).Return(348, nil).Times(2) // called using the addresses with origin zip of 90210 and destination zip of 90211 - planner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), mock.Anything, mock.Anything, false, false).Return(3, nil).Times(5) + planner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), mock.Anything, mock.Anything, false).Return(3, nil).Times(5) // called for zip 3 domestic linehaul service item planner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), "94535", "94535", false).Return(348, nil).Times(2) // called for zip 5 domestic linehaul service item - planner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), "94535", "94535", false, false).Return(348, nil).Times(2) + planner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), "94535", "94535", false).Return(348, nil).Times(2) // called for domestic shorthaul service item planner.On("Zip5TransitDistance", mock.AnythingOfType("*appcontext.appContext"), "90210", "90211").Return(3, nil).Times(7) // called for domestic shorthaul service item - planner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), "90210", "90211", false, false).Return(348, nil).Times(10) + planner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), "90210", "90211", false).Return(348, nil).Times(10) // called for domestic origin SIT pickup service item - planner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), "90210", "94535", false, false).Return(348, nil).Once() + planner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), "90210", "94535", false).Return(348, nil).Once() // called for domestic destination SIT delivery service item - planner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), "94535", "90210", false, false).Return(348, nil).Times(2) + planner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), "94535", "90210", false).Return(348, nil).Times(2) // called for DLH, DSH, FSC service item estimated price calculations - planner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), mock.Anything, mock.Anything, false, false).Return(400, nil).Times(3) + planner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), mock.Anything, mock.Anything, false).Return(400, nil).Times(3) for _, shipment := range []models.MTOShipment{longhaulShipment, shorthaulShipment, shipmentWithOriginalWeight, shipmentWithOriginalAndReweighWeight, shipmentWithOriginalAndReweighWeightReweihBolded, shipmentWithOriginalReweighAndAdjustedWeight, shipmentWithOriginalAndAdjustedWeight} { shipmentUpdater := mtoshipment.NewMTOShipmentStatusUpdater(queryBuilder, serviceItemCreator, planner) @@ -5044,9 +5040,8 @@ func createHHGWithPaymentServiceItems( mock.Anything, mock.Anything, false, - 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 @@ -5533,7 +5528,6 @@ func createHHGMoveWithPaymentRequest(appCtx appcontext.AppContext, userUploader mock.Anything, mock.Anything, false, - false, ).Return(910, nil) paymentRequestCreator := paymentrequest.NewPaymentRequestCreator( @@ -10461,7 +10455,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, @@ -10493,29 +10488,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/make_move.go b/pkg/testdatagen/testharness/make_move.go index ca192666c7d..3e2f5fed100 100644 --- a/pkg/testdatagen/testharness/make_move.go +++ b/pkg/testdatagen/testharness/make_move.go @@ -3823,7 +3823,7 @@ func MakeHHGMoveWithApprovedNTSShipmentsForTOO(appCtx appcontext.AppContext) mod planner := &routemocks.Planner{} // mock any and all planner calls - planner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), mock.Anything, mock.Anything, false, false).Return(2361, nil) + planner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), mock.Anything, mock.Anything, false).Return(2361, nil) queryBuilder := query.NewQueryBuilder() serviceItemCreator := mtoserviceitem.NewMTOServiceItemCreator(planner, queryBuilder, moveRouter, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticPackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticShorthaulPricer(), ghcrateengine.NewDomesticOriginPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) @@ -3927,7 +3927,7 @@ func MakeHHGMoveWithApprovedNTSRShipmentsForTOO(appCtx appcontext.AppContext) mo planner := &routemocks.Planner{} // mock any and all planner calls - planner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), mock.Anything, mock.Anything, false, false).Return(2361, nil) + planner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), mock.Anything, mock.Anything, false).Return(2361, nil) queryBuilder := query.NewQueryBuilder() serviceItemCreator := mtoserviceitem.NewMTOServiceItemCreator(planner, queryBuilder, moveRouter, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticPackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticShorthaulPricer(), ghcrateengine.NewDomesticOriginPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) 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/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/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/tioFlows.spec.js b/playwright/tests/office/txo/tioFlows.spec.js index ea4b8791bc5..2ccea5385be 100644 --- a/playwright/tests/office/txo/tioFlows.spec.js +++ b/playwright/tests/office/txo/tioFlows.spec.js @@ -402,6 +402,17 @@ test.describe('TIO user', () => { // Approve the first service item await tioFlowPage.approveServiceItem(); + // testing the clear selection functionality + await expect(page.getByText('Clear selection')).toBeVisible(); + await page.getByText('Clear selection').click(); + // proceed without approving/rejecting + await page.getByText('Next').click(); + await tioFlowPage.slowDown(); + // go back to the previous page + await page.getByText('Previous').click(); + // the service item should neither be approved/rejected so the clear selection should not be seen + await expect(page.getByText('Clear selection')).not.toBeVisible(); + await tioFlowPage.approveServiceItem(); await page.getByText('Next').click(); await tioFlowPage.slowDown(); @@ -557,86 +568,6 @@ test.describe('TIO user', () => { await expect(page.getByText('Move unflagged for financial review.')).toBeVisible(); }); - /** - * This test is being temporarily skipped until flakiness issues - * can be resolved. It was skipped in cypress and is not part of - * the initial playwright conversion. - ahobson 2023-01-05 - */ - test.skip('can add/edit TAC/SAC', async ({ page }) => { - // Payment Requests page - expect(page.url()).toContain('/payment-requests'); - await expect(page.getByTestId('MovePaymentRequests')).toBeVisible(); - - // await expect(page.locator('button')).toContainText('Edit').click(); - // await expect(page.locator('button')).toContainText('Add or edit codes').click(); - // cy.url().should('include', `/moves/NTSTIO/orders`); - - // await page.locator('form').within(() => { - // await page.locator('input[data-testid="ntsTacInput"]').click().fill('E19A'); - // await page.locator('input[data-testid="ntsSacInput"]').click().fill('3L988AS098F'); - // // Edit orders page | Save - // await expect(page.locator('button')).toContainText('Save').click(); - // }); - // cy.url().should('include', `/moves/NTSTIO/details`); - // await expect(page.getByText('Payment requests').click()).toBeVisible(); - // cy.url().should('include', `/payment-requests`); - // await expect(page.locator('button')).toContainText('Edit').click(); - - // await page.locator('input#tacType-NTS').click({ force: true }); - // await page.locator('input#sacType-NTS').click({ force: true }); - // await page.locator('button[type="submit"]').click(); - - // await expect(page.locator('[data-testid="tac"]')).toContainText('E19A (NTS)'); - // await expect(page.locator('[data-testid="sac"]')).toContainText('3L988AS098F (NTS)'); - }); - - // ahobson - 2023-01-05 skipping this test as it is a subset of - // the test called 'can use a payment request page to update - // orders and review a payment request' - test.skip('can view and approve service items', async ({ page }) => { - // Payment Requests page - expect(page.url()).toContain('/payment-requests'); - await expect(page.getByTestId('MovePaymentRequests')).toBeVisible(); - - await page.getByText('Review service items').first().click(); - - // await expect(page.locator('[data-testid="serviceItemName"]')).toContainText('Move management'); - // await page.locator('[data-testid="approveRadio"]').click({ force: true }); - // cy.wait('@patchPaymentServiceItemStatus'); - // await expect(page.locator('button')).toContainText('Next').click(); - - // await expect(page.locator('[data-testid="serviceItemName"]')).toContainText('Domestic origin shuttle service'); - // await page.locator('[data-testid="approveRadio"]').click({ force: true }); - // cy.wait('@patchPaymentServiceItemStatus'); - // await expect(page.locator('button')).toContainText('Next').click(); - - // await expect(page.locator('[data-testid="serviceItemName"]')).toContainText('Domestic origin shuttle service'); - // await page.locator('[data-testid="approveRadio"]').click({ force: true }); - // cy.wait('@patchPaymentServiceItemStatus'); - // await expect(page.locator('button')).toContainText('Next').click(); - - // await expect(page.locator('[data-testid="serviceItemName"]')).toContainText('Domestic crating'); - // await page.locator('[data-testid="approveRadio"]').click({ force: true }); - // cy.wait('@patchPaymentServiceItemStatus'); - // await expect(page.locator('button')).toContainText('Next').click(); - - // await expect(page.locator('[data-testid="serviceItemName"]')).toContainText('Domestic crating'); - // await page.locator('[data-testid="approveRadio"]').click({ force: true }); - // cy.wait('@patchPaymentServiceItemStatus'); - // await expect(page.locator('button')).toContainText('Next').click(); - - // await expect(page.locator('[data-testid="serviceItemName"]')).toContainText('Domestic linehaul'); - // await page.locator('[data-testid="approveRadio"]').click({ force: true }); - // cy.wait('@patchPaymentServiceItemStatus'); - // await expect(page.locator('button')).toContainText('Next').click(); - - // await expect(page.locator('[data-testid="accepted"]')).toContainText('$1,130.21'); - // await expect(page.locator('button')).toContainText('Authorize payment').click(); - // cy.wait(['@getMovePaymentRequests']); - - // await expect(page.locator('[data-testid="tag"]')).toContainText('Reviewed'); - }); - test('is able to view Origin GBLOC', async ({ page }) => { // Check for Origin GBLOC label await expect(page.getByTestId('originGBLOC')).toHaveText('Origin GBLOC'); diff --git a/scripts/db-truncate b/scripts/db-truncate index 53b929432dd..3494c718de2 100755 --- a/scripts/db-truncate +++ b/scripts/db-truncate @@ -15,8 +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')) 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/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/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/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.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/Office/AddOrdersForm/AddOrdersForm.jsx b/src/components/Office/AddOrdersForm/AddOrdersForm.jsx index 113d8761284..a64245d5dae 100644 --- a/src/components/Office/AddOrdersForm/AddOrdersForm.jsx +++ b/src/components/Office/AddOrdersForm/AddOrdersForm.jsx @@ -1,4 +1,5 @@ import React, { useState, useEffect } from 'react'; +import { useParams } from 'react-router-dom'; import { Field, Formik } from 'formik'; import * as Yup from 'yup'; import { FormGroup, Label, Radio, Link as USWDSLink } from '@trussworks/react-uswds'; @@ -17,9 +18,10 @@ 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; let newDutyMeta = ''; @@ -32,6 +34,7 @@ const AddOrdersForm = ({ isBluebarkMoveSelected, }) => { const payGradeOptions = dropdownInputOptions(ORDERS_PAY_GRADE_OPTIONS); + const [counselingOfficeOptions, setCounselingOfficeOptions] = useState(null); const [currentDutyLocation, setCurrentDutyLocation] = useState(''); const [newDutyLocation, setNewDutyLocation] = useState(''); const [showAccompaniedTourField, setShowAccompaniedTourField] = useState(false); @@ -42,6 +45,7 @@ const AddOrdersForm = ({ const [isHasDependentsDisabled, setHasDependentsDisabled] = useState(false); const [prevOrderType, setPrevOrderType] = useState(''); const [filteredOrderTypeOptions, setFilteredOrderTypeOptions] = useState(ordersTypeOptions); + const { customerId: serviceMemberId } = useParams(); const validationSchema = Yup.object().shape({ ordersType: Yup.mixed() @@ -55,6 +59,9 @@ const AddOrdersForm = ({ .required('Required'), hasDependents: Yup.mixed().oneOf(['yes', 'no']).required('Required'), originDutyLocation: Yup.object().nullable().required('Required'), + counselingOfficeId: currentDutyLocation.provides_services_counseling + ? Yup.string().required('Required') + : Yup.string().notRequired(), newDutyLocation: Yup.object().nullable().required('Required'), grade: Yup.mixed().oneOf(Object.keys(ORDERS_PAY_GRADE_OPTIONS)).required('Required'), accompaniedTour: showAccompaniedTourField @@ -79,6 +86,17 @@ const AddOrdersForm = ({ }, []); useEffect(() => { + if (currentDutyLocation?.id && serviceMemberId) { + showCounselingOffices(currentDutyLocation.id, serviceMemberId).then((fetchedData) => { + if (fetchedData.body) { + const counselingOffices = fetchedData.body.map((item) => ({ + key: item.id, + value: item.name, + })); + setCounselingOfficeOptions(counselingOffices); + } + }); + } // Check if either currentDutyLocation or newDutyLocation is OCONUS if (currentDutyLocation?.address?.isOconus || newDutyLocation?.address?.isOconus) { setIsOconusMove(true); @@ -96,7 +114,7 @@ const AddOrdersForm = ({ setShowDependentAgeFields(false); } } - }, [currentDutyLocation, newDutyLocation, isOconusMove, hasDependents, enableUB]); + }, [currentDutyLocation, newDutyLocation, isOconusMove, hasDependents, enableUB, serviceMemberId]); useEffect(() => { const fetchData = async () => { @@ -115,11 +133,19 @@ const AddOrdersForm = ({ return ( - {({ values, isValid, isSubmitting, handleSubmit, handleChange, touched, setFieldValue }) => { + {({ values, isValid, isSubmitting, handleSubmit, handleChange, touched, setFieldValue, setValues }) => { const isRetirementOrSeparation = ['RETIREMENT', 'SEPARATION'].includes(values.ordersType); if (!values.origin_duty_location && touched.origin_duty_location) originMeta = 'Required'; else originMeta = null; + const handleCounselingOfficeChange = () => { + setValues({ + ...values, + counselingOfficeId: null, + }); + setCounselingOfficeOptions(null); + }; + if (!values.newDutyLocation && touched.newDutyLocation) newDutyMeta = 'Required'; else newDutyMeta = null; const handleHasDependentsChange = (e) => { @@ -172,9 +198,10 @@ const AddOrdersForm = ({ handleOrderTypeChange(e); }} isDisabled={isSafetyMoveSelected || isBluebarkMoveSelected} + hint="Required" /> - - + + { setCurrentDutyLocation(e); + handleCounselingOfficeChange(); }} metaOverride={originMeta} required + hint="Required" /> + {currentDutyLocation.provides_services_counseling && ( +
+ + +
+ )} {isRetirementOrSeparation ? ( <> @@ -216,6 +263,7 @@ const AddOrdersForm = ({ displayAddress={false} placeholder="Enter a city or ZIP" metaOverride={newDutyMeta} + hint="Required" onDutyLocationChange={(e) => { setNewDutyLocation(e); }} @@ -226,6 +274,7 @@ const AddOrdersForm = ({ name="newDutyLocation" label="New duty location" required + hint="Required" metaOverride={newDutyMeta} onDutyLocationChange={(e) => { setNewDutyLocation(e); @@ -234,7 +283,7 @@ const AddOrdersForm = ({ )} - +
- +
)} - +
diff --git a/src/components/Office/AddOrdersForm/AddOrdersForm.test.jsx b/src/components/Office/AddOrdersForm/AddOrdersForm.test.jsx index 43c59015235..3fdc95c7972 100644 --- a/src/components/Office/AddOrdersForm/AddOrdersForm.test.jsx +++ b/src/components/Office/AddOrdersForm/AddOrdersForm.test.jsx @@ -5,10 +5,12 @@ import { Provider } from 'react-redux'; import AddOrdersForm from './AddOrdersForm'; +import { MockProviders } from 'testUtils'; import { dropdownInputOptions } from 'utils/formatters'; import { ORDERS_TYPE, ORDERS_TYPE_OPTIONS } from 'constants/orders'; import { configureStore } from 'shared/store'; import { isBooleanFlagEnabled } from 'utils/featureFlags'; +import { servicesCounselingRoutes } from 'constants/routes'; jest.setTimeout(60000); @@ -75,10 +77,44 @@ jest.mock('components/LocationSearchBox/api', () => ({ name: 'Luke AFB', updated_at: '2021-02-11T16:48:04.117Z', }, + { + address: { + city: '', + id: '25be4d12-fe93-47f1-bbec-1db386dfa67e', + postalCode: '', + state: '', + streetAddress1: '', + }, + address_id: '4334640b-c35e-4293-a2f1-36c7b629f904', + affiliation: 'AIR_FORCE', + created_at: '2021-02-11T16:48:04.117Z', + id: '22f0755f-6f35-478b-9a75-35a69211da1d', + name: 'Scott AFB', + updated_at: '2021-02-11T16:48:04.117Z', + provides_services_counseling: true, + }, ]), ), })); +jest.mock('services/ghcApi', () => ({ + ...jest.requireActual('services/ghcApi'), + showCounselingOffices: jest.fn().mockImplementation(() => + Promise.resolve({ + body: [ + { + id: '3e937c1f-5539-4919-954d-017989130584', + name: 'Albuquerque AFB', + }, + { + id: 'fa51dab0-4553-4732-b843-1f33407f77bc', + name: 'Glendale Luke AFB', + }, + ], + }), + ), +})); + jest.mock('utils/featureFlags', () => ({ ...jest.requireActual('utils/featureFlags'), isBooleanFlagEnabled: jest.fn().mockImplementation(() => Promise.resolve(false)), @@ -96,6 +132,7 @@ const initialValues = { accompaniedTour: '', dependentsUnderTwelve: '', dependentsTwelveAndOver: '', + counselingOfficeId: '', }; const testProps = { initialValues, @@ -103,6 +140,8 @@ const testProps = { onSubmit: jest.fn(), onBack: jest.fn(), }; +const mockParams = { customerId: 'ea51dab0-4553-4732-b843-1f33407f77bd' }; +const mockPath = servicesCounselingRoutes.BASE_CUSTOMERS_ORDERS_ADD_PATH; describe('CreateMoveCustomerInfo Component', () => { it('renders the form inputs', async () => { @@ -114,15 +153,15 @@ describe('CreateMoveCustomerInfo Component', () => { await waitFor(() => { expect(screen.getByText('Tell us about the orders')).toBeInTheDocument(); - expect(screen.getByLabelText('Orders type')).toBeInTheDocument(); - expect(screen.getByLabelText('Orders date')).toBeInTheDocument(); - expect(screen.getByLabelText('Report by date')).toBeInTheDocument(); - expect(screen.getByText('Are dependents included in the orders?')).toBeInTheDocument(); + expect(screen.getByLabelText(/Orders type/)).toBeInTheDocument(); + expect(screen.getByLabelText(/Orders date/)).toBeInTheDocument(); + expect(screen.getByLabelText(/Report by date/)).toBeInTheDocument(); + expect(screen.getByText(/Are dependents included in the orders?/)).toBeInTheDocument(); expect(screen.getByTestId('hasDependentsYes')).toBeInTheDocument(); expect(screen.getByTestId('hasDependentsNo')).toBeInTheDocument(); - expect(screen.getByLabelText('Current duty location')).toBeInTheDocument(); - expect(screen.getByLabelText('New duty location')).toBeInTheDocument(); - expect(screen.getByLabelText('Pay grade')).toBeInTheDocument(); + expect(screen.getByLabelText(/Current duty location/)).toBeInTheDocument(); + expect(screen.getByLabelText(/New duty location/)).toBeInTheDocument(); + expect(screen.getByLabelText(/Pay grade/)).toBeInTheDocument(); }); }); @@ -135,7 +174,7 @@ describe('CreateMoveCustomerInfo Component', () => { , ); - const ordersTypeDropdown = getByLabelText('Orders type'); + const ordersTypeDropdown = getByLabelText(/Orders type/); expect(ordersTypeDropdown).toBeInstanceOf(HTMLSelectElement); await userEvent.selectOptions(ordersTypeDropdown, ORDERS_TYPE.PERMANENT_CHANGE_OF_STATION); @@ -166,12 +205,12 @@ describe('CreateMoveCustomerInfo Component', () => { , ); - await userEvent.click(getByLabelText('Orders type')); - await userEvent.click(getByLabelText('Orders date')); - await userEvent.click(getByLabelText('Report by date')); - await userEvent.click(getByLabelText('Current duty location')); - await userEvent.click(getByLabelText('New duty location')); - await userEvent.click(getByLabelText('Pay grade')); + await userEvent.click(getByLabelText(/Orders type/)); + await userEvent.click(getByLabelText(/Orders date/)); + await userEvent.click(getByLabelText(/Report by date/)); + await userEvent.click(getByLabelText(/Current duty location/)); + await userEvent.click(getByLabelText(/New duty location/)); + await userEvent.click(getByLabelText(/Pay grade/)); const submitBtn = getByRole('button', { name: 'Next' }); await userEvent.click(submitBtn); @@ -189,9 +228,10 @@ 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( - + , ); @@ -202,25 +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/)); - // 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); + const counselingOfficeLabel = await screen.queryByText(/Counseling office/); + expect(counselingOfficeLabel).toBeFalsy(); - await userEvent.click(screen.getByTestId('hasDependentsYes')); + await userEvent.type(screen.getByLabelText(/New duty location/), 'AFB'); + await userEvent.click(await screen.findByText(/Luke/)); - // should now see the OCONUS inputs + await userEvent.click(screen.getByTestId('hasDependentsYes')); await userEvent.click(screen.getByTestId('isAnAccompaniedTourYes')); await userEvent.type(screen.getByTestId('dependentsUnderTwelve'), '2'); await userEvent.type(screen.getByTestId('dependentsTwelveAndOver'), '1'); const nextBtn = screen.getByRole('button', { name: 'Next' }); - expect(nextBtn).not.toBeDisabled(); await userEvent.click(nextBtn); await waitFor(() => { @@ -239,7 +275,7 @@ describe('AddOrdersForm - Student Travel, Early Return of Dependents Test', () = , ); - await userEvent.selectOptions(screen.getByLabelText('Orders type'), ORDERS_TYPE.STUDENT_TRAVEL); + await userEvent.selectOptions(screen.getByLabelText(/Orders type/), ORDERS_TYPE.STUDENT_TRAVEL); const hasDependentsYes = screen.getByLabelText('Yes'); const hasDependentsNo = screen.getByLabelText('No'); @@ -260,7 +296,7 @@ describe('AddOrdersForm - Student Travel, Early Return of Dependents Test', () = , ); - await userEvent.selectOptions(screen.getByLabelText('Orders type'), ORDERS_TYPE.EARLY_RETURN_OF_DEPENDENTS); + await userEvent.selectOptions(screen.getByLabelText(/Orders type/), ORDERS_TYPE.EARLY_RETURN_OF_DEPENDENTS); const hasDependentsYes = screen.getByLabelText('Yes'); const hasDependentsNo = screen.getByLabelText('No'); @@ -280,7 +316,7 @@ describe('AddOrdersForm - Student Travel, Early Return of Dependents Test', () = , ); - await userEvent.selectOptions(screen.getByLabelText('Orders type'), ORDERS_TYPE.PERMANENT_CHANGE_OF_STATION); + await userEvent.selectOptions(screen.getByLabelText(/Orders type/), ORDERS_TYPE.PERMANENT_CHANGE_OF_STATION); const hasDependentsYesPermChg = screen.getByLabelText('Yes'); const hasDependentsNoPermChg = screen.getByLabelText('No'); @@ -293,7 +329,7 @@ describe('AddOrdersForm - Student Travel, Early Return of Dependents Test', () = }); // set order type to value that disables and defaults "has dependents" - await userEvent.selectOptions(screen.getByLabelText('Orders type'), ORDERS_TYPE.STUDENT_TRAVEL); + await userEvent.selectOptions(screen.getByLabelText(/Orders type/), ORDERS_TYPE.STUDENT_TRAVEL); const hasDependentsYesStudent = screen.getByLabelText('Yes'); const hasDependentsNoStudent = screen.getByLabelText('No'); @@ -305,7 +341,7 @@ describe('AddOrdersForm - Student Travel, Early Return of Dependents Test', () = }); // set order type to value the re-enables "has dependents" - await userEvent.selectOptions(screen.getByLabelText('Orders type'), ORDERS_TYPE.LOCAL_MOVE); + await userEvent.selectOptions(screen.getByLabelText(/Orders type/), ORDERS_TYPE.LOCAL_MOVE); const hasDependentsYesLocalMove = screen.getByLabelText('Yes'); const hasDependentsNoLocalMove = screen.getByLabelText('No'); @@ -327,7 +363,7 @@ describe('AddOrdersForm - Student Travel, Early Return of Dependents Test', () = , ); - await userEvent.selectOptions(screen.getByLabelText('Orders type'), ORDERS_TYPE.PERMANENT_CHANGE_OF_STATION); + await userEvent.selectOptions(screen.getByLabelText(/Orders type/), ORDERS_TYPE.PERMANENT_CHANGE_OF_STATION); const hasDependentsYesPermChg = screen.getByLabelText('Yes'); const hasDependentsNoPermChg = screen.getByLabelText('No'); @@ -340,7 +376,7 @@ describe('AddOrdersForm - Student Travel, Early Return of Dependents Test', () = }); // set order type to value that disables and defaults "has dependents" - await userEvent.selectOptions(screen.getByLabelText('Orders type'), ORDERS_TYPE.EARLY_RETURN_OF_DEPENDENTS); + await userEvent.selectOptions(screen.getByLabelText(/Orders type/), ORDERS_TYPE.EARLY_RETURN_OF_DEPENDENTS); const hasDependentsYesEarly = screen.getByLabelText('Yes'); const hasDependentsNoEarly = screen.getByLabelText('No'); @@ -352,7 +388,7 @@ describe('AddOrdersForm - Student Travel, Early Return of Dependents Test', () = }); // set order type to value the re-enables "has dependents" - await userEvent.selectOptions(screen.getByLabelText('Orders type'), ORDERS_TYPE.LOCAL_MOVE); + await userEvent.selectOptions(screen.getByLabelText(/Orders type/), ORDERS_TYPE.LOCAL_MOVE); const hasDependentsYesLocalMove = screen.getByLabelText('Yes'); const hasDependentsNoLocalMove = screen.getByLabelText('No'); @@ -386,3 +422,70 @@ describe('AddOrdersForm - Edge Cases and Additional Scenarios', () => { expect(screen.getByLabelText(/Orders type/)).toBeDisabled(); }); }); + +describe('AddOrdersForm - With Counseling Office', () => { + it('displays the counseling office dropdown', async () => { + isBooleanFlagEnabled.mockImplementation(() => Promise.resolve(true)); + render( + + + , + ); + + await userEvent.selectOptions(await screen.findByLabelText(/Orders type/), 'PERMANENT_CHANGE_OF_STATION'); + 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']); + + // Test Current Duty Location Search Box interaction + await userEvent.type(screen.getByLabelText(/Current duty location/), 'AFB', { delay: 100 }); + const selectedOptionCurrent = await screen.findByText(/Scott/); + await userEvent.click(selectedOptionCurrent); + + // Test New Duty Location Search Box interaction + await userEvent.type(screen.getByLabelText(/New duty location/), 'AFB', { delay: 100 }); + const selectedOptionNew = await screen.findByText(/Luke/); + await userEvent.click(selectedOptionNew); + + const counselingOfficeLabel = await screen.queryByText(/Counseling office/); + expect(counselingOfficeLabel).toBeTruthy(); + + await userEvent.selectOptions(screen.getByLabelText(/Counseling office/), ['Albuquerque AFB']); + + const nextBtn = screen.getByRole('button', { name: 'Next' }); + expect(nextBtn.getAttribute('disabled')).toBeFalsy(); + }); + + it('disabled submit if counseling office is required and blank', async () => { + isBooleanFlagEnabled.mockImplementation(() => Promise.resolve(true)); + render( + + + , + ); + + await userEvent.selectOptions(await screen.findByLabelText(/Orders type/), 'PERMANENT_CHANGE_OF_STATION'); + await userEvent.type(screen.getByLabelText(/Orders date/), '08 Nov 2024'); + await userEvent.type(screen.getByLabelText(/Report by date/), '26 Nov 2024'); + + // Test Current Duty Location Search Box interaction + await userEvent.type(screen.getByLabelText(/Current duty location/), 'AFB', { delay: 100 }); + const selectedOptionCurrent = await screen.findByText(/Scott/); + await userEvent.click(selectedOptionCurrent); + + // Test New Duty Location Search Box interaction + await userEvent.type(screen.getByLabelText(/New duty location/), 'AFB', { delay: 100 }); + const selectedOptionNew = await screen.findByText(/Luke/); + await userEvent.click(selectedOptionNew); + + const counselingOfficeLabel = await screen.queryByText(/Counseling office/); + expect(counselingOfficeLabel).toBeTruthy(); // If the field is visible then it it required + + await userEvent.selectOptions(screen.getByLabelText(/Pay grade/), ['E_5']); + await userEvent.click(screen.getByLabelText('No')); + + const nextBtn = await screen.getByRole('button', { name: 'Next' }, { delay: 100 }); + expect(nextBtn).toBeDisabled(); + }); +}); diff --git a/src/components/Office/AllowancesDetailForm/AllowancesDetailForm.jsx b/src/components/Office/AllowancesDetailForm/AllowancesDetailForm.jsx index 94d8bcc2dc3..32280ffa497 100644 --- a/src/components/Office/AllowancesDetailForm/AllowancesDetailForm.jsx +++ b/src/components/Office/AllowancesDetailForm/AllowancesDetailForm.jsx @@ -1,5 +1,6 @@ import React, { useState, useEffect } from 'react'; import PropTypes from 'prop-types'; +import { useFormikContext } from 'formik'; import { isBooleanFlagEnabled } from '../../../utils/featureFlags'; import { FEATURE_FLAG_KEYS } from '../../../shared/constants'; @@ -20,6 +21,8 @@ const AllowancesDetailForm = ({ header, entitlements, branchOptions, formIsDisab entitlements?.dependentsTwelveAndOver || entitlements?.dependentsUnderTwelve ); + const { setFieldValue } = useFormikContext(); + const [isAdminWeightLocationChecked, setIsAdminWeightLocationChecked] = useState(entitlements?.weightRestriction > 0); useEffect(() => { // Functional component version of "componentDidMount" // By leaving the dependency array empty this will only run once @@ -32,6 +35,25 @@ const AllowancesDetailForm = ({ header, entitlements, branchOptions, formIsDisab checkUBFeatureFlag(); }, []); + useEffect(() => { + if (!isAdminWeightLocationChecked) { + // Find the weight restriction input and reset its value to 0 + const weightRestrictionInput = document.getElementById('weightRestrictionId'); + if (weightRestrictionInput) { + weightRestrictionInput.value = '0'; + } + } + }, [isAdminWeightLocationChecked]); + + const handleAdminWeightLocationChange = (e) => { + const isChecked = e.target.checked; + setIsAdminWeightLocationChecked(isChecked); + + if (!isChecked) { + setFieldValue('weightRestriction', '0'); + } + }; + return (
{header &&

{header}

} @@ -146,6 +168,10 @@ const AllowancesDetailForm = ({ header, entitlements, branchOptions, formIsDisab lazy={false} // immediate masking evaluation isDisabled={formIsDisabled} /> +
+
Standard weight allowance
+
{formatWeight(entitlements.totalWeight)}
+
-
-
Weight allowance
-
{formatWeight(entitlements.totalWeight)}
-
+
+ +
+ {isAdminWeightLocationChecked && ( + + )}
{ expect(screen.queryByLabelText(/Number of dependents of the age 12 or over/)).toBeInTheDocument(); }); }); +describe('AllowancesDetailForm additional tests', () => { + it('renders gun safe checkbox field', async () => { + render( + + + , + ); + + expect(await screen.findByTestId('gunSafeInput')).toBeInTheDocument(); + }); + + it('renders admin weight location section with conditional weight restriction field', 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 () => { + render( + + + , + ); + + expect(await screen.findByTestId('weightAllowance')).toHaveTextContent('11,000'); + }); +}); diff --git a/src/components/Office/DefinitionLists/AllowancesList.jsx b/src/components/Office/DefinitionLists/AllowancesList.jsx index 3d5c1e850cc..7bdd17862ae 100644 --- a/src/components/Office/DefinitionLists/AllowancesList.jsx +++ b/src/components/Office/DefinitionLists/AllowancesList.jsx @@ -16,7 +16,6 @@ const AllowancesList = ({ info, showVisualCues }) => { const visualCuesStyle = classNames(descriptionListStyles.row, { [`${descriptionListStyles.rowWithVisualCue}`]: showVisualCues, }); - useEffect(() => { const checkUBFeatureFlag = async () => { const enabled = await isBooleanFlagEnabled(FEATURE_FLAG_KEYS.UNACCOMPANIED_BAGGAGE); @@ -35,7 +34,7 @@ const AllowancesList = ({ info, showVisualCues }) => {
{info.branch ? ORDERS_BRANCH_OPTIONS[info.branch] : ''}
-
Weight allowance
+
Standard weight allowance
{formatWeight(info.totalWeight)}
@@ -104,11 +103,21 @@ const AllowancesList = ({ info, showVisualCues }) => {
Gun Safe
{info.gunSafe ? 'Authorized' : 'Unauthorized'}
+
+
Admin Weight Restricted Location
+
{info.weightRestriction > 0 ? 'Yes' : 'No'}
+
+ +
+
Weight Restriction
+
+ {info.weightRestriction ? formatWeight(info.weightRestriction) : DEFAULT_EMPTY_VALUE} +
+
); }; - AllowancesList.propTypes = { info: PropTypes.shape({ branch: PropTypes.string, diff --git a/src/components/Office/DefinitionLists/AllowancesList.test.jsx b/src/components/Office/DefinitionLists/AllowancesList.test.jsx index 45c2e97f246..9eed73f1d62 100644 --- a/src/components/Office/DefinitionLists/AllowancesList.test.jsx +++ b/src/components/Office/DefinitionLists/AllowancesList.test.jsx @@ -19,6 +19,7 @@ const info = { requiredMedicalEquipmentWeight: 1000, organizationalClothingAndIndividualEquipment: true, ubAllowance: 400, + weightRestriction: 1500, }; const initialValuesOconusAdditions = { @@ -175,4 +176,9 @@ describe('AllowancesList', () => { expect(screen.getByTestId('unaccompaniedBaggageAllowance')).toBeInTheDocument(); expect(screen.getByTestId('unaccompaniedBaggageAllowance').textContent).toEqual('400 lbs'); }); + it('renders weight restriction', () => { + const adminRestrictedWtLoc = { ...info, adminRestrictedWeightLocation: true }; + render(); + expect(screen.getByTestId('weightRestriction').textContent).toEqual('1,500 lbs'); + }); }); diff --git a/src/components/Office/DefinitionLists/NTSRShipmentInfoList.jsx b/src/components/Office/DefinitionLists/NTSRShipmentInfoList.jsx index ffecea1983c..a34176d50ff 100644 --- a/src/components/Office/DefinitionLists/NTSRShipmentInfoList.jsx +++ b/src/components/Office/DefinitionLists/NTSRShipmentInfoList.jsx @@ -127,7 +127,8 @@ const NTSRShipmentInfoList = ({
Requested pickup date
- {(requestedPickupDate && formatDate(requestedPickupDate, 'DD MMM YYYY')) || '—'} + {(requestedPickupDate && formatDate(requestedPickupDate, 'DD MMM YYYY')) || + getMissingOrDash('requestedPickupDate')}
diff --git a/src/components/Office/DefinitionLists/NTSRShipmentInfoList.test.jsx b/src/components/Office/DefinitionLists/NTSRShipmentInfoList.test.jsx index 3e0ad044c21..dae64c05759 100644 --- a/src/components/Office/DefinitionLists/NTSRShipmentInfoList.test.jsx +++ b/src/components/Office/DefinitionLists/NTSRShipmentInfoList.test.jsx @@ -14,14 +14,14 @@ jest.mock('utils/featureFlags', () => ({ })); const showWhenCollapsed = ['counselorRemarks']; -const warnIfMissing = [ +const warnIfMissing = [{ fieldName: 'counselorRemarks' }, { fieldName: 'sacType' }]; +const errorIfMissing = [ + { fieldName: 'storageFacility' }, { fieldName: 'ntsRecordedWeight' }, { fieldName: 'serviceOrderNumber' }, - { fieldName: 'counselorRemarks' }, + { fieldName: 'requestedPickupDate' }, { fieldName: 'tacType' }, - { fieldName: 'sacType' }, ]; -const errorIfMissing = [{ fieldName: 'storageFacility' }]; const shipment = { ntsRecordedWeight: 2000, @@ -156,7 +156,7 @@ describe('NTSR Shipment Info', () => { }); describe('NTSR Shipment Info List renders missing non-required items correctly', () => { - it.each(['counselorRemarks', 'tacType', 'sacType', 'ntsRecordedWeight', 'serviceOrderNumber'])( + it.each(['counselorRemarks', 'sacType'])( 'Verify Shipment field %s displays "—" with a warning class', async (shipmentField) => { render( @@ -180,26 +180,32 @@ describe('NTSR Shipment Info', () => { }); describe('NTSR Shipment Info List renders missing required items correctly', () => { - it.each(['storageFacilityName', 'storageFacilityAddress'])( - 'Verify Shipment field %s displays "Missing" with an error class', - async (shipmentField) => { - render( + it.each([ + 'storageFacilityName', + 'storageFacilityAddress', + 'ntsRecordedWeight', + 'serviceOrderNumber', + 'requestedPickupDate', + 'tacType', + ])('Verify Shipment field %s displays "Missing" with an error class', async (shipmentField) => { + render( + , - ); - const shipmentFieldElement = screen.getByTestId(shipmentField); - expect(shipmentFieldElement).toHaveTextContent('Missing'); - expect(shipmentFieldElement.parentElement).toHaveClass('missingInfoError'); - }, - ); + /> + , + ); + const shipmentFieldElement = screen.getByTestId(shipmentField); + expect(shipmentFieldElement).toHaveTextContent('Missing'); + expect(shipmentFieldElement.parentElement).toHaveClass('missingInfoError'); + }); }); describe('NTSR Shipment Info List collapsed view', () => { diff --git a/src/components/Office/DefinitionLists/OrdersList.jsx b/src/components/Office/DefinitionLists/OrdersList.jsx index 46ec027d40e..27de47eb1fc 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 : '—'}
( NTStac: text('ordersInfo.NTStac', '9999'), payGrade: text('ordersInfo.payGrade', 'E_5'), }} + moveInfo={{ + name: 'PPPO Los Angeles SFB - USAF', + }} />
); @@ -81,6 +84,9 @@ export const AsServiceCounselor = () => ( NTStac: '', payGrade: text('ordersInfo.payGrade', 'E_5'), }} + moveInfo={{ + name: 'PPPO Los Angeles SFB - USAF', + }} />
); @@ -105,6 +111,9 @@ export const AsServiceCounselorProcessingRetirement = () => ( NTStac: '', payGrade: text('ordersInfo.payGrade', 'E_5'), }} + moveInfo={{ + name: 'PPPO Los Angeles SFB - USAF', + }} />
); @@ -129,6 +138,9 @@ export const AsServiceCounselorProcessingSeparation = () => ( NTStac: '', payGrade: text('ordersInfo.payGrade', 'E_5'), }} + moveInfo={{ + name: 'PPPO Los Angeles SFB - USAF', + }} />
); @@ -166,6 +178,9 @@ export const AsTOO = () => ( NTStac: '', payGrade: text('ordersInfo.payGrade', 'E_5'), }} + moveInfo={{ + name: 'PPPO Los Angeles SFB - USAF', + }} />
); @@ -212,6 +227,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..107463b7a1c 100644 --- a/src/components/Office/DefinitionLists/OrdersList.test.jsx +++ b/src/components/Office/DefinitionLists/OrdersList.test.jsx @@ -32,9 +32,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,7 +72,7 @@ const ordersInfoMissing = { describe('OrdersList', () => { it('renders formatted orders info', () => { - render(); + render(); Object.keys(expectedRenderedOrdersInfo).forEach((key) => { expect(screen.getByText(expectedRenderedOrdersInfo[key])).toBeInTheDocument(); }); 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/ExpandableServiceItemRow/ExpandableServiceItemRow.jsx b/src/components/Office/ExpandableServiceItemRow/ExpandableServiceItemRow.jsx index 585ef6b44d6..e6cbba78392 100644 --- a/src/components/Office/ExpandableServiceItemRow/ExpandableServiceItemRow.jsx +++ b/src/components/Office/ExpandableServiceItemRow/ExpandableServiceItemRow.jsx @@ -6,7 +6,7 @@ import classnames from 'classnames'; import styles from './ExpandableServiceItemRow.module.scss'; import { PAYMENT_SERVICE_ITEM_STATUS } from 'shared/constants'; -import { allowedServiceItemCalculations } from 'constants/serviceItems'; +import { allowedServiceItemCalculations, SERVICE_ITEM_CODES } from 'constants/serviceItems'; import { PaymentServiceItemShape } from 'types'; import { MTOServiceItemShape } from 'types/order'; import { toDollarString, formatCents, formatDollarFromMillicents } from 'utils/formatters'; @@ -25,7 +25,8 @@ const ExpandableServiceItemRow = ({ return canShowExpandableContent && (paymentIsDeprecated || item.status !== PAYMENT_SERVICE_ITEM_STATUS.REQUESTED); }; const canShowExpandableContent = - !disableExpansion && allowedServiceItemCalculations.includes(serviceItem.mtoServiceItemCode); + !disableExpansion && + (allowedServiceItemCalculations.includes(serviceItem.mtoServiceItemCode) || serviceItem.rejectionReason); const handleExpandClick = () => { setIsExpanded((prev) => !prev); @@ -43,6 +44,11 @@ const ExpandableServiceItemRow = ({ [styles.expandedDetail]: isExpanded, }); + const colSpan = + serviceItem.mtoServiceItemCode === SERVICE_ITEM_CODES.MS || serviceItem.mtoServiceItemCode === SERVICE_ITEM_CODES.CS + ? 4 + : 2; + return ( <> {isExpanded && ( - - - + {Object.keys(additionalServiceItemData).length > 0 && ( + + + + )} + {serviceItem.rejectionReason && ( + +
+ +

Rejection Reason

+
+ {serviceItem.rejectionReason} +
+ + )} )} diff --git a/src/components/Office/ExpandableServiceItemRow/ExpandableServiceItemRow.module.scss b/src/components/Office/ExpandableServiceItemRow/ExpandableServiceItemRow.module.scss index 49a76c0f3b9..c6ce0d63ff3 100644 --- a/src/components/Office/ExpandableServiceItemRow/ExpandableServiceItemRow.module.scss +++ b/src/components/Office/ExpandableServiceItemRow/ExpandableServiceItemRow.module.scss @@ -3,6 +3,7 @@ @import 'shared/styles/colors'; .ExpandableServiceItemRow { + background-color: $bg-gray; .accepted { svg { @include u-margin-right(1); @@ -72,4 +73,56 @@ &.expandedDetail td { padding: 0; } + + .rejectionReasonCol { + @include u-padding(2); + border: 1px solid $base-lighter; + border-radius: 3px; + background-color: $bg-gray; + } + + .title { + font-weight: bold; + margin-top: 0; + @include u-margin-bottom(2); + @include u-padding-left(1); + @include u-padding-top(2); + } + + .reasonText { + font-weight: normal; + } + + td[colspan='2']:nth-of-type(2).rejectionReasonTd { + vertical-align: top; + text-align: start; + word-break: break-word; + padding-right: 2rem; + padding-left: 1rem; + border-left: 1px solid #dfe1e2; + background-color: $bg-gray; + } + + .rejectionReasonContainer { + display: flex; + flex-direction: row; + flex-wrap: wrap; + align-items: center; + padding-left: 1rem; + padding-bottom: 1rem; + padding-right: 1rem; + background-color: $bg-gray; + + svg { + path { + fill: $error; + } + height: 1.4rem; + } + } + + .break { + flex-basis: 100%; + height: 0; + } } diff --git a/src/components/Office/ExpandableServiceItemRow/ExpandableServiceItemRow.stories.jsx b/src/components/Office/ExpandableServiceItemRow/ExpandableServiceItemRow.stories.jsx new file mode 100644 index 00000000000..4245c20706c --- /dev/null +++ b/src/components/Office/ExpandableServiceItemRow/ExpandableServiceItemRow.stories.jsx @@ -0,0 +1,235 @@ +import React from 'react'; +import { GridContainer } from '@trussworks/react-uswds'; + +import ExpandableServiceItemRow from './ExpandableServiceItemRow'; +import '../ServiceItemCalculations/ServiceItemCalculations.module.scss'; + +export default { + title: 'Office Components/ExpandableServiceItemRow', + decorators: [ + (Story) => { + return ( +
+ + + + + + + + + + + + + + + + + +
Service itemAmountStatus
+
+
+ ); + }, + ], +}; + +const serviceItemRejected = { + createdAt: '2025-01-09T22:08:38.788Z', + eTag: 'MjAyNS0wMS0xN1QxNTowODo0Mi44MDI4MDZa', + id: '46e2df6f-4fe9-47ee-9baa-b9de28251da8', + mtoServiceItemCode: 'DLH', + mtoServiceItemID: '526f705d-dba1-4bae-bf9a-e97cd1931bd4', + mtoServiceItemName: 'Domestic linehaul', + mtoShipmentID: 'ad5c56af-9e32-41bf-8283-a6a52938cc6a', + mtoShipmentType: 'HHG', + paymentServiceItemParams: [ + { + eTag: 'MjAyNS0wMS0wOVQyMjowODozOC44NDQ5Nlo=', + id: 'd3fba800-cc16-45e3-975d-3236884fbf8a', + key: 'WeightOriginal', + origin: 'PRIME', + paymentServiceItemID: '46e2df6f-4fe9-47ee-9baa-b9de28251da8', + type: 'INTEGER', + value: '2000', + }, + { + eTag: 'MjAyNS0wMS0wOVQyMjowODozOC44NDAyNjFa', + id: '19192fe0-3e0b-4d5d-98dd-ea834fe9062f', + key: 'ActualPickupDate', + origin: 'PRIME', + paymentServiceItemID: '46e2df6f-4fe9-47ee-9baa-b9de28251da8', + type: 'DATE', + value: '2025-01-09', + }, + { + eTag: 'MjAyNS0wMS0wOVQyMjowODozOC44MjcwNTZa', + id: 'e249d609-96fa-4533-90dc-12d3164aed41', + key: 'RequestedPickupDate', + origin: 'PRIME', + paymentServiceItemID: '46e2df6f-4fe9-47ee-9baa-b9de28251da8', + type: 'DATE', + value: '2025-01-02', + }, + { + eTag: 'MjAyNS0wMS0wOVQyMjowODozOC45NDc5NjVa', + id: '75fc3b3b-517d-4383-9d3e-1493bcd564d9', + key: 'DistanceZip', + origin: 'SYSTEM', + paymentServiceItemID: '46e2df6f-4fe9-47ee-9baa-b9de28251da8', + type: 'INTEGER', + value: '1540', + }, + { + eTag: 'MjAyNS0wMS0wOVQyMjowODozOC45NTk0MTZa', + id: '15edbab6-ba00-47e2-bc92-49c7b16f57e1', + key: 'ContractYearName', + origin: 'PRICER', + paymentServiceItemID: '46e2df6f-4fe9-47ee-9baa-b9de28251da8', + type: 'STRING', + value: 'Award Term 1', + }, + { + eTag: 'MjAyNS0wMS0wOVQyMjowODozOC44NDIwMVo=', + id: '984fb8ea-da8a-4b3a-8560-85a9da1589ab', + key: 'ZipDestAddress', + origin: 'PRIME', + paymentServiceItemID: '46e2df6f-4fe9-47ee-9baa-b9de28251da8', + type: 'STRING', + value: '85309', + }, + { + eTag: 'MjAyNS0wMS0wOVQyMjowODozOC44NDk1MzNa', + id: '6d8b063b-8af4-4d6a-9ad4-5880a2d5fbea', + key: 'ReferenceDate', + origin: 'SYSTEM', + paymentServiceItemID: '46e2df6f-4fe9-47ee-9baa-b9de28251da8', + type: 'DATE', + value: '2025-01-02', + }, + { + eTag: 'MjAyNS0wMS0wOVQyMjowODozOC44MzY0MjJa', + id: '7941f3cf-7bcd-495e-9291-f2965291676c', + key: 'ServiceAreaOrigin', + origin: 'SYSTEM', + paymentServiceItemID: '46e2df6f-4fe9-47ee-9baa-b9de28251da8', + type: 'STRING', + value: '456', + }, + { + eTag: 'MjAyNS0wMS0wOVQyMjowODozOC45NjExODda', + id: '9a69db91-2d5d-4446-b1f6-0c15a140cf7a', + key: 'EscalationCompounded', + origin: 'PRICER', + paymentServiceItemID: '46e2df6f-4fe9-47ee-9baa-b9de28251da8', + type: 'DECIMAL', + value: '1.10701', + }, + { + eTag: 'MjAyNS0wMS0wOVQyMjowODozOC45NjI5ODla', + id: '35ddc999-a546-4076-9a2e-5295e4ae4279', + key: 'IsPeak', + origin: 'PRICER', + paymentServiceItemID: '46e2df6f-4fe9-47ee-9baa-b9de28251da8', + type: 'BOOLEAN', + value: 'false', + }, + { + eTag: 'MjAyNS0wMS0wOVQyMjowODozOC45NjQ3ODNa', + id: 'd35e3639-6a43-4f6a-8bf0-5a82685b80c4', + key: 'PriceRateOrFactor', + origin: 'PRICER', + paymentServiceItemID: '46e2df6f-4fe9-47ee-9baa-b9de28251da8', + type: 'DECIMAL', + value: '3.148', + }, + { + eTag: 'MjAyNS0wMS0wOVQyMjowODozOC44Mzg0MTRa', + id: '163babcf-7874-474e-a36d-f602fdca5c88', + key: 'ContractCode', + origin: 'SYSTEM', + paymentServiceItemID: '46e2df6f-4fe9-47ee-9baa-b9de28251da8', + type: 'STRING', + value: 'TRUSS_TEST', + }, + { + eTag: 'MjAyNS0wMS0wOVQyMjowODozOC44Mjk2OTFa', + id: '3f677c1e-1302-4cc1-9333-6fa34f5bdab5', + key: 'WeightEstimated', + origin: 'PRIME', + paymentServiceItemID: '46e2df6f-4fe9-47ee-9baa-b9de28251da8', + type: 'INTEGER', + value: '1500', + }, + { + eTag: 'MjAyNS0wMS0wOVQyMjowODozOC44NDc2NDNa', + id: '1009cca9-cdc1-4814-97e9-e0ef82dce965', + key: 'WeightBilled', + origin: 'SYSTEM', + paymentServiceItemID: '46e2df6f-4fe9-47ee-9baa-b9de28251da8', + type: 'INTEGER', + value: '1650', + }, + { + eTag: 'MjAyNS0wMS0wOVQyMjowODozOC44MzE2Nzha', + id: '66b571ee-4142-4576-b563-cc3c8ea04bfe', + key: 'ZipPickupAddress', + origin: 'PRIME', + paymentServiceItemID: '46e2df6f-4fe9-47ee-9baa-b9de28251da8', + type: 'STRING', + value: '62225', + }, + ], + priceCents: 8855385, + referenceID: '4131-9325-46e2df6f', + rejectionReason: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', + status: 'DENIED', +}; + +const additionalServiceItemData = { + approvedAt: '2025-01-09T20:24:58.522Z', + convertToCustomerExpense: false, + createdAt: '2025-01-09T20:24:58.621Z', + deletedAt: '0001-01-01', + eTag: 'MjAyNS0wMS0wOVQyMDoyNDo1OC42MjE5NzRa', + id: '526f705d-dba1-4bae-bf9a-e97cd1931bd4', + moveTaskOrderID: 'b02c42d7-bd4f-48ff-a5f8-6e7332fa5d03', + mtoShipmentID: 'ad5c56af-9e32-41bf-8283-a6a52938cc6a', + reServiceCode: 'DLH', + reServiceID: '8d600f25-1def-422d-b159-617c7d59156e', + reServiceName: 'Domestic linehaul', + status: 'APPROVED', + submittedAt: '0001-01-01', + updatedAt: '0001-01-01T00:00:00.000Z', +}; + +export const rejectedServiceItem = () => ( + +); + +const serviceItemAccepted = { ...serviceItemRejected }; +serviceItemAccepted.status = 'APPROVED'; +serviceItemAccepted.rejectionReason = null; +export const acceptedServiceItem = () => ( + +); diff --git a/src/components/Office/ExpandableServiceItemRow/ExpandableServiceItemRow.test.jsx b/src/components/Office/ExpandableServiceItemRow/ExpandableServiceItemRow.test.jsx new file mode 100644 index 00000000000..2bc115f4586 --- /dev/null +++ b/src/components/Office/ExpandableServiceItemRow/ExpandableServiceItemRow.test.jsx @@ -0,0 +1,301 @@ +import React from 'react'; +import { act, render, screen } from '@testing-library/react'; + +import ExpandableServiceItemRow from './ExpandableServiceItemRow'; + +import { SERVICE_ITEM_CODES } from 'constants/serviceItems'; + +const serviceItem = { + createdAt: '2025-01-09T22:08:38.788Z', + eTag: 'MjAyNS0wMS0xN1QxNTowODo0Mi44MDI4MDZa', + id: '46e2df6f-4fe9-47ee-9baa-b9de28251da8', + mtoServiceItemCode: 'DLH', + mtoServiceItemID: '526f705d-dba1-4bae-bf9a-e97cd1931bd4', + mtoServiceItemName: 'Domestic linehaul', + mtoShipmentID: 'ad5c56af-9e32-41bf-8283-a6a52938cc6a', + mtoShipmentType: 'HHG', + paymentServiceItemParams: [ + { + eTag: 'MjAyNS0wMS0wOVQyMjowODozOC44NDQ5Nlo=', + id: 'd3fba800-cc16-45e3-975d-3236884fbf8a', + key: 'WeightOriginal', + origin: 'PRIME', + paymentServiceItemID: '46e2df6f-4fe9-47ee-9baa-b9de28251da8', + type: 'INTEGER', + value: '2000', + }, + { + eTag: 'MjAyNS0wMS0wOVQyMjowODozOC44NDAyNjFa', + id: '19192fe0-3e0b-4d5d-98dd-ea834fe9062f', + key: 'ActualPickupDate', + origin: 'PRIME', + paymentServiceItemID: '46e2df6f-4fe9-47ee-9baa-b9de28251da8', + type: 'DATE', + value: '2025-01-09', + }, + { + eTag: 'MjAyNS0wMS0wOVQyMjowODozOC44MjcwNTZa', + id: 'e249d609-96fa-4533-90dc-12d3164aed41', + key: 'RequestedPickupDate', + origin: 'PRIME', + paymentServiceItemID: '46e2df6f-4fe9-47ee-9baa-b9de28251da8', + type: 'DATE', + value: '2025-01-02', + }, + { + eTag: 'MjAyNS0wMS0wOVQyMjowODozOC45NDc5NjVa', + id: '75fc3b3b-517d-4383-9d3e-1493bcd564d9', + key: 'DistanceZip', + origin: 'SYSTEM', + paymentServiceItemID: '46e2df6f-4fe9-47ee-9baa-b9de28251da8', + type: 'INTEGER', + value: '1540', + }, + { + eTag: 'MjAyNS0wMS0wOVQyMjowODozOC45NTk0MTZa', + id: '15edbab6-ba00-47e2-bc92-49c7b16f57e1', + key: 'ContractYearName', + origin: 'PRICER', + paymentServiceItemID: '46e2df6f-4fe9-47ee-9baa-b9de28251da8', + type: 'STRING', + value: 'Award Term 1', + }, + { + eTag: 'MjAyNS0wMS0wOVQyMjowODozOC44NDIwMVo=', + id: '984fb8ea-da8a-4b3a-8560-85a9da1589ab', + key: 'ZipDestAddress', + origin: 'PRIME', + paymentServiceItemID: '46e2df6f-4fe9-47ee-9baa-b9de28251da8', + type: 'STRING', + value: '85309', + }, + { + eTag: 'MjAyNS0wMS0wOVQyMjowODozOC44NDk1MzNa', + id: '6d8b063b-8af4-4d6a-9ad4-5880a2d5fbea', + key: 'ReferenceDate', + origin: 'SYSTEM', + paymentServiceItemID: '46e2df6f-4fe9-47ee-9baa-b9de28251da8', + type: 'DATE', + value: '2025-01-02', + }, + { + eTag: 'MjAyNS0wMS0wOVQyMjowODozOC44MzY0MjJa', + id: '7941f3cf-7bcd-495e-9291-f2965291676c', + key: 'ServiceAreaOrigin', + origin: 'SYSTEM', + paymentServiceItemID: '46e2df6f-4fe9-47ee-9baa-b9de28251da8', + type: 'STRING', + value: '456', + }, + { + eTag: 'MjAyNS0wMS0wOVQyMjowODozOC45NjExODda', + id: '9a69db91-2d5d-4446-b1f6-0c15a140cf7a', + key: 'EscalationCompounded', + origin: 'PRICER', + paymentServiceItemID: '46e2df6f-4fe9-47ee-9baa-b9de28251da8', + type: 'DECIMAL', + value: '1.10701', + }, + { + eTag: 'MjAyNS0wMS0wOVQyMjowODozOC45NjI5ODla', + id: '35ddc999-a546-4076-9a2e-5295e4ae4279', + key: 'IsPeak', + origin: 'PRICER', + paymentServiceItemID: '46e2df6f-4fe9-47ee-9baa-b9de28251da8', + type: 'BOOLEAN', + value: 'false', + }, + { + eTag: 'MjAyNS0wMS0wOVQyMjowODozOC45NjQ3ODNa', + id: 'd35e3639-6a43-4f6a-8bf0-5a82685b80c4', + key: 'PriceRateOrFactor', + origin: 'PRICER', + paymentServiceItemID: '46e2df6f-4fe9-47ee-9baa-b9de28251da8', + type: 'DECIMAL', + value: '3.148', + }, + { + eTag: 'MjAyNS0wMS0wOVQyMjowODozOC44Mzg0MTRa', + id: '163babcf-7874-474e-a36d-f602fdca5c88', + key: 'ContractCode', + origin: 'SYSTEM', + paymentServiceItemID: '46e2df6f-4fe9-47ee-9baa-b9de28251da8', + type: 'STRING', + value: 'TRUSS_TEST', + }, + { + eTag: 'MjAyNS0wMS0wOVQyMjowODozOC44Mjk2OTFa', + id: '3f677c1e-1302-4cc1-9333-6fa34f5bdab5', + key: 'WeightEstimated', + origin: 'PRIME', + paymentServiceItemID: '46e2df6f-4fe9-47ee-9baa-b9de28251da8', + type: 'INTEGER', + value: '1500', + }, + { + eTag: 'MjAyNS0wMS0wOVQyMjowODozOC44NDc2NDNa', + id: '1009cca9-cdc1-4814-97e9-e0ef82dce965', + key: 'WeightBilled', + origin: 'SYSTEM', + paymentServiceItemID: '46e2df6f-4fe9-47ee-9baa-b9de28251da8', + type: 'INTEGER', + value: '1650', + }, + { + eTag: 'MjAyNS0wMS0wOVQyMjowODozOC44MzE2Nzha', + id: '66b571ee-4142-4576-b563-cc3c8ea04bfe', + key: 'ZipPickupAddress', + origin: 'PRIME', + paymentServiceItemID: '46e2df6f-4fe9-47ee-9baa-b9de28251da8', + type: 'STRING', + value: '62225', + }, + ], + priceCents: 8855385, + referenceID: '4131-9325-46e2df6f', + rejectionReason: 'Rejection reason test text', + status: 'DENIED', +}; + +const additionalServiceItemData = { + approvedAt: '2025-01-09T20:24:58.522Z', + convertToCustomerExpense: false, + createdAt: '2025-01-09T20:24:58.621Z', + deletedAt: '0001-01-01', + eTag: 'MjAyNS0wMS0wOVQyMDoyNDo1OC42MjE5NzRa', + id: '526f705d-dba1-4bae-bf9a-e97cd1931bd4', + moveTaskOrderID: 'b02c42d7-bd4f-48ff-a5f8-6e7332fa5d03', + mtoShipmentID: 'ad5c56af-9e32-41bf-8283-a6a52938cc6a', + reServiceCode: 'DLH', + reServiceID: '8d600f25-1def-422d-b159-617c7d59156e', + reServiceName: 'Domestic linehaul', + status: 'APPROVED', + submittedAt: '0001-01-01', + updatedAt: '0001-01-01T00:00:00.000Z', +}; + +const basicServiceItem = { ...serviceItem }; +basicServiceItem.mtoServiceItemCode = SERVICE_ITEM_CODES.MS; +basicServiceItem.mtoServiceItemName = 'Move Management'; + +describe('Payment service items', () => { + it('Displays rejected service item with rejection reason', async () => { + render( + , + ); + + expect(screen.getByText('Domestic linehaul')).toBeVisible(); + expect(screen.getByText('$88,553.85')).toBeVisible(); + expect(screen.getByText('Rejected')).toBeVisible(); + + // Expand service item row + await act(async () => { + screen.getByText('Domestic linehaul').click(); + }); + + // Test for rejection reason + expect(screen.getByText('Rejection Reason')).toBeVisible(); + expect(screen.getByText('Rejection reason test text')).toBeVisible(); + + // Test for calculations + expect(screen.getByText('Billable weight (cwt)')).toBeVisible(); + expect(screen.getByText('16.5 cwt')).toBeVisible(); + expect(screen.getByText('Original: 2,000 lbs')).toBeVisible(); + expect(screen.getByText('Estimated: 1,500 lbs')).toBeVisible(); + + expect(screen.getByText('Mileage')).toBeVisible(); + expect(screen.getByText('1,540')).toBeVisible(); + expect(screen.getByText('ZIP 62225 to ZIP 85309')).toBeVisible(); + + expect(screen.getByText('Baseline linehaul price')).toBeVisible(); + expect(screen.getByText('3.148')).toBeVisible(); + expect(screen.getByText('Domestic non-peak')).toBeVisible(); + expect(screen.getByText('Origin service area: 456')).toBeVisible(); + expect(screen.getByText('Requested pickup: 02 Jan 2025')).toBeVisible(); + + expect(screen.getByText('Price escalation factor')).toBeVisible(); + expect(screen.getByText('1.10701')).toBeVisible(); + expect(screen.getByText('Base year: Award Term 1')).toBeVisible(); + }); + + it('Displays accepted service item without displaying rejection reason markup', async () => { + serviceItem.status = 'APPROVED'; + serviceItem.rejectionReason = null; + render( + , + ); + + expect(screen.getByText('Domestic linehaul')).toBeVisible(); + expect(screen.getByText('$88,553.85')).toBeVisible(); + expect(screen.getByText('Accepted')).toBeVisible(); + + // Expand service item row + await act(async () => { + screen.getByText('Domestic linehaul').click(); + }); + + // Test for rejection reason + expect(screen.queryByText('Rejection Reason')).toBeNull(); + expect(screen.queryByText('Rejection reason test text')).toBeNull(); + + // Test for calculations + expect(screen.getByText('Billable weight (cwt)')).toBeVisible(); + expect(screen.getByText('16.5 cwt')).toBeVisible(); + expect(screen.getByText('Original: 2,000 lbs')).toBeVisible(); + expect(screen.getByText('Estimated: 1,500 lbs')).toBeVisible(); + + expect(screen.getByText('Mileage')).toBeVisible(); + expect(screen.getByText('1,540')).toBeVisible(); + expect(screen.getByText('ZIP 62225 to ZIP 85309')).toBeVisible(); + + expect(screen.getByText('Baseline linehaul price')).toBeVisible(); + expect(screen.getByText('3.148')).toBeVisible(); + expect(screen.getByText('Domestic non-peak')).toBeVisible(); + expect(screen.getByText('Origin service area: 456')).toBeVisible(); + expect(screen.getByText('Requested pickup: 02 Jan 2025')).toBeVisible(); + + expect(screen.getByText('Price escalation factor')).toBeVisible(); + expect(screen.getByText('1.10701')).toBeVisible(); + expect(screen.getByText('Base year: Award Term 1')).toBeVisible(); + }); + + it('Displays rejected basic service item (Move Management) with rejection reason and no calculations', async () => { + render( + , + ); + + expect(screen.getByText('Move Management')).toBeVisible(); + expect(screen.getByText('$88,553.85')).toBeVisible(); + expect(screen.getByText('Rejected')).toBeVisible(); + + // Expand service item row + await act(async () => { + screen.getByText('Move Management').click(); + }); + + // Test for rejection reason + expect(screen.getByText('Rejection Reason')).toBeVisible(); + expect(screen.getByText('Rejection reason test text')).toBeVisible(); + }); +}); 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 603305cd7c2..b7be816baeb 100644 --- a/src/components/Office/PPM/PPMHeaderSummary/HeaderSection.jsx +++ b/src/components/Office/PPM/PPMHeaderSummary/HeaderSection.jsx @@ -62,6 +62,9 @@ const getSectionMarkup = (sectionInfo, handleEditOnClick, isFetchingItems, updat const isCivilian = grade === 'CIVILIAN_EMPLOYEE'; const renderHaulType = (haulType) => { + if (haulType === '') { + return null; + } return haulType === HAUL_TYPES.LINEHAUL ? 'Linehaul' : 'Shorthaul'; }; // check if the itemName is one of the items recalulated after item edit(updatedItemName). @@ -268,16 +271,18 @@ const getSectionMarkup = (sectionInfo, handleEditOnClick, isFetchingItems, updat case sectionTypes.incentiveFactors: return (
-
- - - {isFetchingItems && isRecalulatedItem('haulPrice') ? ( - - ) : ( - `$${formatCents(sectionInfo.haulPrice)}` - )} - -
+ {sectionInfo.haulPrice > 0 && ( +
+ + + {isFetchingItems && isRecalulatedItem('haulPrice') ? ( + + ) : ( + `$${formatCents(sectionInfo.haulPrice)}` + )} + +
+ )}
@@ -311,6 +316,7 @@ const getSectionMarkup = (sectionInfo, handleEditOnClick, isFetchingItems, updat )}
+
@@ -321,6 +327,7 @@ const getSectionMarkup = (sectionInfo, handleEditOnClick, isFetchingItems, updat )}
+
@@ -331,6 +338,36 @@ const getSectionMarkup = (sectionInfo, handleEditOnClick, isFetchingItems, updat )}
+
+ + + {isFetchingItems && isRecalulatedItem('intlPackPrice') ? ( + + ) : ( + `$${formatCents(sectionInfo.intlPackPrice)}` + )} + +
+
+ + + {isFetchingItems && isRecalulatedItem('intlUnpackPrice') ? ( + + ) : ( + `$${formatCents(sectionInfo.intlUnpackPrice)}` + )} + +
+
+ + + {isFetchingItems && isRecalulatedItem('intlLinehaulPrice') ? ( + + ) : ( + `$${formatCents(sectionInfo.intlLinehaulPrice)}` + )} + +
@@ -351,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/HeaderSection.test.jsx b/src/components/Office/PPM/PPMHeaderSummary/HeaderSection.test.jsx index 7fb05d4da7d..4b173db4bdb 100644 --- a/src/components/Office/PPM/PPMHeaderSummary/HeaderSection.test.jsx +++ b/src/components/Office/PPM/PPMHeaderSummary/HeaderSection.test.jsx @@ -219,16 +219,24 @@ const incentivesAdvanceReceivedZeroProps = { setIsSubmitting: jest.fn(), }; +const HAUL_TYPES = { + SHORTHAUL: 'Shorthaul', + LINEHAUL: 'Linehaul', +}; + const incentiveFactorsProps = { sectionInfo: { type: 'incentiveFactors', - haulType: 'Linehaul', + haulType: HAUL_TYPES.LINEHAUL, haulPrice: 6892668, - haulFSC: -143, + haulFSC: 143, packPrice: 20000, unpackPrice: 10000, dop: 15640, ddp: 34640, + intlPackPrice: 1234, + intlUnpackPrice: 12345, + intlLinehaulPrice: 123456, sitReimbursement: 30000, }, }; @@ -469,7 +477,7 @@ describe('PPMHeaderSummary component', () => { expect(screen.getByText('Linehaul Price')).toBeInTheDocument(); expect(screen.getByTestId('haulPrice')).toHaveTextContent('$68,926.68'); expect(screen.getByText('Linehaul Fuel Rate Adjustment')).toBeInTheDocument(); - expect(screen.getByTestId('haulFSC')).toHaveTextContent('-$1.43'); + expect(screen.getByTestId('haulFSC')).toHaveTextContent('$1.43'); expect(screen.getByText('Packing Charge')).toBeInTheDocument(); expect(screen.getByTestId('packPrice')).toHaveTextContent('$200.00'); expect(screen.getByText('Unpacking Charge')).toBeInTheDocument(); @@ -478,6 +486,12 @@ describe('PPMHeaderSummary component', () => { expect(screen.getByTestId('originPrice')).toHaveTextContent('$156.40'); expect(screen.getByText('Destination Price')).toBeInTheDocument(); expect(screen.getByTestId('destinationPrice')).toHaveTextContent('$346.40'); + expect(screen.getByText('International Packing Charge')).toBeInTheDocument(); + expect(screen.getByTestId('intlPackPrice')).toHaveTextContent('$12.34'); + expect(screen.getByText('International Unpacking Charge')).toBeInTheDocument(); + expect(screen.getByTestId('intlUnpackPrice')).toHaveTextContent('$123.45'); + expect(screen.getByText('International Shipping & Linehaul Charge')).toBeInTheDocument(); + expect(screen.getByTestId('intlLinehaulPrice')).toHaveTextContent('$1,234.56'); expect(screen.getByTestId('sitReimbursement')).toHaveTextContent('$300.00'); }); diff --git a/src/components/Office/PPM/PPMHeaderSummary/PPMHeaderSummary.jsx b/src/components/Office/PPM/PPMHeaderSummary/PPMHeaderSummary.jsx index 955389774cd..a21328b3d24 100644 --- a/src/components/Office/PPM/PPMHeaderSummary/PPMHeaderSummary.jsx +++ b/src/components/Office/PPM/PPMHeaderSummary/PPMHeaderSummary.jsx @@ -36,6 +36,9 @@ const GCCAndIncentiveInfo = ({ ppmShipmentInfo, updatedItemName, setUpdatedItemN dop: ppmCloseout.dop, ddp: ppmCloseout.ddp, sitReimbursement: ppmCloseout.SITReimbursement, + intlPackPrice: ppmCloseout.intlPackPrice, + intlUnpackPrice: ppmCloseout.intlUnpackPrice, + intlLinehaulPrice: ppmCloseout.intlLinehaulPrice, }; return ( @@ -78,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 ( @@ -100,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/PPMHeaderSummary/PPMHeaderSummary.test.jsx b/src/components/Office/PPM/PPMHeaderSummary/PPMHeaderSummary.test.jsx index 89c94406ccc..6d938da22b2 100644 --- a/src/components/Office/PPM/PPMHeaderSummary/PPMHeaderSummary.test.jsx +++ b/src/components/Office/PPM/PPMHeaderSummary/PPMHeaderSummary.test.jsx @@ -167,7 +167,7 @@ const defaultProps = { describe('PPMHeaderSummary component', () => { describe('displays form', () => { - it('renders blank form on load with defaults', async () => { + it('renders default values', async () => { usePPMShipmentDocsQueries.mockReturnValue(useEditShipmentQueriesReturnValue); useEditShipmentQueries.mockReturnValue(useEditShipmentQueriesReturnValue); renderWithProviders(, mockRoutingConfig); 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 ppm?.destinationAddress?.isOconus || ppm?.pickupAddress?.isOconus; + }; + const isInternationalShipment = isEitherAddressOconus(ppmShipmentInfo); + setEstimatedCost(estimatedCost?.sitCost || 0); return (
@@ -36,18 +41,25 @@ export default function SitCostBreakdown({ SIT Information:
-
- - {estimatedCost.paramsFirstDaySIT.serviceAreaOrigin - ? `Origin service area: ${estimatedCost?.paramsFirstDaySIT.serviceAreaOrigin}` - : `Destination service area: ${estimatedCost?.paramsFirstDaySIT.serviceAreaDestination}`} - -
+ {(estimatedCost.paramsFirstDaySIT.serviceAreaOrigin || + estimatedCost.paramsFirstDaySIT.serviceAreaDestination) && ( +
+ + {estimatedCost.paramsFirstDaySIT.serviceAreaOrigin + ? `Origin service area: ${estimatedCost.paramsFirstDaySIT.serviceAreaOrigin}` + : `Destination service area: ${estimatedCost.paramsFirstDaySIT.serviceAreaDestination}`} + +
+ )}
Actual move date: {formatDate(ppmShipmentInfo.actualMoveDate)}
- {estimatedCost.paramsFirstDaySIT.isPeak ? 'Domestic peak' : 'Domestic non-peak'} + + {isInternationalShipment + ? `${estimatedCost.paramsFirstDaySIT.isPeak ? 'International peak' : 'International non-peak'}` + : `${estimatedCost.paramsFirstDaySIT.isPeak ? 'Domestic peak' : 'Domestic non-peak'}`} +
diff --git a/src/components/Office/RequestedShipments/RequestedShipments.test.jsx b/src/components/Office/RequestedShipments/RequestedShipments.test.jsx index 076edae05ec..8fdd77d5f51 100644 --- a/src/components/Office/RequestedShipments/RequestedShipments.test.jsx +++ b/src/components/Office/RequestedShipments/RequestedShipments.test.jsx @@ -667,7 +667,7 @@ describe('RequestedShipments', () => { 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, @@ -675,10 +675,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 69793103aae..1d71878a9bb 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 @@ -275,8 +275,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 814e82d124a..fea91773c2e 100644 --- a/src/components/Office/ServiceItemCalculations/ServiceItemCalculations.jsx +++ b/src/components/Office/ServiceItemCalculations/ServiceItemCalculations.jsx @@ -14,7 +14,6 @@ import { } from 'constants/serviceItems'; const times = ; -const equals = ; const ServiceItemCalculations = ({ itemCode, @@ -37,10 +36,6 @@ const ServiceItemCalculations = ({ return times; } - if (index === length - 1) { - return equals; - } - return null; }; diff --git a/src/components/Office/ServiceItemCalculations/ServiceItemCalculations.module.scss b/src/components/Office/ServiceItemCalculations/ServiceItemCalculations.module.scss index c83658fa704..48e25cd1efb 100644 --- a/src/components/Office/ServiceItemCalculations/ServiceItemCalculations.module.scss +++ b/src/components/Office/ServiceItemCalculations/ServiceItemCalculations.module.scss @@ -59,8 +59,6 @@ .col:last-of-type { @include u-margin-right(0); - @include u-text-align('right'); - hr { width: 100%; } diff --git a/src/components/Office/ServiceItemCalculations/ServiceItemCalculations.test.jsx b/src/components/Office/ServiceItemCalculations/ServiceItemCalculations.test.jsx index 79a3704f074..7d7f1bf62c5 100644 --- a/src/components/Office/ServiceItemCalculations/ServiceItemCalculations.test.jsx +++ b/src/components/Office/ServiceItemCalculations/ServiceItemCalculations.test.jsx @@ -77,10 +77,8 @@ describe('ServiceItemCalculations DLH', () => { it('renders icons', () => { const wrapper = serviceItemCalculationsLarge; const timesIcons = wrapper.find('[icon="times"]'); - const equalsIcons = wrapper.find('[icon="equals"]'); expect(timesIcons.length).toBe(3); - expect(equalsIcons.length).toBe(1); }); }); @@ -96,10 +94,8 @@ describe('ServiceItemCalculations DLH', () => { it('renders no icons', () => { const wrapper = serviceItemCalculationsSmall; const timesIcons = wrapper.find('[icon="times"]'); - const equalsIcons = wrapper.find('[icon="equals"]'); expect(timesIcons.length).toBe(0); - expect(equalsIcons.length).toBe(0); }); }); @@ -162,10 +158,8 @@ describe('ServiceItemCalculations DCRT', () => { it('renders icons', () => { const wrapper = serviceItemCalculationsLarge; const timesIcons = wrapper.find('[icon="times"]'); - const equalsIcons = wrapper.find('[icon="equals"]'); expect(timesIcons.length).toBe(2); - expect(equalsIcons.length).toBe(1); }); }); @@ -181,10 +175,8 @@ describe('ServiceItemCalculations DCRT', () => { it('renders no icons', () => { const wrapper = serviceItemCalculationsSmall; const timesIcons = wrapper.find('[icon="times"]'); - const equalsIcons = wrapper.find('[icon="equals"]'); expect(timesIcons.length).toBe(0); - expect(equalsIcons.length).toBe(0); }); }); @@ -248,10 +240,8 @@ describe('ServiceItemCalculations DUCRT', () => { it('renders icons', () => { const wrapper = serviceItemCalculationsLarge; const timesIcons = wrapper.find('[icon="times"]'); - const equalsIcons = wrapper.find('[icon="equals"]'); expect(timesIcons.length).toBe(2); - expect(equalsIcons.length).toBe(1); }); }); @@ -267,10 +257,8 @@ describe('ServiceItemCalculations DUCRT', () => { it('renders no icons', () => { const wrapper = serviceItemCalculationsSmall; const timesIcons = wrapper.find('[icon="times"]'); - const equalsIcons = wrapper.find('[icon="equals"]'); expect(timesIcons.length).toBe(0); - expect(equalsIcons.length).toBe(0); }); }); diff --git a/src/components/Office/ServiceItemCalculations/helpers.js b/src/components/Office/ServiceItemCalculations/helpers.js index c5d26d73e75..8412e4192d7 100644 --- a/src/components/Office/ServiceItemCalculations/helpers.js +++ b/src/components/Office/ServiceItemCalculations/helpers.js @@ -687,7 +687,7 @@ const uncappedRequestTotal = (params) => { const totalAmountRequested = (totalAmount) => { const value = toDollarString(formatCents(totalAmount)); - const label = `${SERVICE_ITEM_CALCULATION_LABELS.Total}:`; + const label = `${SERVICE_ITEM_CALCULATION_LABELS.Total}: `; const detail = ''; return calculation(value, label, formatDetail(detail)); diff --git a/src/components/Office/ServiceItemDetails/ServiceItemDetails.jsx b/src/components/Office/ServiceItemDetails/ServiceItemDetails.jsx index 689a790ca64..76be9ca5d57 100644 --- a/src/components/Office/ServiceItemDetails/ServiceItemDetails.jsx +++ b/src/components/Office/ServiceItemDetails/ServiceItemDetails.jsx @@ -491,8 +491,11 @@ const ServiceItemDetails = ({ id, code, details, serviceRequestDocs, shipment, s case 'ISLH': case 'IHPK': case 'IHUPK': + case 'IUBPK': + case 'IUBUPK': case 'POEFSC': - case 'PODFSC': { + case 'PODFSC': + case 'UBP': { detailSection = (
diff --git a/src/components/Office/ServiceItemDetails/ServiceItemDetails.test.jsx b/src/components/Office/ServiceItemDetails/ServiceItemDetails.test.jsx index d9267d7e572..fab0ded5351 100644 --- a/src/components/Office/ServiceItemDetails/ServiceItemDetails.test.jsx +++ b/src/components/Office/ServiceItemDetails/ServiceItemDetails.test.jsx @@ -523,44 +523,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/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..96485a69be5 100644 --- a/src/components/Office/ShipmentForm/ShipmentForm.jsx +++ b/src/components/Office/ShipmentForm/ShipmentForm.jsx @@ -891,8 +891,8 @@ const ShipmentForm = (props) => { {isNTSR && } - {isMobileHome && ( - { /> )} - {isBoat && ( -
- {shipmentInfo.marketCode} -

{shipmentInfo.shipmentType}

+

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

{shipmentStatus === shipmentStatuses.CANCELED && canceled} {shipmentInfo.isDiversion && diversion} diff --git a/src/components/PrimeUI/CreateShipmentServiceItemForm/CreateShipmentServiceItemForm.jsx b/src/components/PrimeUI/CreateShipmentServiceItemForm/CreateShipmentServiceItemForm.jsx index 15ccedb7d41..b351755d15b 100644 --- a/src/components/PrimeUI/CreateShipmentServiceItemForm/CreateShipmentServiceItemForm.jsx +++ b/src/components/PrimeUI/CreateShipmentServiceItemForm/CreateShipmentServiceItemForm.jsx @@ -8,6 +8,7 @@ import OriginSITServiceItemForm from './OriginSITServiceItemForm'; import ShuttleSITServiceItemForm from './ShuttleSITServiceItemForm'; import DomesticCratingForm from './DomesticCratingForm'; import InternationalCratingForm from './InternationalCratingForm'; +import InternationalShuttleServiceItemForm from './InternationalShuttleServiceItemForm'; import { isBooleanFlagEnabled } from 'utils/featureFlags'; import { ShipmentShape } from 'types/shipment'; @@ -22,6 +23,7 @@ const CreateShipmentServiceItemForm = ({ shipment, createServiceItemMutation }) MTOServiceItemShuttle, MTOServiceItemDomesticCrating, MTOServiceItemInternationalCrating, + MTOServiceItemInternationalShuttle, } = createServiceItemModelTypes; const [selectedServiceItemType, setSelectedServiceItemType] = useState(MTOServiceItemOriginSIT); const [enableAlaskaFeatureFlag, setEnableAlaskaFeatureFlag] = useState(false); @@ -48,6 +50,7 @@ const CreateShipmentServiceItemForm = ({ shipment, createServiceItemMutation }) + {enableAlaskaFeatureFlag && } @@ -61,6 +64,9 @@ const CreateShipmentServiceItemForm = ({ shipment, createServiceItemMutation }) {selectedServiceItemType === MTOServiceItemShuttle && ( )} + {selectedServiceItemType === MTOServiceItemInternationalShuttle && ( + + )} {selectedServiceItemType === MTOServiceItemDomesticCrating && ( )} diff --git a/src/components/PrimeUI/CreateShipmentServiceItemForm/CreateShipmentServiceItemForm.test.jsx b/src/components/PrimeUI/CreateShipmentServiceItemForm/CreateShipmentServiceItemForm.test.jsx index 55da98f0b2f..f30c0b9f459 100644 --- a/src/components/PrimeUI/CreateShipmentServiceItemForm/CreateShipmentServiceItemForm.test.jsx +++ b/src/components/PrimeUI/CreateShipmentServiceItemForm/CreateShipmentServiceItemForm.test.jsx @@ -91,6 +91,7 @@ describe('CreateShipmentServiceItemForm component', () => { ['shuttleSITServiceItemForm', createServiceItemModelTypes.MTOServiceItemShuttle], ['DomesticCratingForm', createServiceItemModelTypes.MTOServiceItemDomesticCrating], ['InternationalCratingForm', createServiceItemModelTypes.MTOServiceItemInternationalCrating], + ['InternationalShuttleServiceItemForm', createServiceItemModelTypes.MTOServiceItemInternationalShuttle], ])('renders %s after selecting %s type', async (formName, serviceItemType) => { isBooleanFlagEnabled.mockResolvedValue(true); const shipment = approvedMoveTaskOrder.moveTaskOrder.mtoShipments[0]; diff --git a/src/components/PrimeUI/CreateShipmentServiceItemForm/InternationalShuttleServiceItemForm.jsx b/src/components/PrimeUI/CreateShipmentServiceItemForm/InternationalShuttleServiceItemForm.jsx new file mode 100644 index 00000000000..51b69ba6963 --- /dev/null +++ b/src/components/PrimeUI/CreateShipmentServiceItemForm/InternationalShuttleServiceItemForm.jsx @@ -0,0 +1,83 @@ +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 TextField from 'components/form/fields/TextField/TextField'; +import MaskedTextField from 'components/form/fields/MaskedTextField/MaskedTextField'; +import { Form } from 'components/form/Form'; +import { ShipmentShape } from 'types/shipment'; +import { DropdownInput } from 'components/form/fields'; +import { internationalShuttleServiceItemCodeOptions, createServiceItemModelTypes } from 'constants/prime'; + +const internationalShuttleValidationSchema = Yup.object().shape({ + reServiceCode: Yup.string().required('Required'), + reason: Yup.string().required('Required'), +}); + +const InternationalShuttleServiceItemForm = ({ shipment, submission }) => { + const initialValues = { + moveTaskOrderID: shipment.moveTaskOrderID, + mtoShipmentID: shipment.id, + modelType: createServiceItemModelTypes.MTOServiceItemInternationalShuttle, + reason: '', + estimatedWeight: null, + actualWeight: null, + }; + + const onSubmit = (values) => { + const { estimatedWeight, actualWeight, ...otherFields } = values; + const body = { + estimatedWeight: Number.parseInt(estimatedWeight, 10), + actualWeight: Number.parseInt(actualWeight, 10), + ...otherFields, + }; + submission({ body }); + }; + + return ( + +
+ + + + + + +
+ ); +}; + +InternationalShuttleServiceItemForm.propTypes = { + shipment: ShipmentShape.isRequired, + submission: PropTypes.func.isRequired, +}; + +export default InternationalShuttleServiceItemForm; diff --git a/src/components/PrimeUI/CreateShipmentServiceItemForm/InternationalShuttleServiceItemForm.test.jsx b/src/components/PrimeUI/CreateShipmentServiceItemForm/InternationalShuttleServiceItemForm.test.jsx new file mode 100644 index 00000000000..f1fb47038f7 --- /dev/null +++ b/src/components/PrimeUI/CreateShipmentServiceItemForm/InternationalShuttleServiceItemForm.test.jsx @@ -0,0 +1,83 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; + +import InternationalShuttleServiceItemForm from './InternationalShuttleServiceItemForm'; + +const moveId = '9c7b255c-2981-4bf8-839f-61c7458e2b4d'; + +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('InternationalShuttleSITForm component', () => { + it.each([ + ['Service item code', 'reServiceCode'], + ['Reason', 'reason'], + ['Estimated weight (lbs)', 'estimatedWeight'], + ['Actual weight (lbs)', 'actualWeight'], + ])('renders field %s in form', (labelName, inputName) => { + const shipment = approvedMoveTaskOrder.moveTaskOrder.mtoShipments[0]; + + render(); + + // shipment text values + const field = screen.getByText(labelName); + expect(field).toBeInTheDocument(); + expect(field.closest('div').nextElementSibling.name).toBe(inputName); + }); +}); diff --git a/src/components/Table/TableQueue.jsx b/src/components/Table/TableQueue.jsx index d657df5007f..32e13cfc75f 100644 --- a/src/components/Table/TableQueue.jsx +++ b/src/components/Table/TableQueue.jsx @@ -125,6 +125,7 @@ const TableQueue = ({ currentPage, currentPageSize, viewAsGBLOC: selectedGbloc, + activeRole, }); // react-table setup below diff --git a/src/constants/MoveHistory/Database/BooleanFields.js b/src/constants/MoveHistory/Database/BooleanFields.js index 32a4cc5f7c7..c14cb67f56c 100644 --- a/src/constants/MoveHistory/Database/BooleanFields.js +++ b/src/constants/MoveHistory/Database/BooleanFields.js @@ -12,6 +12,7 @@ export default { missing_receipt: 'missing_receipt', organizational_clothing_and_individual_equipment: 'organizational_clothing_and_individual_equipment', gun_safe: 'gun_safe', + admin_restricted_weight_location: 'admin_restricted_weight_location', email_is_preferred: 'email_is_preferred', phone_is_preferred: 'phone_is_preferred', uses_external_vendor: 'uses_external_vendor', diff --git a/src/constants/MoveHistory/Database/FieldMappings.js b/src/constants/MoveHistory/Database/FieldMappings.js index 6ba6b0793d4..972900172c8 100644 --- a/src/constants/MoveHistory/Database/FieldMappings.js +++ b/src/constants/MoveHistory/Database/FieldMappings.js @@ -39,6 +39,8 @@ export default { required_medical_equipment_weight: 'Required medical equipment', organizational_clothing_and_individual_equipment: 'OCIE', gun_safe: 'Gun Safe', + admin_restricted_weight_location: 'Admin restricted weight location', + weight_restriction: 'Weight restriction', requested_pickup_date: 'Requested pickup date', grade: 'Pay grade', shipment_type: 'Shipment type', @@ -148,4 +150,10 @@ export default { approvals_requested_at: 'Approvals requested at', approved_at: 'Approved at', counseling_office_name: 'Counseling office', + assigned_sc: 'Counselor assigned', + assigned_too: 'Task ordering officer assigned', + assigned_tio: 'Task invoicing officer assigned', + re_assigned_sc: 'Counselor reassigned', + re_assigned_too: 'Task ordering officer reassigned', + re_assigned_tio: 'Task invoicing officer reassigned', }; diff --git a/src/constants/MoveHistory/Database/Tables.js b/src/constants/MoveHistory/Database/Tables.js index f860416dda7..921febc1464 100644 --- a/src/constants/MoveHistory/Database/Tables.js +++ b/src/constants/MoveHistory/Database/Tables.js @@ -19,5 +19,6 @@ export default { moving_expenses: 'moving_expenses', progear_weight_tickets: 'progear_weight_tickets', gsr_appeals: 'gsr_appeals', + payment_service_items: 'payment_service_items', shipment_address_updates: 'shipment_address_updates', }; diff --git a/src/constants/MoveHistory/EventTemplates/FinishDocumentReview/FinishDocumentReviewMoves.jsx b/src/constants/MoveHistory/EventTemplates/FinishDocumentReview/FinishDocumentReviewMoves.jsx new file mode 100644 index 00000000000..1764fc61497 --- /dev/null +++ b/src/constants/MoveHistory/EventTemplates/FinishDocumentReview/FinishDocumentReviewMoves.jsx @@ -0,0 +1,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'; + +export default { + action: a.UPDATE, + eventName: o.finishDocumentReview, + tableName: t.moves, + getEventNameDisplay: () => 'Updated move', + getDetails: ({ changedValues }) => ( + <> +
PPM Closeout Complete
+ {changedValues?.sc_assigned_id !== undefined ?
Closeout Counselor Unassigned
: null} + + ), +}; diff --git a/src/constants/MoveHistory/EventTemplates/FinishDocumentReview/FinishDocumentReviewMoves.test.jsx b/src/constants/MoveHistory/EventTemplates/FinishDocumentReview/FinishDocumentReviewMoves.test.jsx new file mode 100644 index 00000000000..967f33d05e2 --- /dev/null +++ b/src/constants/MoveHistory/EventTemplates/FinishDocumentReview/FinishDocumentReviewMoves.test.jsx @@ -0,0 +1,43 @@ +import { screen, render } from '@testing-library/react'; + +import e from 'constants/MoveHistory/EventTemplates/FinishDocumentReview/FinishDocumentReviewMoves'; +import getTemplate from 'constants/MoveHistory/TemplateManager'; + +describe('When given a completed services counseling for a move', () => { + const historyRecord = { + action: 'UPDATE', + eventName: 'finishDocumentReview', + tableName: 'moves', + }; + it('correctly matches the update mto status services counseling completed event to the proper template', () => { + const template = getTemplate(historyRecord); + expect(template).toMatchObject(e); + }); + + it('displays the proper name in the event name display column', () => { + const template = getTemplate(historyRecord); + + render(template.getEventNameDisplay(historyRecord)); + expect(screen.getByText('Updated move')).toBeInTheDocument(); + }); + + it('displays default when SC ID is not present', () => { + const template = getTemplate(historyRecord); + + render(template.getDetails(historyRecord)); + expect(screen.getByText('PPM Closeout Complete')).toBeInTheDocument(); + expect(screen.queryByText('Closeout Counselor Unassigned')).not.toBeInTheDocument(); + }); + + it('displays correct details when a SC is unassigned', () => { + historyRecord.changedValues = { + ...historyRecord.changedValues, + sc_assigned_id: null, + }; + const template = getTemplate(historyRecord); + + render(template.getDetails(historyRecord)); + expect(screen.getByText('PPM Closeout Complete')).toBeInTheDocument(); + expect(screen.getByText('Closeout Counselor Unassigned')).toBeInTheDocument(); + }); +}); diff --git a/src/constants/MoveHistory/EventTemplates/UpdateAssignedOfficeUser/DeleteAssignedOfficeUser.jsx b/src/constants/MoveHistory/EventTemplates/UpdateAssignedOfficeUser/DeleteAssignedOfficeUser.jsx new file mode 100644 index 00000000000..e721b67abd0 --- /dev/null +++ b/src/constants/MoveHistory/EventTemplates/UpdateAssignedOfficeUser/DeleteAssignedOfficeUser.jsx @@ -0,0 +1,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'; + +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; + 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 new file mode 100644 index 00000000000..cf02b1f8fb5 --- /dev/null +++ b/src/constants/MoveHistory/EventTemplates/UpdateAssignedOfficeUser/DeleteAssignedOfficeUser.test.jsx @@ -0,0 +1,50 @@ +import { screen, render } from '@testing-library/react'; + +import e from 'constants/MoveHistory/EventTemplates/UpdateAssignedOfficeUser/DeleteAssignedOfficeUser'; +import getTemplate from 'constants/MoveHistory/TemplateManager'; + +describe('When given a move that has been unassigned', () => { + const historyRecord = { + action: 'UPDATE', + eventName: 'deleteAssignedOfficeUser', + tableName: 'moves', + changedValues: { + sc_assigned_id: null, + }, + }; + + it('correctly matches the template', () => { + const template = getTemplate(historyRecord); + expect(template).toMatchObject(e); + }); + + it('displays the proper name in the event name display column', () => { + const template = getTemplate(historyRecord); + + render(template.getEventNameDisplay(historyRecord)); + expect(screen.getByText('Updated move')).toBeInTheDocument(); + }); + + describe('displays the proper details for', () => { + it('services counselor', () => { + const template = getTemplate(historyRecord); + + render(template.getDetails(historyRecord)); + expect(screen.getByText('Counselor unassigned')).toBeInTheDocument(); + }); + it('task ordering officer', () => { + historyRecord.changedValues = { too_assigned_id: null }; + const template = getTemplate(historyRecord); + + render(template.getDetails(historyRecord)); + expect(screen.getByText('Task ordering officer unassigned')).toBeInTheDocument(); + }); + it('task invoicing officer', () => { + historyRecord.changedValues = { tio_assigned_id: null }; + const template = getTemplate(historyRecord); + + render(template.getDetails(historyRecord)); + expect(screen.getByText('Task invoicing officer unassigned')).toBeInTheDocument(); + }); + }); +}); diff --git a/src/constants/MoveHistory/EventTemplates/UpdateAssignedOfficeUser/UpdateAssignedOfficeUser.jsx b/src/constants/MoveHistory/EventTemplates/UpdateAssignedOfficeUser/UpdateAssignedOfficeUser.jsx new file mode 100644 index 00000000000..16e43bc64b6 --- /dev/null +++ b/src/constants/MoveHistory/EventTemplates/UpdateAssignedOfficeUser/UpdateAssignedOfficeUser.jsx @@ -0,0 +1,23 @@ +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 LabeledDetails from 'pages/Office/MoveHistory/LabeledDetails'; +import { formatAssignedOfficeUserFromContext } from 'utils/formatters'; + +const formatChangedValues = (historyRecord) => { + const newChangedValues = { + ...formatAssignedOfficeUserFromContext(historyRecord), + }; + + return { ...historyRecord, changedValues: newChangedValues }; +}; + +export default { + action: a.UPDATE, + eventName: o.updateAssignedOfficeUser, + tableName: t.moves, + getEventNameDisplay: () => 'Updated move', + getDetails: (historyRecord) => , +}; diff --git a/src/constants/MoveHistory/EventTemplates/UpdateAssignedOfficeUser/UpdateAssignedOfficeUser.test.jsx b/src/constants/MoveHistory/EventTemplates/UpdateAssignedOfficeUser/UpdateAssignedOfficeUser.test.jsx new file mode 100644 index 00000000000..bc1e6aa2d73 --- /dev/null +++ b/src/constants/MoveHistory/EventTemplates/UpdateAssignedOfficeUser/UpdateAssignedOfficeUser.test.jsx @@ -0,0 +1,65 @@ +import { screen, render } from '@testing-library/react'; + +import e from 'constants/MoveHistory/EventTemplates/UpdateAssignedOfficeUser/UpdateAssignedOfficeUser'; +import getTemplate from 'constants/MoveHistory/TemplateManager'; + +describe('When given a move that has been assigned', () => { + const historyRecord = { + action: 'UPDATE', + eventName: 'updateAssignedOfficeUser', + tableName: 'moves', + changedValues: { + sc_assigned_id: 'fb625e3c-067c-49d7-8fd9-88ef040e6137', + }, + oldValues: { + sc_assigned_id: null, + }, + context: [{ assigned_office_user_last_name: 'Daniels', assigned_office_user_first_name: 'Jayden' }], + }; + + it('correctly matches the template', () => { + const template = getTemplate(historyRecord); + expect(template).toMatchObject(e); + }); + + it('displays the proper name in the event name display column', () => { + const template = getTemplate(historyRecord); + + render(template.getEventNameDisplay(historyRecord)); + expect(screen.getByText('Updated move')).toBeInTheDocument(); + }); + + describe('displays the proper details for', () => { + it('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', () => { + historyRecord.changedValues = { too_assigned_id: 'fb625e3c-067c-49d7-8fd9-88ef040e6137' }; + historyRecord.oldValues = { too_assigned_id: null }; + 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 assigned')).toBeInTheDocument(); + expect(screen.getByText(': Robinson, Brian')).toBeInTheDocument(); + }); + it('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' }]; + + const template = getTemplate(historyRecord); + + render(template.getDetails(historyRecord)); + expect(screen.getByText('Task invoicing officer assigned')).toBeInTheDocument(); + expect(screen.getByText(': Luvu, Frankie')).toBeInTheDocument(); + }); + }); +}); diff --git a/src/constants/MoveHistory/EventTemplates/UpdateMTOShipment/UpdateMTOShipmentAgent.module.scss b/src/constants/MoveHistory/EventTemplates/UpdateMTOShipment/UpdateMTOShipmentAgent.module.scss new file mode 100644 index 00000000000..f1d4df13283 --- /dev/null +++ b/src/constants/MoveHistory/EventTemplates/UpdateMTOShipment/UpdateMTOShipmentAgent.module.scss @@ -0,0 +1,7 @@ +@import 'shared/styles/colors'; + +.shipmentType { + display: block; + color: $base; + margin-bottom: 8px 0; +} diff --git a/src/constants/MoveHistory/EventTemplates/UpdateMTOShipment/updateMTOShipmentAgent.jsx b/src/constants/MoveHistory/EventTemplates/UpdateMTOShipment/updateMTOShipmentAgent.jsx index 72a1f7974a1..3789096e2db 100644 --- a/src/constants/MoveHistory/EventTemplates/UpdateMTOShipment/updateMTOShipmentAgent.jsx +++ b/src/constants/MoveHistory/EventTemplates/UpdateMTOShipment/updateMTOShipmentAgent.jsx @@ -1,6 +1,9 @@ import React from 'react'; +import styles from './UpdateMTOShipmentAgent.module.scss'; + import { formatMoveHistoryAgent } from 'utils/formatters'; +import fieldMappings from 'constants/MoveHistory/Database/FieldMappings'; import o from 'constants/MoveHistory/UIDisplay/Operations'; import a from 'constants/MoveHistory/Database/Actions'; import t from 'constants/MoveHistory/Database/Tables'; @@ -26,10 +29,34 @@ const formatChangedValues = (historyRecord) => { return { ...historyRecord, changedValues: newChangedValues }; }; +const formatDeletedAgentRecord = (historyRecord) => { + const mtoShipmentLabel = getMtoShipmentLabel(historyRecord); + const historyLabel = `${mtoShipmentLabel.shipment_type} shipment #${mtoShipmentLabel.shipment_locator}`; + const agentTypeFieldName = historyRecord.oldValues.agent_type.toLowerCase(); + const agentType = fieldMappings[agentTypeFieldName]; + const agent = formatMoveHistoryAgent(historyRecord.oldValues)[agentTypeFieldName]; + + return ( + <> + + Deleted {agentType} on {historyLabel} + +
+ {agentType}: {agent} +
+ + ); +}; + export default { action: a.UPDATE, eventName: o.updateMTOShipment, tableName: t.mto_agents, getEventNameDisplay: () => 'Updated shipment', - getDetails: (historyRecord) => , + getDetails: (historyRecord) => { + if (historyRecord.changedValues.deleted_at) { + return formatDeletedAgentRecord(historyRecord); + } + return ; + }, }; diff --git a/src/constants/MoveHistory/EventTemplates/UpdateMTOShipment/updateMTOShipmentAgent.test.jsx b/src/constants/MoveHistory/EventTemplates/UpdateMTOShipment/updateMTOShipmentAgent.test.jsx index bc2681fd996..c293dcc3c5d 100644 --- a/src/constants/MoveHistory/EventTemplates/UpdateMTOShipment/updateMTOShipmentAgent.test.jsx +++ b/src/constants/MoveHistory/EventTemplates/UpdateMTOShipment/updateMTOShipmentAgent.test.jsx @@ -41,6 +41,46 @@ describe('when given an mto shipment agents update with mto agents table history }, context: [{ shipment_type: 'HHG', shipment_locator: 'RQ38D4-01', shipment_id_abbr: 'a1b2c' }], }, + DELETED_RECEIVING_AGENT: { + action: 'UPDATE', + eventName: 'updateMTOShipment', + tableName: 'mto_agents', + changedValues: { + deleted_at: '2025-01-21T15:39:24.890356+00:00', + email: null, + first_name: null, + last_name: null, + phone: null, + }, + oldValues: { + agent_type: 'RECEIVING_AGENT', + email: 'john.smith@email.com', + first_name: 'John', + last_name: 'Smith', + phone: '555-765-4321', + }, + context: [{ shipment_type: 'HHG', shipment_locator: 'RQ38D4-01', shipment_id_abbr: 'a1b2c' }], + }, + DELETED_RELEASING_AGENT: { + action: 'UPDATE', + eventName: 'updateMTOShipment', + tableName: 'mto_agents', + changedValues: { + deleted_at: '2025-01-21T16:39:24.890356+00:00', + email: null, + first_name: null, + last_name: null, + phone: null, + }, + oldValues: { + agent_type: 'RELEASING_AGENT', + email: 'jane.smith@email.com', + first_name: 'Jane', + last_name: 'Smith', + phone: '555-123-4567', + }, + context: [{ shipment_type: 'NTS', shipment_locator: 'RQ38D4-01', shipment_id_abbr: 'a1b2c' }], + }, }; describe('when agent type is Releasing', () => { @@ -75,7 +115,7 @@ describe('when given an mto shipment agents update with mto agents table history expect(screen.getAllByText('HHG shipment #RQ38D4-01')); }); - it('it displays the proper labeled details for the given releasing agent', () => { + it('it displays the proper labeled details for the given receiving agent', () => { const template = getTemplate(historyRecord.RECEIVE); render(template.getDetails(historyRecord.RECEIVE)); @@ -83,4 +123,22 @@ describe('when given an mto shipment agents update with mto agents table history expect(screen.getByText(': Nancy Drew, 555-555-5555, nancy@email.com')).toBeInTheDocument(); }); }); + + describe('when agent is deleted', () => { + it('displays deleted receiving agent', () => { + const template = getTemplate(historyRecord.DELETED_RECEIVING_AGENT); + render(template.getDetails(historyRecord.DELETED_RECEIVING_AGENT)); + expect(screen.getByText('Deleted Receiving agent on HHG shipment #RQ38D4-01')).toBeInTheDocument(); + expect(screen.getByText('Receiving agent')).toBeInTheDocument(); + expect(screen.getByText(': John Smith, 555-765-4321, john.smith@email.com')).toBeInTheDocument(); + }); + + it('displays deleted releasing agent', () => { + const template = getTemplate(historyRecord.DELETED_RELEASING_AGENT); + render(template.getDetails(historyRecord.DELETED_RELEASING_AGENT)); + expect(screen.getByText('Deleted Releasing agent on NTS shipment #RQ38D4-01')).toBeInTheDocument(); + expect(screen.getByText('Releasing agent')).toBeInTheDocument(); + expect(screen.getByText(': Jane Smith, 555-123-4567, jane.smith@email.com')).toBeInTheDocument(); + }); + }); }); diff --git a/src/constants/MoveHistory/EventTemplates/UpdateMTOStatusServiceCounselingCompleted/updateMTOStatusServiceCounselingCompleted.jsx b/src/constants/MoveHistory/EventTemplates/UpdateMTOStatusServiceCounselingCompleted/updateMTOStatusServiceCounselingCompleted.jsx index 6fda8260ba0..033dbada873 100644 --- a/src/constants/MoveHistory/EventTemplates/UpdateMTOStatusServiceCounselingCompleted/updateMTOStatusServiceCounselingCompleted.jsx +++ b/src/constants/MoveHistory/EventTemplates/UpdateMTOStatusServiceCounselingCompleted/updateMTOStatusServiceCounselingCompleted.jsx @@ -9,5 +9,12 @@ export default { eventName: o.updateMTOStatusServiceCounselingCompleted, tableName: t.moves, getEventNameDisplay: () => 'Updated move', - getDetails: () => <> Counseling Completed , + getDetails: ({ changedValues }) => { + return ( + <> +
Counseling Completed
+ {changedValues?.sc_assigned_id !== undefined ?
Counselor Unassigned
: null} + + ); + }, }; diff --git a/src/constants/MoveHistory/EventTemplates/UpdateMTOStatusServiceCounselingCompleted/updateMTOStatusServiceCounselingCompleted.test.jsx b/src/constants/MoveHistory/EventTemplates/UpdateMTOStatusServiceCounselingCompleted/updateMTOStatusServiceCounselingCompleted.test.jsx index 153216daf83..d4422ca4cc0 100644 --- a/src/constants/MoveHistory/EventTemplates/UpdateMTOStatusServiceCounselingCompleted/updateMTOStatusServiceCounselingCompleted.test.jsx +++ b/src/constants/MoveHistory/EventTemplates/UpdateMTOStatusServiceCounselingCompleted/updateMTOStatusServiceCounselingCompleted.test.jsx @@ -26,4 +26,15 @@ describe('When given a completed services counseling for a move', () => { render(template.getEventNameDisplay(historyRecord)); expect(screen.getByText('Updated move')).toBeInTheDocument(); }); + it('displays correct details when an SC is unassigned', () => { + historyRecord.changedValues = { + ...historyRecord.changedValues, + sc_assigned_id: null, + }; + const template = getTemplate(historyRecord); + + render(template.getDetails(historyRecord)); + expect(screen.getByText('Counseling Completed')).toBeInTheDocument(); + expect(screen.getByText('Counselor Unassigned')).toBeInTheDocument(); + }); }); diff --git a/src/constants/MoveHistory/EventTemplates/UpdateMoveTaskOrderStatus/updateMoveTaskOrderStatus.jsx b/src/constants/MoveHistory/EventTemplates/UpdateMoveTaskOrderStatus/updateMoveTaskOrderStatus.jsx index 1d433adf367..bcdc2533a0c 100644 --- a/src/constants/MoveHistory/EventTemplates/UpdateMoveTaskOrderStatus/updateMoveTaskOrderStatus.jsx +++ b/src/constants/MoveHistory/EventTemplates/UpdateMoveTaskOrderStatus/updateMoveTaskOrderStatus.jsx @@ -9,5 +9,12 @@ export default { eventName: o.updateMoveTaskOrderStatus, tableName: t.moves, getEventNameDisplay: () => 'Approved move', - getDetails: () => <> Created Move Task Order (MTO) , + getDetails: ({ changedValues }) => { + return ( + <> +
Created Move Task Order (MTO)
+ {changedValues?.too_assigned_id !== undefined ?
Task Ordering Officer Unassigned
: null} + + ); + }, }; diff --git a/src/constants/MoveHistory/EventTemplates/UpdateMoveTaskOrderStatus/updateMoveTaskOrderStatus.test.jsx b/src/constants/MoveHistory/EventTemplates/UpdateMoveTaskOrderStatus/updateMoveTaskOrderStatus.test.jsx index 6f6f5a11f78..c7a47beb1d3 100644 --- a/src/constants/MoveHistory/EventTemplates/UpdateMoveTaskOrderStatus/updateMoveTaskOrderStatus.test.jsx +++ b/src/constants/MoveHistory/EventTemplates/UpdateMoveTaskOrderStatus/updateMoveTaskOrderStatus.test.jsx @@ -27,4 +27,16 @@ describe('when given a Move approved history record', () => { expect(screen.getByText('Approved move')); expect(screen.getByText('Created Move Task Order (MTO)')); }); + + it('displays correct details when a TOO is unassigned', () => { + historyRecord.changedValues = { + ...historyRecord.changedValues, + too_assigned_id: null, + }; + + const template = getTemplate(historyRecord); + render(template.getDetails(historyRecord)); + expect(screen.getByText('Created Move Task Order (MTO)')); + expect(screen.getByText('Task Ordering Officer Unassigned')); + }); }); diff --git a/src/constants/MoveHistory/EventTemplates/UpdatePaymentRequestStatus/UpdatePaymentRequestStatusMoves.jsx b/src/constants/MoveHistory/EventTemplates/UpdatePaymentRequestStatus/UpdatePaymentRequestStatusMoves.jsx new file mode 100644 index 00000000000..82fa3b3d2c7 --- /dev/null +++ b/src/constants/MoveHistory/EventTemplates/UpdatePaymentRequestStatus/UpdatePaymentRequestStatusMoves.jsx @@ -0,0 +1,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'; + +export default { + action: a.UPDATE, + eventName: o.updatePaymentRequestStatus, + tableName: t.moves, + getEventNameDisplay: () => 'Updated move', + getDetails: ({ changedValues }) => ( + <> +
Payment Requests Addressed
+ {changedValues?.tio_assigned_id !== undefined ?
Task Invoicing Officer Unassigned
: null} + + ), +}; diff --git a/src/constants/MoveHistory/EventTemplates/UpdatePaymentRequestStatus/UpdatePaymentRequestStatusMoves.test.jsx b/src/constants/MoveHistory/EventTemplates/UpdatePaymentRequestStatus/UpdatePaymentRequestStatusMoves.test.jsx new file mode 100644 index 00000000000..4868aefe01c --- /dev/null +++ b/src/constants/MoveHistory/EventTemplates/UpdatePaymentRequestStatus/UpdatePaymentRequestStatusMoves.test.jsx @@ -0,0 +1,42 @@ +import { screen, render } from '@testing-library/react'; + +import e from 'constants/MoveHistory/EventTemplates/UpdatePaymentRequestStatus/UpdatePaymentRequestStatusMoves'; +import getTemplate from 'constants/MoveHistory/TemplateManager'; + +describe('When given a completed services counseling for a move', () => { + const historyRecord = { + action: 'UPDATE', + eventName: 'updatePaymentRequestStatus', + tableName: 'moves', + }; + it('correctly matches the update mto status services counseling completed event to the proper template', () => { + const template = getTemplate(historyRecord); + expect(template).toMatchObject(e); + }); + + it('displays the proper name in the event name display column', () => { + const template = getTemplate(historyRecord); + + render(template.getEventNameDisplay(historyRecord)); + expect(screen.getByText('Updated move')).toBeInTheDocument(); + }); + + it('displays default when TIO ID is not present', () => { + const template = getTemplate(historyRecord); + + render(template.getDetails(historyRecord)); + expect(screen.getByText('Payment Requests Addressed')).toBeInTheDocument(); + }); + + it('displays correct details when a TIO is unassigned', () => { + historyRecord.changedValues = { + ...historyRecord.changedValues, + tio_assigned_id: null, + }; + const template = getTemplate(historyRecord); + + render(template.getDetails(historyRecord)); + expect(screen.getByText('Payment Requests Addressed')).toBeInTheDocument(); + expect(screen.getByText('Task Invoicing Officer Unassigned')).toBeInTheDocument(); + }); +}); diff --git a/src/constants/MoveHistory/EventTemplates/UpdatePaymentServiceItem/UpdatePaymentServiceItemStatus.jsx b/src/constants/MoveHistory/EventTemplates/UpdatePaymentServiceItem/UpdatePaymentServiceItemStatus.jsx new file mode 100644 index 00000000000..8733ec0bc30 --- /dev/null +++ b/src/constants/MoveHistory/EventTemplates/UpdatePaymentServiceItem/UpdatePaymentServiceItemStatus.jsx @@ -0,0 +1,56 @@ +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 { getMtoShipmentLabel } from 'utils/formatMtoShipment'; +import LabeledDetails from 'pages/Office/MoveHistory/LabeledDetails'; +import { PAYMENT_SERVICE_ITEM_STATUS } from 'shared/constants'; + +const formatChangedValues = (historyRecord) => { + const newChangedValues = { + ...historyRecord.changedValues, + ...getMtoShipmentLabel(historyRecord), + }; + + // Removed unneeded values to avoid clutter in audit log + if (newChangedValues.status === 'APPROVED' || newChangedValues.status === 'REQUESTED') { + delete newChangedValues.rejection_reason; + } + delete newChangedValues.status; + const newHistoryRecord = { ...historyRecord }; + delete newHistoryRecord.changedValues.status; + return { ...newHistoryRecord, changedValues: newChangedValues }; +}; + +export default { + action: a.UPDATE, + eventName: o.updatePaymentServiceItemStatus, + tableName: t.payment_service_items, + getEventNameDisplay: (historyRecord) => { + let actionPrefix = ''; + + /** + * IF there is a rejection_reason present in the changedValues, then either the reason was updated (in which case the status will be undefined) + * OR it was just rejected, wither way we want the rejected prefix, second || condition is a "just in case" check, not sure if there's a state + * where status would be updated but not rejection_reason + */ + if ( + ('rejection_reason' in historyRecord.changedValues && + historyRecord.changedValues.rejection_reason !== null && + historyRecord.changedValues.status !== 'APPROVED' && + historyRecord.changedValues.status !== 'REQUESTED') || + historyRecord.changedValues.status === PAYMENT_SERVICE_ITEM_STATUS.DENIED + ) { + actionPrefix = 'Rejected'; + } else if (historyRecord.changedValues.status === PAYMENT_SERVICE_ITEM_STATUS.APPROVED) { + actionPrefix = 'Approved'; + } else { + actionPrefix = 'Updated'; + } + return
{actionPrefix} Payment Service Item
; + }, + getDetails: (historyRecord) => { + return ; + }, +}; diff --git a/src/constants/MoveHistory/EventTemplates/UpdatePaymentServiceItem/UpdatePaymentServiceItemStatus.test.jsx b/src/constants/MoveHistory/EventTemplates/UpdatePaymentServiceItem/UpdatePaymentServiceItemStatus.test.jsx new file mode 100644 index 00000000000..1d0c3216a91 --- /dev/null +++ b/src/constants/MoveHistory/EventTemplates/UpdatePaymentServiceItem/UpdatePaymentServiceItemStatus.test.jsx @@ -0,0 +1,88 @@ +import { render, screen } from '@testing-library/react'; + +import getTemplate from 'constants/MoveHistory/TemplateManager'; +import o from 'constants/MoveHistory/UIDisplay/Operations'; +import a from 'constants/MoveHistory/Database/Actions'; +import t from 'constants/MoveHistory/Database/Tables'; + +describe('When approving/rejecting a payment service item', () => { + const rejectPaymentServiceItemRecord = { + action: a.UPDATE, + actionTstampClk: '2025-01-10T19:44:31.255Z', + actionTstampStm: '2025-01-10T19:44:31.253Z', + actionTstampTx: '2025-01-10T19:44:31.220Z', + context: [ + { + shipment_type: 'PPM', + shipment_locator: 'RQ38D4-01', + shipment_id_abbr: 'f10be', + }, + ], + changedValues: { + rejection_reason: 'Some reason', + status: 'DENIED', + }, + eventName: o.updatePaymentServiceItemStatus, + tableName: t.payment_service_items, + id: '2419f1db-3f8b-4823-974f-9aa4edb753da', + objectId: 'eee30fb1-dc66-4821-a17c-2ecf431ceb9d', + oldValues: { + id: 'eee30fb1-dc66-4821-a17c-2ecf431ceb9d', + ppm_shipment_id: '86329c14-564b-4580-94b9-8a2e80bccefc', + reason: null, + status: null, + }, + }; + + const approvePaymentServiceItemRecord = { ...rejectPaymentServiceItemRecord }; + approvePaymentServiceItemRecord.changedValues = { + status: 'APPROVED', + }; + + it('displays an approved payment service item', () => { + const template = getTemplate(approvePaymentServiceItemRecord); + + render(template.getEventNameDisplay(approvePaymentServiceItemRecord)); + expect(screen.getByText('Approved Payment Service Item')).toBeInTheDocument(); + + render(template.getDetails(approvePaymentServiceItemRecord)); + expect(screen.getByText('PPM shipment #RQ38D4-01')).toBeInTheDocument(); + }); + + it('displays an updated payment service item', () => { + const updatedServiceItemRecord = { ...approvePaymentServiceItemRecord }; + delete updatedServiceItemRecord.changedValues.status; + delete updatedServiceItemRecord.changedValues.rejection_reason; + const template = getTemplate(updatedServiceItemRecord); + + render(template.getEventNameDisplay(updatedServiceItemRecord)); + expect(screen.getByText('Updated Payment Service Item')).toBeInTheDocument(); + + render(template.getDetails(updatedServiceItemRecord)); + expect(screen.getByText('PPM shipment #RQ38D4-01')).toBeInTheDocument(); + }); + + it('displays a rejected payment service item and the rejection reason', () => { + const template = getTemplate(rejectPaymentServiceItemRecord); + + render(template.getEventNameDisplay(rejectPaymentServiceItemRecord)); + expect(screen.getByText('Rejected Payment Service Item')).toBeInTheDocument(); + + render(template.getDetails(rejectPaymentServiceItemRecord)); + expect(screen.getByText('Reason')).toBeInTheDocument(); + expect(screen.getByText(': Some reason')).toBeInTheDocument(); + }); + + it('displays a cleared payment service item with no unneeded information', () => { + const clearedServiceItem = rejectPaymentServiceItemRecord; + clearedServiceItem.changedValues.status = 'REQUESTED'; + const template = getTemplate(clearedServiceItem); + + render(template.getEventNameDisplay(clearedServiceItem)); + expect(screen.getByText('Updated Payment Service Item')).toBeInTheDocument(); + + render(template.getDetails(clearedServiceItem)); + expect(screen.queryByText('Reason')).not.toBeInTheDocument(); + expect(screen.queryByText(': Some reason')).not.toBeInTheDocument(); + }); +}); diff --git a/src/constants/MoveHistory/EventTemplates/UpdateServiceItemStatus/updateServiceItemStatusUpdateMove.jsx b/src/constants/MoveHistory/EventTemplates/UpdateServiceItemStatus/updateServiceItemStatusUpdateMove.jsx index 799beef3b0a..0bd4ffcedd8 100644 --- a/src/constants/MoveHistory/EventTemplates/UpdateServiceItemStatus/updateServiceItemStatusUpdateMove.jsx +++ b/src/constants/MoveHistory/EventTemplates/UpdateServiceItemStatus/updateServiceItemStatusUpdateMove.jsx @@ -10,5 +10,17 @@ export default { eventName: o.updateMTOServiceItemStatus, tableName: t.moves, getEventNameDisplay: () => 'Updated move', - getDetails: (historyRecord) => , + getDetails: (historyRecord) => { + return ( + <> + + {historyRecord?.changedValues?.too_assigned_id !== undefined ? ( + <> +
Service Items Addressed
+
Task Ordering Officer Unassigned
+ + ) : null} + + ); + }, }; diff --git a/src/constants/MoveHistory/EventTemplates/UpdateServiceItemStatus/updateServiceItemStatusUpdateMove.test.jsx b/src/constants/MoveHistory/EventTemplates/UpdateServiceItemStatus/updateServiceItemStatusUpdateMove.test.jsx index 6660baa258e..94d6497cd49 100644 --- a/src/constants/MoveHistory/EventTemplates/UpdateServiceItemStatus/updateServiceItemStatusUpdateMove.test.jsx +++ b/src/constants/MoveHistory/EventTemplates/UpdateServiceItemStatus/updateServiceItemStatusUpdateMove.test.jsx @@ -25,4 +25,16 @@ describe('when given a update service item status, update move history record', expect(screen.getByText(value)).toBeInTheDocument(); }); }); + + it('displays correct details when a TOO is unassigned', () => { + historyRecord.changedValues = { + ...historyRecord.changedValues, + too_assigned_id: null, + }; + const template = getTemplate(historyRecord); + + render(template.getDetails(historyRecord)); + expect(screen.getByText('Service Items Addressed')).toBeInTheDocument(); + expect(screen.getByText('Task Ordering Officer Unassigned')).toBeInTheDocument(); + }); }); diff --git a/src/constants/MoveHistory/EventTemplates/index.js b/src/constants/MoveHistory/EventTemplates/index.js index 249eaf2dad3..acd7545aa41 100644 --- a/src/constants/MoveHistory/EventTemplates/index.js +++ b/src/constants/MoveHistory/EventTemplates/index.js @@ -111,4 +111,9 @@ export { default as moveCancelerPPMShipments } from './MoveCanceler/MoveCanceler export { default as cancelMove } from './CancelMove/CancelMove'; export { default as cancelMoveMTOShipments } from './CancelMove/CancelMoveMTOShipments'; export { default as cancelMovePPMShipments } from './CancelMove/CancelMovePPMShipments'; +export { default as updateAssignedOfficeUser } from './UpdateAssignedOfficeUser/UpdateAssignedOfficeUser'; +export { default as deleteAssignedOfficeUser } from './UpdateAssignedOfficeUser/DeleteAssignedOfficeUser'; +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'; diff --git a/src/constants/MoveHistory/UIDisplay/Operations.js b/src/constants/MoveHistory/UIDisplay/Operations.js index a74bccc3603..43a8327210b 100644 --- a/src/constants/MoveHistory/UIDisplay/Operations.js +++ b/src/constants/MoveHistory/UIDisplay/Operations.js @@ -35,6 +35,7 @@ export default { updateOrder: 'updateOrder', // ghc.yaml updateOrders: 'updateOrders', // internal.yaml updatePaymentRequestStatus: 'updatePaymentRequestStatus', + updatePaymentServiceItemStatus: 'updatePaymentServiceItemStatus', updateReweigh: 'updateReweigh', updateServiceItemStatus: 'updateMTOServiceItemStatus', updateServiceItemSitEntryDate: 'updateServiceItemSitEntryDate', // ghc.yaml @@ -67,4 +68,6 @@ export default { addAppealToSeriousIncident: 'addAppealToSeriousIncident', // ghc.yaml cancelMove: 'cancelMove', // internal.yaml reviewShipmentAddressUpdate: 'reviewShipmentAddressUpdate', // ghc.yaml + updateAssignedOfficeUser: 'updateAssignedOfficeUser', // ghc.yaml + deleteAssignedOfficeUser: 'deleteAssignedOfficeUser', // ghc.yaml }; diff --git a/src/constants/prime.js b/src/constants/prime.js index 098dfe54dc7..267156031b9 100644 --- a/src/constants/prime.js +++ b/src/constants/prime.js @@ -7,6 +7,7 @@ export const createServiceItemModelTypes = { MTOServiceItemShuttle: 'MTOServiceItemShuttle', MTOServiceItemDomesticCrating: 'MTOServiceItemDomesticCrating', MTOServiceItemInternationalCrating: 'MTOServiceItemInternationalCrating', + MTOServiceItemInternationalShuttle: 'MTOServiceItemInternationalShuttle', }; export const shuttleServiceItemCodeOptions = [ @@ -14,6 +15,11 @@ export const shuttleServiceItemCodeOptions = [ { value: serviceItemCodes.DDSHUT, key: SERVICE_ITEM_CODES.DDSHUT }, ]; +export const internationalShuttleServiceItemCodeOptions = [ + { value: serviceItemCodes.IOSHUT, key: SERVICE_ITEM_CODES.IOSHUT }, + { value: serviceItemCodes.IDSHUT, key: SERVICE_ITEM_CODES.IDSHUT }, +]; + export const domesticCratingServiceItemCodeOptions = [ { value: serviceItemCodes.DCRT, key: SERVICE_ITEM_CODES.DCRT }, { value: serviceItemCodes.DUCRT, key: SERVICE_ITEM_CODES.DUCRT }, diff --git a/src/constants/serviceItems.js b/src/constants/serviceItems.js index 31e47819527..213afb45f38 100644 --- a/src/constants/serviceItems.js +++ b/src/constants/serviceItems.js @@ -138,12 +138,14 @@ const SERVICE_ITEM_CODES = { DOP: 'DOP', DOPSIT: 'DOPSIT', DOSHUT: 'DOSHUT', + IOSHUT: 'IOSHUT', DPK: 'DPK', DNPK: 'DNPK', DSH: 'DSH', DUPK: 'DUPK', FSC: 'FSC', DDSHUT: 'DDSHUT', + IDSHUT: 'IDSHUT', DCRT: 'DCRT', DUCRT: 'DUCRT', ICRT: 'ICRT', diff --git a/src/hooks/queries.js b/src/hooks/queries.js index ce469702359..17ccfe30f5a 100644 --- a/src/hooks/queries.js +++ b/src/hooks/queries.js @@ -571,9 +571,10 @@ export const useMovesQueueQueries = ({ currentPage = PAGINATION_PAGE_DEFAULT, currentPageSize = PAGINATION_PAGE_SIZE_DEFAULT, viewAsGBLOC, + activeRole, }) => { const { data = {}, ...movesQueueQuery } = useQuery( - [MOVES_QUEUE, { sort, order, filters, currentPage, currentPageSize, viewAsGBLOC }], + [MOVES_QUEUE, { sort, order, filters, currentPage, currentPageSize, viewAsGBLOC, activeRole }], ({ queryKey }) => getMovesQueue(...queryKey), ); const { isLoading, isError, isSuccess } = movesQueueQuery; @@ -593,11 +594,12 @@ export const useServicesCounselingQueuePPMQueries = ({ currentPage = PAGINATION_PAGE_DEFAULT, currentPageSize = PAGINATION_PAGE_SIZE_DEFAULT, viewAsGBLOC, + activeRole, }) => { const { data = {}, ...servicesCounselingQueueQuery } = useQuery( [ SERVICES_COUNSELING_QUEUE, - { sort, order, filters, currentPage, currentPageSize, needsPPMCloseout: true, viewAsGBLOC }, + { sort, order, filters, currentPage, currentPageSize, needsPPMCloseout: true, viewAsGBLOC, activeRole }, ], ({ queryKey }) => getServicesCounselingPPMQueue(...queryKey), ); @@ -619,11 +621,12 @@ export const useServicesCounselingQueueQueries = ({ currentPage = PAGINATION_PAGE_DEFAULT, currentPageSize = PAGINATION_PAGE_SIZE_DEFAULT, viewAsGBLOC, + activeRole, }) => { const { data = {}, ...servicesCounselingQueueQuery } = useQuery( [ SERVICES_COUNSELING_QUEUE, - { sort, order, filters, currentPage, currentPageSize, needsPPMCloseout: false, viewAsGBLOC }, + { sort, order, filters, currentPage, currentPageSize, needsPPMCloseout: false, viewAsGBLOC, activeRole }, ], ({ queryKey }) => getServicesCounselingQueue(...queryKey), ); @@ -645,9 +648,10 @@ export const usePaymentRequestQueueQueries = ({ currentPage = PAGINATION_PAGE_DEFAULT, currentPageSize = PAGINATION_PAGE_SIZE_DEFAULT, viewAsGBLOC, + activeRole, }) => { const { data = {}, ...paymentRequestsQueueQuery } = useQuery( - [PAYMENT_REQUESTS_QUEUE, { sort, order, filters, currentPage, currentPageSize, viewAsGBLOC }], + [PAYMENT_REQUESTS_QUEUE, { sort, order, filters, currentPage, currentPageSize, viewAsGBLOC, activeRole }], ({ queryKey }) => getPaymentRequestsQueue(...queryKey), ); diff --git a/src/pages/Admin/RequestedOfficeUsers/RequestedOfficeUserEdit.jsx b/src/pages/Admin/RequestedOfficeUsers/RequestedOfficeUserEdit.jsx index f315bdef42d..5402895269d 100644 --- a/src/pages/Admin/RequestedOfficeUsers/RequestedOfficeUserEdit.jsx +++ b/src/pages/Admin/RequestedOfficeUsers/RequestedOfficeUserEdit.jsx @@ -11,13 +11,15 @@ import { ReferenceInput, useRecordContext, useRedirect, + DeleteButton, + Confirm, } from 'react-admin'; import styles from './RequestedOfficeUserShow.module.scss'; import { RolesPrivilegesCheckboxInput } from 'scenes/SystemAdmin/shared/RolesPrivilegesCheckboxes'; import { edipiValidator, phoneValidators } from 'scenes/SystemAdmin/shared/form_validators'; -import { updateRequestedOfficeUser } from 'services/adminApi'; +import { deleteOfficeUser, updateRequestedOfficeUser } from 'services/adminApi'; import { roleTypes } from 'constants/userRoles'; const RequestedOfficeUserShowTitle = () => { @@ -62,6 +64,11 @@ const RequestedOfficeUserEdit = () => { const redirect = useRedirect(); const [serverError, setServerError] = useState(''); const [validationCheck, setValidationCheck] = useState(''); + const [open, setOpen] = useState(false); + const [userData, setUserData] = useState({}); + + const handleClick = () => setOpen(true); + const handleDialogClose = () => setOpen(false); // rejects the user with all relevant updates made by admin // performs validation to ensure the rejection reason was provided @@ -125,6 +132,24 @@ const RequestedOfficeUserEdit = () => { } }; + // hard deletes a user and associated roles/privileges + // cannot be undone, but the user is shown a confirmation modal to avoid oopsies + const deleteUser = async () => { + deleteOfficeUser(userData.id) + .then(() => { + redirect('/'); + }) + .catch((error) => { + setServerError(error); + redirect(false); + }); + }; + + const handleConfirm = () => { + deleteUser(); + setOpen(false); + }; + // rendering tool bar with added error/validation alerts const renderToolBar = () => { return ( @@ -161,6 +186,15 @@ const RequestedOfficeUserEdit = () => { }, }} /> + { + // setting user data so we can use it in the delete function + setUserData(data); + handleClick(); + }, + }} + /> ); @@ -168,6 +202,13 @@ const RequestedOfficeUserEdit = () => { return ( }> + { const RequestedOfficeUserListFilter = () => ( - + + + ); const defaultSort = { field: 'createdAt', order: 'DESC' }; +const RolesTextField = (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
{RolesTextField(record)}
; +}; + const RequestedOfficeUserList = () => ( } @@ -34,6 +67,9 @@ const RequestedOfficeUserList = () => ( + + + ); diff --git a/src/pages/Admin/RequestedOfficeUsers/RequestedOfficeUserShow.module.scss b/src/pages/Admin/RequestedOfficeUsers/RequestedOfficeUserShow.module.scss index b37e5801e46..d2b9adfc325 100644 --- a/src/pages/Admin/RequestedOfficeUsers/RequestedOfficeUserShow.module.scss +++ b/src/pages/Admin/RequestedOfficeUsers/RequestedOfficeUserShow.module.scss @@ -7,7 +7,8 @@ gap: 10px; .approveBtn { - width: 125px; + width: 100px; + height: 38px; margin-left: 15px; background-color: $primary; @@ -20,7 +21,8 @@ } .rejectBtn { - width: 125px; + width: 100px; + height: 38px; background-color: $error; &:active, @@ -50,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 aa1da0f83ad..42bbb619866 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/CustomerOnboarding/CustomerName.jsx b/src/pages/Office/CustomerOnboarding/CustomerName.jsx deleted file mode 100644 index ffa831cf422..00000000000 --- a/src/pages/Office/CustomerOnboarding/CustomerName.jsx +++ /dev/null @@ -1,81 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { useState } from 'react'; -import { GridContainer, Grid, Alert } from '@trussworks/react-uswds'; -import { useNavigate } from 'react-router-dom'; - -import styles from './CustomerName.module.scss'; - -import NotificationScrollToTop from 'components/NotificationScrollToTop'; -import NameForm from 'components/Customer/NameForm/NameForm'; -import { patchServiceMember, getResponseError } from 'services/internalApi'; -import { ServiceMemberShape } from 'types/customerShapes'; -import { generalRoutes } from 'constants/routes'; - -export const CustomerName = ({ serviceMember, updateServiceMember }) => { - const [serverError, setServerError] = useState(null); - const navigate = useNavigate(); - const initialValues = { - first_name: serviceMember?.first_name || '', - middle_name: serviceMember?.middle_name || '', - last_name: serviceMember?.last_name || '', - suffix: serviceMember?.suffix || '', - }; - - const handleNext = () => { - // add next route - }; - - const handleBack = () => { - navigate(generalRoutes.BASE_QUEUE_SEARCH_PATH); - }; - - const handleSubmit = (values) => { - const payload = { - id: serviceMember.id, - first_name: values.first_name, - middle_name: values.middle_name, - last_name: values.last_name, - suffix: values.suffix, - }; - - return patchServiceMember(payload) - .then(updateServiceMember) - .then(handleNext) - .catch((e) => { - // TODO - error handling - below is rudimentary error handling to approximate existing UX - // Error shape: https://github.com/swagger-api/swagger-js/blob/master/docs/usage/http-client.md#errors - const { response } = e; - const errorMessage = getResponseError(response, 'failed to update service member due to server error'); - setServerError(errorMessage); - }); - }; - - return ( - - - - {serverError && ( - - - - {serverError} - - - - )} - - - - - - - - ); -}; - -CustomerName.propTypes = { - updateServiceMember: PropTypes.func.isRequired, - serviceMember: ServiceMemberShape.isRequired, -}; - -export default CustomerName; diff --git a/src/pages/Office/CustomerOnboarding/CustomerName.module.scss b/src/pages/Office/CustomerOnboarding/CustomerName.module.scss deleted file mode 100644 index bff50b614eb..00000000000 --- a/src/pages/Office/CustomerOnboarding/CustomerName.module.scss +++ /dev/null @@ -1,9 +0,0 @@ -.nameFormContainer { - width: 40vw; - display: flex; - justify-content: center; -} - -.nameForm { - width: 100%; -} diff --git a/src/pages/Office/CustomerOnboarding/CustomerName.test.jsx b/src/pages/Office/CustomerOnboarding/CustomerName.test.jsx deleted file mode 100644 index 5c8d22efc6c..00000000000 --- a/src/pages/Office/CustomerOnboarding/CustomerName.test.jsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; -import { render, screen } from '@testing-library/react'; - -import NameForm from 'components/Customer/NameForm/NameForm'; - -describe('Name page', () => { - it('renders the NameForm', async () => { - render(); - - expect(await screen.findByRole('heading', { name: 'Name', level: 1 })).toBeInTheDocument(); - }); -}); diff --git a/src/pages/Office/HeadquartersQueues/HeadquartersQueues.jsx b/src/pages/Office/HeadquartersQueues/HeadquartersQueues.jsx index 1786b4525ff..c9d08481f3b 100644 --- a/src/pages/Office/HeadquartersQueues/HeadquartersQueues.jsx +++ b/src/pages/Office/HeadquartersQueues/HeadquartersQueues.jsx @@ -38,7 +38,7 @@ import { milmoveLogger } from 'utils/milmoveLog'; import ConnectedFlashMessage from 'containers/FlashMessage/FlashMessage'; import CustomerSearchForm from 'components/CustomerSearchForm/CustomerSearchForm'; -const HeadquartersQueue = ({ isQueueManagementFFEnabled }) => { +const HeadquartersQueue = ({ isQueueManagementFFEnabled, activeRole }) => { const navigate = useNavigate(); const { queueType } = useParams(); const [search, setSearch] = useState({ moveCode: null, dodID: null, customerName: null, paymentRequestCode: null }); @@ -247,6 +247,7 @@ const HeadquartersQueue = ({ isQueueManagementFFEnabled }) => { csvExportQueueFetcher={getMovesQueue} csvExportQueueFetcherKey="queueMoves" sessionStorageKey={queueType} + activeRole={activeRole} />
); @@ -273,6 +274,7 @@ const HeadquartersQueue = ({ isQueueManagementFFEnabled }) => { csvExportQueueFetcher={getPaymentRequestsQueue} csvExportQueueFetcherKey="queuePaymentRequests" sessionStorageKey={queueType} + activeRole={activeRole} />
); @@ -299,6 +301,7 @@ const HeadquartersQueue = ({ isQueueManagementFFEnabled }) => { csvExportQueueFetcher={getServicesCounselingPPMQueue} csvExportQueueFetcherKey="queueMoves" sessionStorageKey={queueType} + activeRole={activeRole} />
); @@ -326,6 +329,7 @@ const HeadquartersQueue = ({ isQueueManagementFFEnabled }) => { csvExportQueueFetcher={getServicesCounselingQueue} csvExportQueueFetcherKey="queueMoves" sessionStorageKey={queueType} + activeRole={activeRole} />
); diff --git a/src/pages/Office/MoveAllowances/MoveAllowances.jsx b/src/pages/Office/MoveAllowances/MoveAllowances.jsx index 9adf62bdc39..b2cf0264b3a 100644 --- a/src/pages/Office/MoveAllowances/MoveAllowances.jsx +++ b/src/pages/Office/MoveAllowances/MoveAllowances.jsx @@ -43,6 +43,11 @@ 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(), + weightRestriction: Yup.number() + .min(0, 'Weight restriction must be greater than or equal to 0') + .max(18000, 'Weight restriction cannot exceed 18,000 lbs') + .transform((value) => (Number.isNaN(value) ? 0 : value)) + .notRequired(), }); const MoveAllowances = () => { @@ -98,6 +103,7 @@ const MoveAllowances = () => { organizationalClothingAndIndividualEquipment, storageInTransit, gunSafe, + weightRestriction, accompaniedTour, dependentsTwelveAndOver, dependentsUnderTwelve, @@ -118,6 +124,7 @@ const MoveAllowances = () => { organizationalClothingAndIndividualEquipment, storageInTransit: Number(storageInTransit), gunSafe, + weightRestriction: Number(weightRestriction), accompaniedTour, dependentsTwelveAndOver: Number(dependentsTwelveAndOver), dependentsUnderTwelve: Number(dependentsUnderTwelve), @@ -133,6 +140,7 @@ const MoveAllowances = () => { requiredMedicalEquipmentWeight, organizationalClothingAndIndividualEquipment, gunSafe, + weightRestriction, storageInTransit, dependentsUnderTwelve, dependentsTwelveAndOver, @@ -148,6 +156,7 @@ const MoveAllowances = () => { requiredMedicalEquipmentWeight: `${requiredMedicalEquipmentWeight}`, organizationalClothingAndIndividualEquipment, gunSafe, + weightRestriction: `${weightRestriction}`, storageInTransit: `${storageInTransit}`, accompaniedTour, dependentsUnderTwelve: `${dependentsUnderTwelve}`, diff --git a/src/pages/Office/MoveDetails/MoveDetails.jsx b/src/pages/Office/MoveDetails/MoveDetails.jsx index 2032009100a..b50f239d6af 100644 --- a/src/pages/Office/MoveDetails/MoveDetails.jsx +++ b/src/pages/Office/MoveDetails/MoveDetails.jsx @@ -55,8 +55,8 @@ const MoveDetails = ({ }) => { const { moveCode } = useParams(); const [isFinancialModalVisible, setIsFinancialModalVisible] = useState(false); - const [isCancelMoveModalVisible, setIsCancelMoveModalVisible] = useState(false); const [errorMessage, setErrorMessage] = useState(null); + const [isCancelMoveModalVisible, setIsCancelMoveModalVisible] = useState(false); const [alertMessage, setAlertMessage] = useState(null); const [alertType, setAlertType] = useState('success'); const [enableBoat, setEnableBoat] = useState(false); @@ -77,6 +77,7 @@ const MoveDetails = ({ { fieldName: 'storageFacility' }, { fieldName: 'ntsRecordedWeight' }, { fieldName: 'serviceOrderNumber' }, + { fieldName: 'requestedPickupDate' }, { fieldName: 'tacType' }, ], PPM: [ @@ -430,6 +431,7 @@ const MoveDetails = ({ requiredMedicalEquipmentWeight: allowances.requiredMedicalEquipmentWeight, organizationalClothingAndIndividualEquipment: allowances.organizationalClothingAndIndividualEquipment, gunSafe: allowances.gunSafe, + weightRestriction: allowances.weightRestriction, dependentsUnderTwelve: allowances.dependentsUnderTwelve, dependentsTwelveAndOver: allowances.dependentsTwelveAndOver, accompaniedTour: allowances.accompaniedTour, @@ -658,7 +660,7 @@ const MoveDetails = ({ } shipmentsInfoNonPpm={shipmentsInfoNonPPM} > - +
diff --git a/src/pages/Office/MoveHistory/PaymentDetails.jsx b/src/pages/Office/MoveHistory/PaymentDetails.jsx index 65af4dbe90c..f1b38320d23 100644 --- a/src/pages/Office/MoveHistory/PaymentDetails.jsx +++ b/src/pages/Office/MoveHistory/PaymentDetails.jsx @@ -24,6 +24,13 @@ const filterContextStatus = (context, statusToFilter) => {
{value.name}
{price.toFixed(2)}
+
+ {value.status === PAYMENT_SERVICE_ITEM_STATUS.DENIED ? ( +
+ Rejection Reason: + {value?.rejection_reason} +
+ ) : null}
, ); } diff --git a/src/pages/Office/MoveHistory/PaymentDetails.module.scss b/src/pages/Office/MoveHistory/PaymentDetails.module.scss index ba62a69fd09..8e347f56909 100644 --- a/src/pages/Office/MoveHistory/PaymentDetails.module.scss +++ b/src/pages/Office/MoveHistory/PaymentDetails.module.scss @@ -13,6 +13,7 @@ font-weight: 500; display: flex; justify-content: space-between; + flex-wrap: wrap; } .statusRow { display: flex; @@ -24,4 +25,11 @@ .rejectTimes { color: $error; } + .break { + flex-basis: 100%; + height: 0; + } + .rejectionReason { + text-indent: 1rem; + } } diff --git a/src/pages/Office/MoveHistory/PaymentDetails.test.jsx b/src/pages/Office/MoveHistory/PaymentDetails.test.jsx index fca870071c2..f9f38895d70 100644 --- a/src/pages/Office/MoveHistory/PaymentDetails.test.jsx +++ b/src/pages/Office/MoveHistory/PaymentDetails.test.jsx @@ -1,5 +1,5 @@ import React from 'react'; -import { render, screen } from '@testing-library/react'; +import { render, screen, act } from '@testing-library/react'; import PaymentDetails from './PaymentDetails'; @@ -47,4 +47,26 @@ describe('PaymentDetails', () => { expect(screen.getByText(156.78, { exact: false })).toBeInTheDocument(); }); + + describe('rejected service items', () => { + const context = [ + { + name: 'Domestic uncrating', + price: '5555', + status: 'DENIED', + rejection_reason: 'some reason', + }, + ]; + it('renders a rejected service item and its rejection reason', async () => { + render(); + + expect(screen.getByText('Domestic uncrating')).toBeInTheDocument(); + + expect(screen.getByText('Rejection Reason:')).toBeInTheDocument(); + await act(() => { + screen.getByText('Rejection Reason:').click(); + }); + expect(screen.getByText('some reason')).toBeVisible(); + }); + }); }); diff --git a/src/pages/Office/MoveQueue/MoveQueue.jsx b/src/pages/Office/MoveQueue/MoveQueue.jsx index 95b703189fc..12d42ac5d22 100644 --- a/src/pages/Office/MoveQueue/MoveQueue.jsx +++ b/src/pages/Office/MoveQueue/MoveQueue.jsx @@ -188,7 +188,7 @@ export const columns = (moveLockFlag, isQueueManagementEnabled, showBranchFilter return cols; }; -const MoveQueue = ({ isQueueManagementFFEnabled, userPrivileges, isBulkAssignmentFFEnabled }) => { +const MoveQueue = ({ isQueueManagementFFEnabled, userPrivileges, isBulkAssignmentFFEnabled, activeRole }) => { const navigate = useNavigate(); const { queueType } = useParams(); const [search, setSearch] = useState({ moveCode: null, dodID: null, customerName: null, paymentRequestCode: null }); @@ -334,6 +334,7 @@ const MoveQueue = ({ isQueueManagementFFEnabled, userPrivileges, isBulkAssignmen key={queueType} isSupervisor={supervisor} isBulkAssignmentFFEnabled={isBulkAssignmentFFEnabled} + activeRole={activeRole} />
); diff --git a/src/pages/Office/PaymentRequestQueue/PaymentRequestQueue.jsx b/src/pages/Office/PaymentRequestQueue/PaymentRequestQueue.jsx index a46518a2627..c43cf7bfbe6 100644 --- a/src/pages/Office/PaymentRequestQueue/PaymentRequestQueue.jsx +++ b/src/pages/Office/PaymentRequestQueue/PaymentRequestQueue.jsx @@ -191,7 +191,7 @@ export const columns = (moveLockFlag, isQueueManagementEnabled, showBranchFilter return cols; }; -const PaymentRequestQueue = ({ isQueueManagementFFEnabled, userPrivileges, isBulkAssignmentFFEnabled }) => { +const PaymentRequestQueue = ({ isQueueManagementFFEnabled, userPrivileges, isBulkAssignmentFFEnabled, activeRole }) => { const { queueType } = useParams(); const navigate = useNavigate(); const [search, setSearch] = useState({ moveCode: null, dodID: null, customerName: null, paymentRequestCode: null }); @@ -334,6 +334,7 @@ const PaymentRequestQueue = ({ isQueueManagementFFEnabled, userPrivileges, isBul key={queueType} isSupervisor={supervisor} isBulkAssignmentFFEnabled={isBulkAssignmentFFEnabled} + activeRole={activeRole} />
); diff --git a/src/pages/Office/ServicesCounselingAddOrders/ServicesCounselingAddOrders.test.jsx b/src/pages/Office/ServicesCounselingAddOrders/ServicesCounselingAddOrders.test.jsx index eb57eefb665..972b01d05ce 100644 --- a/src/pages/Office/ServicesCounselingAddOrders/ServicesCounselingAddOrders.test.jsx +++ b/src/pages/Office/ServicesCounselingAddOrders/ServicesCounselingAddOrders.test.jsx @@ -8,6 +8,7 @@ import { MockProviders } from 'testUtils'; import { counselingCreateOrder } from 'services/ghcApi'; import { setCanAddOrders } from 'store/general/actions'; import { isBooleanFlagEnabled } from 'utils/featureFlags'; +import { servicesCounselingRoutes } from 'constants/routes'; const mockNavigate = jest.fn(); jest.mock('react-router-dom', () => ({ @@ -23,6 +24,20 @@ jest.mock('utils/featureFlags', () => ({ jest.mock('services/ghcApi', () => ({ ...jest.requireActual('services/ghcApi'), counselingCreateOrder: jest.fn().mockImplementation(() => Promise.resolve()), + showCounselingOffices: jest.fn().mockImplementation(() => + Promise.resolve({ + body: [ + { + id: '3e937c1f-5539-4919-954d-017989130584', + name: 'Albuquerque AFB', + }, + { + id: 'fa51dab0-4553-4732-b843-1f33407f77bc', + name: 'Glendale Luke AFB', + }, + ], + }), + ), })); jest.mock('services/internalApi', () => ({ @@ -79,6 +94,7 @@ jest.mock('components/LocationSearchBox/api', () => ({ id: '7d123884-7c1b-4611-92ae-e8d43ca03ad9', name: 'Hill AFB', updated_at: '2021-02-11T16:48:04.117Z', + provides_services_counseling: true, }, { address: { @@ -316,10 +332,13 @@ const fakeResponse = { }, }; +const mockParams = { customerId: 'ea51dab0-4553-4732-b843-1f33407f77bd' }; +const mockPath = servicesCounselingRoutes.BASE_CUSTOMERS_ORDERS_ADD_PATH; + const renderWithMocks = () => { const testProps = { customer, setCanAddOrders: jest.fn() }; render( - + , ); @@ -342,18 +361,21 @@ describe('ServicesCounselingAddOrders component', () => { const user = userEvent.setup(); - await user.selectOptions(screen.getByLabelText('Orders type'), 'PERMANENT_CHANGE_OF_STATION'); - await user.type(screen.getByLabelText('Orders date'), '08 Nov 2020'); - await user.type(screen.getByLabelText('Report by date'), '26 Nov 2020'); + await user.selectOptions(screen.getByLabelText(/Orders type/), 'PERMANENT_CHANGE_OF_STATION'); + await user.type(screen.getByLabelText(/Orders date/), '08 Nov 2020'); + await user.type(screen.getByLabelText(/Report by date/), '26 Nov 2020'); await user.click(screen.getByLabelText('No')); - await user.selectOptions(screen.getByLabelText('Pay grade'), ['E-5']); + await user.selectOptions(screen.getByLabelText(/Pay grade/), ['E-5']); // Test Current Duty Location Search Box interaction - await user.type(screen.getByLabelText('Current duty location'), 'AFB', { delay: 500 }); + await user.type(screen.getByLabelText(/Current duty location/), 'AFB', { delay: 500 }); const selectedOptionCurrent = await screen.findByText(/Altus/); await user.click(selectedOptionCurrent); - await user.type(screen.getByLabelText('New duty location'), 'AFB', { delay: 500 }); + const counselingOfficeLabel = await screen.queryByText(/Counseling office/); + expect(counselingOfficeLabel).toBeFalsy(); + + await user.type(screen.getByLabelText(/New duty location/), 'AFB', { delay: 500 }); const selectedOptionNew = await screen.findByText(/Luke/); await user.click(selectedOptionNew); @@ -370,6 +392,39 @@ describe('ServicesCounselingAddOrders component', () => { }); }); + it('Displays the counseling office dropdown', async () => { + renderWithMocks(); + + counselingCreateOrder.mockImplementation(() => Promise.resolve(fakeResponse)); + + const user = userEvent.setup(); + + await user.selectOptions(screen.getByLabelText(/Orders type/), 'PERMANENT_CHANGE_OF_STATION'); + await user.type(screen.getByLabelText(/Orders date/), '08 Nov 2020'); + await user.type(screen.getByLabelText(/Report by date/), '29 Nov 2020'); + await user.click(screen.getByLabelText('No')); + await user.selectOptions(screen.getByLabelText(/Pay grade/), ['E-5']); + + // Test Current Duty Location Search Box interaction + await user.type(screen.getByLabelText(/Current duty location/), 'AFB', { delay: 500 }); + const selectedOptionCurrent = await screen.findByText(/Hill/); + await user.click(selectedOptionCurrent); + + const counselingOfficeLabel = await screen.queryByText(/Counseling office/); + expect(counselingOfficeLabel).toBeTruthy(); + + await userEvent.selectOptions(screen.getByLabelText(/Counseling office/), ['Glendale Luke AFB']); + + await user.type(screen.getByLabelText(/New duty location/), 'AFB', { delay: 500 }); + const selectedOptionNew = await screen.findByText(/Luke/); + await user.click(selectedOptionNew); + + const nextBtn = await screen.findByRole('button', { name: 'Next' }); + await waitFor(() => { + expect(nextBtn.getAttribute('disabled')).toBeFalsy(); + }); + }); + it('routes to the move details page when the next button is clicked for OCONUS orders', async () => { isBooleanFlagEnabled.mockImplementation(() => Promise.resolve(true)); renderWithMocks(); @@ -378,17 +433,17 @@ describe('ServicesCounselingAddOrders component', () => { const user = userEvent.setup(); - await user.selectOptions(screen.getByLabelText('Orders type'), 'PERMANENT_CHANGE_OF_STATION'); - await user.type(screen.getByLabelText('Orders date'), '08 Nov 2020'); - await user.type(screen.getByLabelText('Report by date'), '26 Nov 2020'); + await user.selectOptions(screen.getByLabelText(/Orders type/), 'PERMANENT_CHANGE_OF_STATION'); + await user.type(screen.getByLabelText(/Orders date/), '08 Nov 2020'); + await user.type(screen.getByLabelText(/Report by date/), '26 Nov 2020'); await user.click(screen.getByLabelText('No')); - await user.selectOptions(screen.getByLabelText('Pay grade'), ['E-5']); + await user.selectOptions(screen.getByLabelText(/Pay grade/), ['E-5']); - await user.type(screen.getByLabelText('Current duty location'), 'AFB', { delay: 500 }); + await user.type(screen.getByLabelText(/Current duty location/), 'AFB', { delay: 500 }); const selectedOptionCurrent = await screen.findByText(/Altus/); await user.click(selectedOptionCurrent); - await user.type(screen.getByLabelText('New duty location'), 'AFB', { delay: 500 }); + await user.type(screen.getByLabelText(/New duty location/), 'AFB', { delay: 500 }); const selectedOptionNew = await screen.findByText(/Outta This World/); await user.click(selectedOptionNew); diff --git a/src/pages/Office/ServicesCounselingMoveAllowances/ServicesCounselingMoveAllowances.jsx b/src/pages/Office/ServicesCounselingMoveAllowances/ServicesCounselingMoveAllowances.jsx index 98a85147767..dafd49b76f6 100644 --- a/src/pages/Office/ServicesCounselingMoveAllowances/ServicesCounselingMoveAllowances.jsx +++ b/src/pages/Office/ServicesCounselingMoveAllowances/ServicesCounselingMoveAllowances.jsx @@ -41,6 +41,11 @@ 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(), + weightRestriction: Yup.number() + .min(0, 'Weight restriction must be greater than or equal to 0') + .max(18000, 'Weight restriction cannot exceed 18,000 lbs') + .transform((value) => (Number.isNaN(value) ? 0 : value)) + .notRequired(), }); const ServicesCounselingMoveAllowances = () => { @@ -86,6 +91,7 @@ const ServicesCounselingMoveAllowances = () => { organizationalClothingAndIndividualEquipment, storageInTransit, gunSafe, + weightRestriction, accompaniedTour, dependentsTwelveAndOver, dependentsUnderTwelve, @@ -106,6 +112,7 @@ const ServicesCounselingMoveAllowances = () => { storageInTransit: Number(storageInTransit), organizationalClothingAndIndividualEquipment, gunSafe, + weightRestriction: Number(weightRestriction), accompaniedTour, dependentsTwelveAndOver: Number(dependentsTwelveAndOver), dependentsUnderTwelve: Number(dependentsUnderTwelve), @@ -121,6 +128,7 @@ const ServicesCounselingMoveAllowances = () => { requiredMedicalEquipmentWeight, organizationalClothingAndIndividualEquipment, gunSafe, + weightRestriction, storageInTransit, dependentsUnderTwelve, dependentsTwelveAndOver, @@ -136,6 +144,7 @@ const ServicesCounselingMoveAllowances = () => { requiredMedicalEquipmentWeight: `${requiredMedicalEquipmentWeight}`, storageInTransit: `${storageInTransit}`, gunSafe, + weightRestriction: `${weightRestriction}`, organizationalClothingAndIndividualEquipment, accompaniedTour, dependentsUnderTwelve: `${dependentsUnderTwelve}`, diff --git a/src/pages/Office/ServicesCounselingMoveDetails/ServicesCounselingMoveDetails.jsx b/src/pages/Office/ServicesCounselingMoveDetails/ServicesCounselingMoveDetails.jsx index 7ab63c0a024..7fe85a56517 100644 --- a/src/pages/Office/ServicesCounselingMoveDetails/ServicesCounselingMoveDetails.jsx +++ b/src/pages/Office/ServicesCounselingMoveDetails/ServicesCounselingMoveDetails.jsx @@ -53,8 +53,8 @@ import NotificationScrollToTop from 'components/NotificationScrollToTop'; import { objectIsMissingFieldWithCondition } from 'utils/displayFlags'; import { ReviewButton } from 'components/form/IconButtons'; import { calculateWeightRequested } from 'hooks/custom'; -import { ADVANCE_STATUSES } from 'constants/ppms'; import { isBooleanFlagEnabled } from 'utils/featureFlags'; +import { ADVANCE_STATUSES } from 'constants/ppms'; const ServicesCounselingMoveDetails = ({ infoSavedAlert, @@ -387,6 +387,7 @@ const ServicesCounselingMoveDetails = ({ requiredMedicalEquipmentWeight: allowances.requiredMedicalEquipmentWeight, organizationalClothingAndIndividualEquipment: allowances.organizationalClothingAndIndividualEquipment, gunSafe: allowances.gunSafe, + weightRestriction: allowances.weightRestriction, dependentsUnderTwelve: allowances.dependentsUnderTwelve, dependentsTwelveAndOver: allowances.dependentsTwelveAndOver, accompaniedTour: allowances.accompaniedTour, @@ -878,7 +879,7 @@ const ServicesCounselingMoveDetails = ({ } ppmShipmentInfoNeedsApproval={ppmShipmentsInfoNeedsApproval} > - +
diff --git a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx index 44b11a5567d..5ba55412a60 100644 --- a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx +++ b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx @@ -433,6 +433,7 @@ const ServicesCounselingQueue = ({ isQueueManagementFFEnabled, officeUser, isBulkAssignmentFFEnabled, + activeRole, }) => { const { queueType } = useParams(); const { data, isLoading, isError } = useUserQueries(); @@ -670,6 +671,7 @@ const ServicesCounselingQueue = ({ key={queueType} isSupervisor={supervisor} isBulkAssignmentFFEnabled={isBulkAssignmentFFEnabled} + activeRole={activeRole} />
); @@ -699,6 +701,7 @@ const ServicesCounselingQueue = ({ key={queueType} isSupervisor={supervisor} isBulkAssignmentFFEnabled={isBulkAssignmentFFEnabled} + activeRole={activeRole} />
); diff --git a/src/pages/Office/index.jsx b/src/pages/Office/index.jsx index 8a945c4c7f8..69f55fbf90c 100644 --- a/src/pages/Office/index.jsx +++ b/src/pages/Office/index.jsx @@ -292,7 +292,7 @@ export class OfficeApp extends Component { end element={ - + } /> @@ -301,7 +301,10 @@ export class OfficeApp extends Component { path="/invoicing/queue" element={ - + } /> @@ -311,7 +314,10 @@ export class OfficeApp extends Component { end element={ - + } /> @@ -336,6 +342,7 @@ export class OfficeApp extends Component { userPrivileges={userPrivileges} isQueueManagementFFEnabled={queueManagementFlag} isBulkAssignmentFFEnabled={bulkAssignmentFlag} + activeRole={activeRole} /> } @@ -367,6 +374,7 @@ export class OfficeApp extends Component { isQueueManagementFFEnabled={queueManagementFlag} userPrivileges={userPrivileges} isBulkAssignmentFFEnabled={bulkAssignmentFlag} + activeRole={activeRole} /> } @@ -382,6 +390,7 @@ export class OfficeApp extends Component { isQueueManagementFFEnabled={queueManagementFlag} userPrivileges={userPrivileges} isBulkAssignmentFFEnabled={bulkAssignmentFlag} + activeRole={activeRole} /> } @@ -394,7 +403,10 @@ export class OfficeApp extends Component { end element={ - + } /> 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/Shipment/PrimeUIShipmentCreate.test.jsx b/src/pages/PrimeUI/Shipment/PrimeUIShipmentCreate.test.jsx index 370372ffe9c..13ea0f32af6 100644 --- a/src/pages/PrimeUI/Shipment/PrimeUIShipmentCreate.test.jsx +++ b/src/pages/PrimeUI/Shipment/PrimeUIShipmentCreate.test.jsx @@ -264,6 +264,44 @@ describe('Error when submitting', () => { }); }); +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/scenes/Office/ducks.js b/src/scenes/Office/ducks.js index 676ca143ecb..808841e4337 100644 --- a/src/scenes/Office/ducks.js +++ b/src/scenes/Office/ducks.js @@ -1,3 +1,4 @@ +/* istanbul ignore file */ // SINGLE RESOURCE ACTION TYPES const REMOVE_BANNER = 'REMOVE_BANNER'; const SHOW_BANNER = 'SHOW_BANNER'; diff --git a/src/services/adminApi.js b/src/services/adminApi.js index de3de33deeb..e44df562111 100644 --- a/src/services/adminApi.js +++ b/src/services/adminApi.js @@ -1,3 +1,4 @@ +/* istanbul ignore file */ import Swagger from 'swagger-client'; import { makeSwaggerRequest, requestInterceptor, responseInterceptor, makeSwaggerRequestRaw } from './swaggerRequest'; @@ -38,3 +39,15 @@ export async function updateRequestedOfficeUser(officeUserId, body) { { normalize: false }, ); } + +export async function deleteOfficeUser(officeUserId) { + const operationPath = 'Office users.deleteOfficeUser'; + + return makeAdminRequest( + operationPath, + { + officeUserId, + }, + { normalize: false }, + ); +} diff --git a/src/services/ghcApi.js b/src/services/ghcApi.js index 1b787b7b8ad..da479d2f63e 100644 --- a/src/services/ghcApi.js +++ b/src/services/ghcApi.js @@ -618,7 +618,7 @@ export function deleteShipment({ shipmentID, normalize = false, schemaKey = 'shi export async function getMovesQueue( key, - { sort, order, filters = [], currentPage = 1, currentPageSize = 20, viewAsGBLOC }, + { sort, order, filters = [], currentPage = 1, currentPageSize = 20, viewAsGBLOC, activeRole }, ) { const operationPath = 'queues.getMovesQueue'; const paramFilters = {}; @@ -627,14 +627,23 @@ export async function getMovesQueue( }); return makeGHCRequest( operationPath, - { sort, order, page: currentPage, perPage: currentPageSize, viewAsGBLOC, ...paramFilters }, + { sort, order, page: currentPage, perPage: currentPageSize, viewAsGBLOC, activeRole, ...paramFilters }, { schemaKey: 'queueMovesResult', normalize: false }, ); } export async function getServicesCounselingQueue( key, - { sort, order, filters = [], currentPage = 1, currentPageSize = 20, needsPPMCloseout = false, viewAsGBLOC }, + { + sort, + order, + filters = [], + currentPage = 1, + currentPageSize = 20, + needsPPMCloseout = false, + viewAsGBLOC, + activeRole, + }, ) { const operationPath = 'queues.getServicesCounselingQueue'; const paramFilters = {}; @@ -651,6 +660,7 @@ export async function getServicesCounselingQueue( perPage: currentPageSize, needsPPMCloseout, viewAsGBLOC, + activeRole, ...paramFilters, }, @@ -674,7 +684,16 @@ export async function getServicesCounselingOriginLocations(needsPPMCloseout, vie export async function getServicesCounselingPPMQueue( key, - { sort, order, filters = [], currentPage = 1, currentPageSize = 20, needsPPMCloseout = true, viewAsGBLOC }, + { + sort, + order, + filters = [], + currentPage = 1, + currentPageSize = 20, + needsPPMCloseout = true, + viewAsGBLOC, + activeRole, + }, ) { const operationPath = 'queues.getServicesCounselingQueue'; const paramFilters = {}; @@ -684,14 +703,23 @@ export async function getServicesCounselingPPMQueue( return makeGHCRequest( operationPath, - { sort, order, page: currentPage, perPage: currentPageSize, needsPPMCloseout, viewAsGBLOC, ...paramFilters }, + { + sort, + order, + page: currentPage, + perPage: currentPageSize, + needsPPMCloseout, + viewAsGBLOC, + ...paramFilters, + activeRole, + }, { schemaKey: 'queueMovesResult', normalize: false }, ); } export async function getPaymentRequestsQueue( key, - { sort, order, filters = [], currentPage = 1, currentPageSize = 20, viewAsGBLOC }, + { sort, order, filters = [], currentPage = 1, currentPageSize = 20, viewAsGBLOC, activeRole }, ) { const operationPath = 'queues.getPaymentRequestsQueue'; const paramFilters = {}; @@ -700,7 +728,7 @@ export async function getPaymentRequestsQueue( }); return makeGHCRequest( operationPath, - { sort, order, page: currentPage, perPage: currentPageSize, viewAsGBLOC, ...paramFilters }, + { sort, order, page: currentPage, perPage: currentPageSize, viewAsGBLOC, activeRole, ...paramFilters }, { schemaKey: 'queuePaymentRequestsResult', normalize: false }, ); } @@ -763,6 +791,10 @@ export async function getGBLOCs() { return makeGHCRequest(operationPath, {}, { normalize: false }); } +export async function showCounselingOffices(dutyLocationId, serviceMemberId) { + return makeGHCRequestRaw('transportationOffice.showCounselingOffices', { dutyLocationId, serviceMemberId }); +} + export const reviewShipmentAddressUpdate = async ({ shipmentID, ifMatchETag, body }) => { const operationPath = 'shipment.reviewShipmentAddressUpdate'; const schemaKey = 'ShipmentAddressUpdate'; @@ -904,7 +936,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/ToolTip/ToolTip.test.jsx b/src/shared/ToolTip/ToolTip.test.jsx index 0e48e50d9d6..4ae28c34d57 100644 --- a/src/shared/ToolTip/ToolTip.test.jsx +++ b/src/shared/ToolTip/ToolTip.test.jsx @@ -57,7 +57,6 @@ describe('ToolTip', () => { // Assert that the tooltip content is displayed expect(tooltipContent.text()).toBe(text); }); - it('should display tooltip with title when provided', () => { const titleText = 'Tooltip Title'; const bodyText = 'Tooltip Body'; @@ -96,7 +95,6 @@ describe('ToolTip', () => { const tooltipIcon = screen.getByTestId('tooltip-container'); expect(tooltipIcon).toBeInTheDocument(); }); - it('should display a large tooltip', () => { const text = 'Test Text'; const component = mount(); diff --git a/src/shared/styles/_custom.scss b/src/shared/styles/_custom.scss index 7b178084322..f1adceb5e7e 100644 --- a/src/shared/styles/_custom.scss +++ b/src/shared/styles/_custom.scss @@ -345,3 +345,30 @@ table { } } } + +// For displaying payment request details/rows in Storybook +.expandableServiceItemRow { + .shipmentCard { + @include u-padding-left(4); + } + + .table--stacked { + width: 100%; + table-layout: fixed; + th { + @include u-font-weight(bold); + @include u-font-size('body', '3xs'); + color: $base-darker; + border-top: 0; + + &:not(:first-child) { + text-align: right; + } + } + + td:not(:first-child) { + @include u-font-weight(bold); + text-align: right; + } + } +} diff --git a/src/styles/form.module.scss b/src/styles/form.module.scss index 990fa6474eb..1e81e16f936 100644 --- a/src/styles/form.module.scss +++ b/src/styles/form.module.scss @@ -10,7 +10,6 @@ @include typeset('body', 11, 2); @include u-margin-y(4); } - .hide { display: none; } diff --git a/src/utils/formatters.js b/src/utils/formatters.js index 53f0afa3567..d979dcdc624 100644 --- a/src/utils/formatters.js +++ b/src/utils/formatters.js @@ -601,6 +601,27 @@ export const constructSCOrderOconusFields = (values) => { }; }; +export const formatAssignedOfficeUserFromContext = (historyRecord) => { + const { changedValues, context, oldValues } = historyRecord; + const newValues = {}; + if (!context) return newValues; + + const name = `${context[0].assigned_office_user_last_name}, ${context[0].assigned_office_user_first_name}`; + + if (changedValues?.sc_assigned_id) { + if (oldValues.sc_assigned_id === null) newValues.assigned_sc = name; + if (oldValues.sc_assigned_id !== null) newValues.re_assigned_sc = name; + } + if (changedValues?.too_assigned_id) { + if (oldValues.too_assigned_id === null) newValues.assigned_too = name; + if (oldValues.too_assigned_id !== null) newValues.re_assigned_too = name; + } + if (changedValues?.tio_assigned_id) { + if (oldValues.tio_assigned_id === null) newValues.assigned_tio = name; + if (oldValues.tio_assigned_id !== null) newValues.re_assigned_tio = name; + } + return newValues; +}; /** * @description Converts a string to title case (capitalizes the first letter of each word) * @param {string} str - The input string to format. diff --git a/src/utils/formatters.test.js b/src/utils/formatters.test.js index 387fae0bef7..97b99f470a3 100644 --- a/src/utils/formatters.test.js +++ b/src/utils/formatters.test.js @@ -349,6 +349,62 @@ describe('formatters', () => { }); }); +describe('formatAssignedOfficeUserFromContext', () => { + it('properly formats an SCs name', () => { + const values = { + changedValues: { + sc_assigned_id: 'fb625e3c-067c-49d7-8fd9-88ef040e6137', + }, + oldValues: { + sc_assigned_id: null, + }, + context: [{ assigned_office_user_last_name: 'Daniels', assigned_office_user_first_name: 'Jayden' }], + }; + + const result = formatters.formatAssignedOfficeUserFromContext(values); + + expect(result).toEqual({ + assigned_sc: 'Daniels, Jayden', + }); + }); + + it('properly formats a TOOs name', () => { + const values = { + changedValues: { + too_assigned_id: 'fb625e3c-067c-49d7-8fd9-88ef040e6137', + }, + oldValues: { + too_assigned_id: null, + }, + context: [{ assigned_office_user_last_name: 'McLaurin', assigned_office_user_first_name: 'Terry' }], + }; + + const result = formatters.formatAssignedOfficeUserFromContext(values); + + expect(result).toEqual({ + assigned_too: 'McLaurin, Terry', + }); + }); + + it('properly formats a TIOs name', () => { + const values = { + changedValues: { + tio_assigned_id: 'fb625e3c-067c-49d7-8fd9-88ef040e6137', + }, + oldValues: { + tio_assigned_id: null, + }, + context: [{ assigned_office_user_last_name: 'Robinson', assigned_office_user_first_name: 'Brian' }], + }; + + const result = formatters.formatAssignedOfficeUserFromContext(values); + + expect(result).toEqual({ + assigned_tio: 'Robinson, Brian', + }); + }); +}); + describe('constructSCOrderOconusFields', () => { it('returns null for all fields if not OCONUS and no dependents', () => { const values = { diff --git a/src/utils/ppmCloseout.jsx b/src/utils/ppmCloseout.jsx index 6ee33434c7b..2a2a121107f 100644 --- a/src/utils/ppmCloseout.jsx +++ b/src/utils/ppmCloseout.jsx @@ -5,6 +5,7 @@ import moment from 'moment'; import { formatCents, formatCentsTruncateWhole, formatCustomerDate, formatWeight } from 'utils/formatters'; import { expenseTypeLabels, expenseTypes } from 'constants/ppmExpenseTypes'; import { isExpenseComplete, isWeightTicketComplete, isProGearComplete } from 'utils/shipments'; +import PPMDocumentsStatus from 'constants/ppms'; export const getW2Address = (address) => { const addressLine1 = address?.streetAddress2 @@ -166,7 +167,10 @@ export const formatExpenseItems = (expenses, editPath, editParams, handleDelete) }; export const calculateTotalMovingExpensesAmount = (movingExpenses = []) => { + const excludedExpenseStatuses = [PPMDocumentsStatus.EXCLUDED, PPMDocumentsStatus.REJECTED]; // EXCLUDED and REJECTED expenses aren't included in the total. return movingExpenses.reduce((prev, curr) => { - return curr.amount && !Number.isNaN(Number(curr.amount)) ? prev + curr.amount : prev; + return curr.amount && !Number.isNaN(Number(curr.amount)) && !excludedExpenseStatuses.includes(curr.status) + ? prev + curr.amount + : prev; }, 0); }; diff --git a/src/utils/ppmCloseout.test.jsx b/src/utils/ppmCloseout.test.jsx index 4c8df5e07d5..b168a896eaf 100644 --- a/src/utils/ppmCloseout.test.jsx +++ b/src/utils/ppmCloseout.test.jsx @@ -1,6 +1,7 @@ import { calculateTotalMovingExpensesAmount, formatExpenseItems } from 'utils/ppmCloseout'; import { createCompleteMovingExpense, createCompleteSITMovingExpense } from 'utils/test/factories/movingExpense'; import { expenseTypeLabels } from 'constants/ppmExpenseTypes'; +import PPMDocumentsStatus from 'constants/ppms'; describe('formatExpenseItems', () => { it.each([ @@ -50,3 +51,31 @@ describe('calculateTotalMovingExpensesAmount', () => { expect(calculateTotalMovingExpensesAmount(expenses)).toEqual(expectedTotal); }); }); + +describe('calculateTotalMovingExpensesAmount with rejected and excluded amount', () => { + it('rejected and excluded expenses are not included in total amount', () => { + const approvedMovingExpense1 = createCompleteMovingExpense( + {}, + { status: PPMDocumentsStatus.APPROVED, amount: 350 }, + ); + const approvedMovingExpense2 = createCompleteMovingExpense( + {}, + { status: PPMDocumentsStatus.APPROVED, amount: 650 }, + ); + const approveAmountTotal = approvedMovingExpense1.amount + approvedMovingExpense2.amount; + const rejectedMovingExpense = createCompleteMovingExpense({}, { status: PPMDocumentsStatus.REJECTED, amount: 123 }); + const excludedMovingExpense = createCompleteMovingExpense({}, { status: PPMDocumentsStatus.EXCLUDED, amount: 456 }); + expect(approvedMovingExpense1.amount).toBeGreaterThan(0); + expect(approvedMovingExpense2.amount).toBeGreaterThan(0); + expect(rejectedMovingExpense.amount).toBeGreaterThan(0); + expect(excludedMovingExpense.amount).toBeGreaterThan(0); + expect( + calculateTotalMovingExpensesAmount([ + approvedMovingExpense1, + approvedMovingExpense2, + rejectedMovingExpense, + excludedMovingExpense, + ]), + ).toEqual(approveAmountTotal); + }); +}); diff --git a/src/utils/shipmentWeights.js b/src/utils/shipmentWeights.js index 08d57c8130d..58cd6e8ab73 100644 --- a/src/utils/shipmentWeights.js +++ b/src/utils/shipmentWeights.js @@ -4,6 +4,7 @@ import returnLowestValue from './returnLowestValue'; import { SHIPMENT_OPTIONS } from 'shared/constants'; +import PPMDocumentsStatus from 'constants/ppms'; // eslint-disable-next-line import/prefer-default-export export function shipmentIsOverweight(estimatedWeight, weightCap) { @@ -34,7 +35,11 @@ export const getDisplayWeight = (shipment, weightAdjustment = 1.0) => { }; export const calculateNetWeightForProGearWeightTicket = (weightTicket) => { - if (weightTicket.weight == null || Number.isNaN(Number(weightTicket.weight))) { + if ( + weightTicket.weight == null || + Number.isNaN(Number(weightTicket.weight)) || + weightTicket.status === PPMDocumentsStatus.REJECTED + ) { return 0; } @@ -61,7 +66,7 @@ export const calculateWeightTicketWeightDifference = (weightTicket) => { }; export const getWeightTicketNetWeight = (weightTicket) => { - if (weightTicket.status !== 'REJECTED') + if (weightTicket.status !== PPMDocumentsStatus.REJECTED) return weightTicket.adjustedNetWeight ?? calculateWeightTicketWeightDifference(weightTicket); return 0; }; diff --git a/src/utils/shipmentWeights.test.js b/src/utils/shipmentWeights.test.js index a3ec57823e4..4f4e7616358 100644 --- a/src/utils/shipmentWeights.test.js +++ b/src/utils/shipmentWeights.test.js @@ -10,7 +10,10 @@ import { shipmentIsOverweight, getWeightTicketNetWeight, } from './shipmentWeights'; -import { createCompleteProGearWeightTicket } from './test/factories/proGearWeightTicket'; +import { + createCompleteProGearWeightTicket, + createRejectedProGearWeightTicket, +} from './test/factories/proGearWeightTicket'; import { createCompleteWeightTicket } from './test/factories/weightTicket'; describe('shipmentWeights utils', () => { @@ -202,6 +205,13 @@ describe('calculateNetWeightForProGearWeightTicket', () => { expect(calculateNetWeightForProGearWeightTicket(proGearWeightTicket)).toEqual(expectedNetWeight); }, ); + + it('rejected weight ticket net weight is zero', () => { + const rejectedProGearWeightTicket = createRejectedProGearWeightTicket({}, { weight: 200 }); + // The weight of the ticket should be greater than zero for this test to be valid. + expect(rejectedProGearWeightTicket.weight).toBeGreaterThan(0); + expect(calculateNetWeightForProGearWeightTicket(rejectedProGearWeightTicket)).toEqual(0); + }); }); describe('calculateTotalNetWeightForProGearWeightTickets', () => { @@ -226,6 +236,20 @@ describe('calculateTotalNetWeightForProGearWeightTickets', () => { }); }); +describe('calculateTotalNetWeightForProGearWeightTickets with a rejected weight ticket', () => { + it('rejected weight ticket is not included in total net weight', () => { + const approvedWeight = 350; + const approvedProGearWeightTicket = createCompleteProGearWeightTicket({}, { weight: approvedWeight }); + const rejectedProGearWeightTicket = createRejectedProGearWeightTicket({}, { weight: 200 }); + // The weight of each ticket should be greater than zero for this test to be valid. + expect(approvedProGearWeightTicket.weight).toBeGreaterThan(0); + expect(rejectedProGearWeightTicket.weight).toBeGreaterThan(0); + expect( + calculateTotalNetWeightForProGearWeightTickets([approvedProGearWeightTicket, rejectedProGearWeightTicket]), + ).toEqual(approvedWeight); + }); +}); + describe('Calculating shipment net weights', () => { const ppmShipments = [ { diff --git a/src/utils/test/factories/proGearWeightTicket.js b/src/utils/test/factories/proGearWeightTicket.js index 767dbe667a6..1935bc1b4a8 100644 --- a/src/utils/test/factories/proGearWeightTicket.js +++ b/src/utils/test/factories/proGearWeightTicket.js @@ -3,6 +3,7 @@ import { v4 } from 'uuid'; import createUpload from 'utils/test/factories/upload'; import { createDocumentWithoutUploads } from 'utils/test/factories/document'; +import PPMDocumentsStatus from 'constants/ppms'; const createBaseProGearWeightTicket = ({ serviceMemberId, creationDate = new Date() } = {}, fieldOverrides = {}) => { const createdAt = creationDate.toISOString(); @@ -81,8 +82,22 @@ const createCompleteProGearWeightTicketWithConstructedWeight = ( return weightTicket; }; +const createRejectedProGearWeightTicket = ({ serviceMemberId, creationDate } = {}, fieldOverrides = {}) => { + const fullFieldOverrides = { + belongsToSelf: true, + description: 'Laptop', + hasWeightTickets: true, + weight: 150, + ...fieldOverrides, + }; + const weightTicket = createBaseProGearWeightTicket({ serviceMemberId, creationDate }, fullFieldOverrides); + weightTicket.status = PPMDocumentsStatus.REJECTED; + return weightTicket; +}; + export { createBaseProGearWeightTicket, createCompleteProGearWeightTicket, createCompleteProGearWeightTicketWithConstructedWeight, + createRejectedProGearWeightTicket, }; diff --git a/src/utils/validation.js b/src/utils/validation.js index 6053d83ce30..51175dcf296 100644 --- a/src/utils/validation.js +++ b/src/utils/validation.js @@ -56,7 +56,6 @@ export const IsSupportedState = async (value, context) => { const selectedState = value; const enableAK = 'enabledAK'; - let unsupportedStates; if (enableAK in context.options.context) { unsupportedStates = context.options.context.enabledAK ? unSupportedStates : unSupportedStatesDisabledAlaska; diff --git a/swagger-def/admin.yaml b/swagger-def/admin.yaml index a7c7dc058e0..ccbce27b6bb 100644 --- a/swagger-def/admin.yaml +++ b/swagger-def/admin.yaml @@ -1552,6 +1552,29 @@ paths: description: Not authorized to update an Office User "500": description: Server error + delete: + produces: + - application/json + summary: Deletes an Office User + description: Deletes a single office user in any status. This endpoint is used in the Admin UI that will allow the admin user to delete an office user. + operationId: deleteOfficeUser + tags: + - Office users + parameters: + - in: path + name: officeUserId + type: string + format: uuid + required: true + responses: + '204': + description: deleted + "401": + description: request requires user authentication + "404": + description: Office User not found + "500": + description: server error /admin-users: get: produces: diff --git a/swagger-def/definitions/PPMCloseout.yaml b/swagger-def/definitions/PPMCloseout.yaml index 20203d0e4e7..f7b420a0e7b 100644 --- a/swagger-def/definitions/PPMCloseout.yaml +++ b/swagger-def/definitions/PPMCloseout.yaml @@ -6,7 +6,6 @@ properties: format: uuid type: string readOnly: true - plannedMoveDate: description: > Date the customer expects to begin their move. @@ -27,32 +26,27 @@ properties: type: integer x-nullable: true x-omitempty: false - estimatedWeight: description: The estimated weight of the PPM shipment goods being moved. type: integer example: 4200 x-nullable: true x-omitempty: false - actualWeight: example: 2000 type: integer x-nullable: true x-omitempty: false - proGearWeightCustomer: description: The estimated weight of the pro-gear being moved belonging to the service member. type: integer x-nullable: true x-omitempty: false - proGearWeightSpouse: description: The estimated weight of the pro-gear being moved belonging to a spouse. type: integer x-nullable: true x-omitempty: false - grossIncentive: description: > The final calculated incentive for the PPM shipment. This does not include **SIT** as it is a reimbursement. @@ -61,7 +55,6 @@ properties: x-nullable: true x-omitempty: false readOnly: true - gcc: description: Government Constructive Cost (GCC) type: integer @@ -69,75 +62,82 @@ properties: format: cents x-nullable: true x-omitempty: false - aoa: description: Advance Operating Allowance (AOA). type: integer format: cents x-nullable: true x-omitempty: false - remainingIncentive: description: The remaining reimbursement amount that is still owed to the customer. type: integer format: cents x-nullable: true x-omitempty: false - haulType: description: The type of haul calculation used for this shipment (shorthaul or linehaul). type: string x-nullable: true x-omitempty: false - haulPrice: description: The price of the linehaul or shorthaul. type: integer format: cents x-nullable: true x-omitempty: false - haulFSC: description: The linehaul/shorthaul Fuel Surcharge (FSC). type: integer format: cents x-nullable: true x-omitempty: false - dop: description: The Domestic Origin Price (DOP). type: integer format: cents x-nullable: true x-omitempty: false - ddp: description: The Domestic Destination Price (DDP). type: integer format: cents x-nullable: true x-omitempty: false - packPrice: description: The full price of all packing/unpacking services. type: integer format: cents x-nullable: true x-omitempty: false - unpackPrice: description: The full price of all packing/unpacking services. type: integer format: cents x-nullable: true x-omitempty: false - + intlPackPrice: + description: The full price of international packing (IHPK) + type: integer + format: cents + x-nullable: true + x-omitempty: false + intlUnpackPrice: + description: The full price of international unpacking (IHUPK) + type: integer + format: cents + x-nullable: true + x-omitempty: false + intlLinehaulPrice: + description: The full price of international shipping and linehaul (ISLH) + type: integer + format: cents + x-nullable: true + x-omitempty: false SITReimbursement: description: The estimated amount that the government will pay the service member to put their goods into storage. This estimated storage cost is separate from the estimated incentive. type: integer format: cents x-nullable: true x-omitempty: false - required: - id diff --git a/swagger-def/definitions/prime/Entitlements.yaml b/swagger-def/definitions/prime/Entitlements.yaml index 5722870f9d7..83a989da2b8 100644 --- a/swagger-def/definitions/prime/Entitlements.yaml +++ b/swagger-def/definitions/prime/Entitlements.yaml @@ -54,6 +54,11 @@ properties: totalDependents: example: 2 type: integer + weightRestriction: + example: 1500 + type: integer + x-formatting: weight + x-nullable: true eTag: type: string readOnly: true diff --git a/swagger-def/definitions/prime/MTOServiceItemInternationalShuttle.yaml b/swagger-def/definitions/prime/MTOServiceItemInternationalShuttle.yaml new file mode 100644 index 00000000000..137aed40a02 --- /dev/null +++ b/swagger-def/definitions/prime/MTOServiceItemInternationalShuttle.yaml @@ -0,0 +1,47 @@ +description: Describes an international shuttle service item. +allOf: + - $ref: 'MTOServiceItem.yaml' + - type: object + properties: + reServiceCode: + type: string + description: > + A unique code for the service item. Indicates if shuttling is requested for the international shipment origin (`IOSHUT`) + or destination (`IDSHUT`). + enum: + - IOSHUT # International Origin Shuttle Service + - IDSHUT # International Destination Shuttle Service + reason: + type: string + example: Storage items need to be picked up. + 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. + estimatedWeight: + type: integer + example: 4200 + description: An estimate of how much weight from a shipment will be included in the shuttling service. + x-nullable: true + x-omitempty: false + actualWeight: + type: integer + example: 4000 + description: A record of the actual weight that was shuttled. Provided by the movers, based on weight tickets. + x-nullable: true + x-omitempty: false + requestApprovalsRequestedStatus: + description: Indicates if "Approvals Requested" status is being requested. + type: boolean + x-nullable: true + market: + type: string + enum: + - CONUS + - OCONUS + example: CONUS + description: >- + To identify whether the service was provided within (CONUS) or + (OCONUS) + required: + - reason + - reServiceCode diff --git a/swagger-def/definitions/prime/MTOServiceItemModelType.yaml b/swagger-def/definitions/prime/MTOServiceItemModelType.yaml index f96badf678a..4d0ed5ad684 100644 --- a/swagger-def/definitions/prime/MTOServiceItemModelType.yaml +++ b/swagger-def/definitions/prime/MTOServiceItemModelType.yaml @@ -7,6 +7,7 @@ description: > * DOFSIT, DOASIT - MTOServiceItemOriginSIT * DDFSIT, DDASIT - MTOServiceItemDestSIT * DOSHUT, DDSHUT - MTOServiceItemShuttle + * IOSHUT, IDSHUT - MTOServiceItemInternationalShuttle * DCRT, DUCRT - MTOServiceItemDomesticCrating * ICRT, IUCRT - MTOServiceItemInternationalCrating * PODFSC, POEFSC - MTOSerivceItemInternationalFuelSurcharge @@ -18,6 +19,7 @@ enum: - MTOServiceItemOriginSIT - MTOServiceItemDestSIT - MTOServiceItemShuttle + - MTOServiceItemInternationalShuttle - MTOServiceItemDomesticCrating - MTOServiceItemInternationalCrating - MTOSerivceItemInternationalFuelSurcharge diff --git a/swagger-def/ghc.yaml b/swagger-def/ghc.yaml index df06a4ca220..38a5c9efb53 100644 --- a/swagger-def/ghc.yaml +++ b/swagger-def/ghc.yaml @@ -1,4 +1,4 @@ -swagger: '2.0' +swagger: "2.0" info: contact: email: milmove-developers@caci.com @@ -6,7 +6,7 @@ info: $ref: info/ghc_description.md license: name: MIT - url: 'https://opensource.org/licenses/MIT' + url: "https://opensource.org/licenses/MIT" title: MilMove GHC API version: 0.0.1 basePath: /ghc/v1 @@ -15,7 +15,7 @@ schemes: tags: - name: queues - name: move - - $ref: 'tags/order.yaml' + - $ref: "tags/order.yaml" - name: moveTaskOrder - name: customer - name: mtoServiceItem @@ -31,7 +31,7 @@ tags: - name: paymentRequests - name: reServiceItems paths: - '/customer': + "/customer": post: summary: Creates a customer with Okta option description: Creates a customer with option to also create an Okta profile account based on the office user's input when completing the UI form and submitting. @@ -47,28 +47,28 @@ paths: name: body required: true schema: - $ref: '#/definitions/CreateCustomerPayload' + $ref: "#/definitions/CreateCustomerPayload" responses: - '200': + "200": description: successfully created the customer schema: - $ref: '#/definitions/CreatedCustomer' - '400': - $ref: '#/responses/InvalidRequest' - '401': - $ref: '#/responses/PermissionDenied' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '409': - $ref: '#/responses/Conflict' - '412': - $ref: '#/responses/PreconditionFailed' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' + $ref: "#/definitions/CreatedCustomer" + "400": + $ref: "#/responses/InvalidRequest" + "401": + $ref: "#/responses/PermissionDenied" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "409": + $ref: "#/responses/Conflict" + "412": + $ref: "#/responses/PreconditionFailed" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" /open/requested-office-users: post: consumes: @@ -92,19 +92,19 @@ paths: name: officeUser description: Office User information schema: - $ref: '#/definitions/OfficeUserCreate' + $ref: "#/definitions/OfficeUserCreate" responses: - '201': + "201": description: successfully requested the creation of provided office user schema: - $ref: '#/definitions/OfficeUser' - '422': + $ref: "#/definitions/OfficeUser" + "422": description: validation error schema: - $ref: '#/definitions/ValidationError' - '500': + $ref: "#/definitions/ValidationError" + "500": description: internal server error - '/customer/{customerID}': + "/customer/{customerID}": parameters: - description: ID of customer to use in: path @@ -117,20 +117,20 @@ paths: - application/json parameters: [] responses: - '200': + "200": description: Successfully retrieved information on an individual customer schema: - $ref: '#/definitions/Customer' - '400': - $ref: '#/responses/InvalidRequest' - '401': - $ref: '#/responses/PermissionDenied' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '500': - $ref: '#/responses/ServerError' + $ref: "#/definitions/Customer" + "400": + $ref: "#/responses/InvalidRequest" + "401": + $ref: "#/responses/PermissionDenied" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "500": + $ref: "#/responses/ServerError" tags: - customer description: Returns a given customer @@ -151,30 +151,30 @@ paths: name: body required: true schema: - $ref: '#/definitions/UpdateCustomerPayload' + $ref: "#/definitions/UpdateCustomerPayload" - in: header name: If-Match type: string required: true responses: - '200': + "200": description: updated instance of orders schema: - $ref: '#/definitions/Customer' - '400': - $ref: '#/responses/InvalidRequest' - '401': - $ref: '#/responses/PermissionDenied' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '412': - $ref: '#/responses/PreconditionFailed' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' + $ref: "#/definitions/Customer" + "400": + $ref: "#/responses/InvalidRequest" + "401": + $ref: "#/responses/PermissionDenied" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "412": + $ref: "#/responses/PreconditionFailed" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" x-permissions: - update.customer /customer/search: @@ -223,22 +223,30 @@ paths: sort: type: string x-nullable: true - enum: [customerName, edipi, emplid, branch, personalEmail, telephone] + enum: + [ + customerName, + edipi, + emplid, + branch, + personalEmail, + telephone, + ] order: type: string x-nullable: true enum: [asc, desc] description: field that results should be sorted by responses: - '200': + "200": description: Successfully returned all customers matching the criteria schema: - $ref: '#/definitions/SearchCustomersResult' - '403': - $ref: '#/responses/PermissionDenied' - '500': - $ref: '#/responses/ServerError' - '/move/{locator}': + $ref: "#/definitions/SearchCustomersResult" + "403": + $ref: "#/responses/PermissionDenied" + "500": + $ref: "#/responses/ServerError" + "/move/{locator}": parameters: - description: Code used to identify a move in the system in: path @@ -250,26 +258,26 @@ paths: - application/json parameters: [] responses: - '200': + "200": description: Successfully retrieved the individual move schema: - $ref: '#/definitions/Move' - '400': - $ref: '#/responses/InvalidRequest' - '401': - $ref: '#/responses/PermissionDenied' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '500': - $ref: '#/responses/ServerError' + $ref: "#/definitions/Move" + "400": + $ref: "#/responses/InvalidRequest" + "401": + $ref: "#/responses/PermissionDenied" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "500": + $ref: "#/responses/ServerError" tags: - move description: Returns a given move for a unique alphanumeric locator string summary: Returns a given move operationId: getMove - '/move/{locator}/history': + "/move/{locator}/history": parameters: - description: Code used to identify a move in the system in: path @@ -289,26 +297,26 @@ paths: type: integer description: results per page responses: - '200': + "200": description: Successfully retrieved the individual move history schema: - $ref: '#/definitions/MoveHistoryResult' - '400': - $ref: '#/responses/InvalidRequest' - '401': - $ref: '#/responses/PermissionDenied' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '500': - $ref: '#/responses/ServerError' + $ref: "#/definitions/MoveHistoryResult" + "400": + $ref: "#/responses/InvalidRequest" + "401": + $ref: "#/responses/PermissionDenied" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "500": + $ref: "#/responses/ServerError" tags: - move description: Returns the history for a given move for a unique alphanumeric locator string summary: Returns the history of an identified move operationId: getMoveHistory - '/moves/{moveID}/shipment-evaluation-reports-list': + "/moves/{moveID}/shipment-evaluation-reports-list": parameters: - description: Code used to identify a move in the system in: path @@ -320,26 +328,26 @@ paths: produces: - application/json responses: - '200': + "200": description: Successfully retrieved the move's evaluation reports schema: - $ref: '#/definitions/EvaluationReportList' - '400': - $ref: '#/responses/InvalidRequest' - '401': - $ref: '#/responses/PermissionDenied' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '500': - $ref: '#/responses/ServerError' + $ref: "#/definitions/EvaluationReportList" + "400": + $ref: "#/responses/InvalidRequest" + "401": + $ref: "#/responses/PermissionDenied" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "500": + $ref: "#/responses/ServerError" tags: - move description: Returns shipment evaluation reports for the specified move that are visible to the current office user summary: Returns shipment evaluation reports for the specified move that are visible to the current office user operationId: getMoveShipmentEvaluationReportsList - '/moves/{moveID}/counseling-evaluation-reports-list': + "/moves/{moveID}/counseling-evaluation-reports-list": parameters: - description: Code used to identify a move in the system in: path @@ -351,26 +359,26 @@ paths: produces: - application/json responses: - '200': + "200": description: Successfully retrieved the move's evaluation reports schema: - $ref: '#/definitions/EvaluationReportList' - '400': - $ref: '#/responses/InvalidRequest' - '401': - $ref: '#/responses/PermissionDenied' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '500': - $ref: '#/responses/ServerError' + $ref: "#/definitions/EvaluationReportList" + "400": + $ref: "#/responses/InvalidRequest" + "401": + $ref: "#/responses/PermissionDenied" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "500": + $ref: "#/responses/ServerError" tags: - move description: Returns counseling evaluation reports for the specified move that are visible to the current office user summary: Returns counseling evaluation reports for the specified move that are visible to the current office user operationId: getMoveCounselingEvaluationReportsList - '/moves/{moveID}/cancel': + "/moves/{moveID}/cancel": parameters: - description: ID of the move in: path @@ -385,22 +393,22 @@ paths: - application/json parameters: [] responses: - '200': + "200": description: Successfully canceled move schema: - $ref: '#/definitions/Move' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '409': - $ref: '#/responses/Conflict' - '412': - $ref: '#/responses/PreconditionFailed' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' + $ref: "#/definitions/Move" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "409": + $ref: "#/responses/Conflict" + "412": + $ref: "#/responses/PreconditionFailed" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" tags: - move description: cancels a move @@ -408,7 +416,7 @@ paths: summary: Cancels a move x-permissions: - update.cancelMoveFlag - '/counseling/orders/{orderID}': + "/counseling/orders/{orderID}": parameters: - description: ID of order to update in: path @@ -431,27 +439,27 @@ paths: name: body required: true schema: - $ref: '#/definitions/CounselingUpdateOrderPayload' + $ref: "#/definitions/CounselingUpdateOrderPayload" - in: header name: If-Match type: string required: true responses: - '200': + "200": description: updated instance of orders schema: - $ref: '#/definitions/Order' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '412': - $ref: '#/responses/PreconditionFailed' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' - '/orders': + $ref: "#/definitions/Order" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "412": + $ref: "#/responses/PreconditionFailed" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" + "/orders": post: summary: Creates an orders model for a logged-in user description: Creates an instance of orders tied to a service member, which allow for creation of a move and an entitlement. Orders are required before the creation of a move @@ -466,23 +474,23 @@ paths: - in: body name: createOrders schema: - $ref: '#/definitions/CreateOrders' + $ref: "#/definitions/CreateOrders" responses: - '200': + "200": description: created instance of orders schema: - $ref: '#/definitions/Order' - '400': + $ref: "#/definitions/Order" + "400": description: invalid request - '401': + "401": description: request requires user authentication - '403': + "403": description: user is not authorized - '422': - $ref: '#/responses/UnprocessableEntity' - '500': + "422": + $ref: "#/responses/UnprocessableEntity" + "500": description: internal server error - '/orders/{orderID}': + "/orders/{orderID}": parameters: - description: ID of order to use in: path @@ -505,30 +513,30 @@ paths: name: body required: true schema: - $ref: '#/definitions/UpdateOrderPayload' + $ref: "#/definitions/UpdateOrderPayload" - in: header name: If-Match type: string required: true responses: - '200': + "200": description: updated instance of orders schema: - $ref: '#/definitions/Order' - '400': - $ref: '#/responses/InvalidRequest' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '409': - $ref: '#/responses/Conflict' - '412': - $ref: '#/responses/PreconditionFailed' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' + $ref: "#/definitions/Order" + "400": + $ref: "#/responses/InvalidRequest" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "409": + $ref: "#/responses/Conflict" + "412": + $ref: "#/responses/PreconditionFailed" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" x-permissions: - update.orders get: @@ -536,26 +544,26 @@ paths: - application/json parameters: [] responses: - '200': + "200": description: Successfully retrieved order schema: - $ref: '#/definitions/Order' - '400': - $ref: '#/responses/InvalidRequest' - '401': - $ref: '#/responses/PermissionDenied' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '500': - $ref: '#/responses/ServerError' + $ref: "#/definitions/Order" + "400": + $ref: "#/responses/InvalidRequest" + "401": + $ref: "#/responses/PermissionDenied" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "500": + $ref: "#/responses/ServerError" tags: - order description: Gets an order operationId: getOrder summary: Gets an order by ID - '/orders/{orderID}/allowances': + "/orders/{orderID}/allowances": parameters: - description: ID of order to use in: path @@ -578,29 +586,29 @@ paths: name: body required: true schema: - $ref: '#/definitions/UpdateAllowancePayload' + $ref: "#/definitions/UpdateAllowancePayload" - in: header name: If-Match type: string required: true responses: - '200': + "200": description: updated instance of allowance schema: - $ref: '#/definitions/Order' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '412': - $ref: '#/responses/PreconditionFailed' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' + $ref: "#/definitions/Order" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "412": + $ref: "#/responses/PreconditionFailed" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" x-permissions: - update.allowances - '/orders/{orderID}/acknowledge-excess-weight-risk': + "/orders/{orderID}/acknowledge-excess-weight-risk": parameters: - description: ID of order to use in: path @@ -624,23 +632,23 @@ paths: type: string required: true responses: - '200': + "200": description: updated Move schema: - $ref: '#/definitions/Move' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '412': - $ref: '#/responses/PreconditionFailed' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' + $ref: "#/definitions/Move" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "412": + $ref: "#/responses/PreconditionFailed" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" x-permissions: - update.excessWeightRisk - '/orders/{orderID}/acknowledge-excess-unaccompanied-baggage-weight-risk': + "/orders/{orderID}/acknowledge-excess-unaccompanied-baggage-weight-risk": parameters: - description: ID of order to use in: path @@ -664,23 +672,23 @@ paths: type: string required: true responses: - '200': + "200": description: updated Move schema: - $ref: '#/definitions/Move' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '412': - $ref: '#/responses/PreconditionFailed' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' + $ref: "#/definitions/Move" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "412": + $ref: "#/responses/PreconditionFailed" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" x-permissions: - update.excessWeightRisk - '/orders/{orderID}/update-billable-weight': + "/orders/{orderID}/update-billable-weight": parameters: - description: ID of order to use in: path @@ -703,29 +711,29 @@ paths: name: body required: true schema: - $ref: '#/definitions/UpdateBillableWeightPayload' + $ref: "#/definitions/UpdateBillableWeightPayload" - in: header name: If-Match type: string required: true responses: - '200': + "200": description: updated Order schema: - $ref: '#/definitions/Order' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '412': - $ref: '#/responses/PreconditionFailed' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' + $ref: "#/definitions/Order" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "412": + $ref: "#/responses/PreconditionFailed" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" x-permissions: - update.billableWeight - '/orders/{orderID}/update-max-billable-weight/tio': + "/orders/{orderID}/update-max-billable-weight/tio": parameters: - description: ID of order to use in: path @@ -748,23 +756,23 @@ paths: name: body required: true schema: - $ref: '#/definitions/UpdateMaxBillableWeightAsTIOPayload' - - $ref: 'parameters/ifMatch.yaml' + $ref: "#/definitions/UpdateMaxBillableWeightAsTIOPayload" + - $ref: "parameters/ifMatch.yaml" responses: - '200': + "200": description: updated Order schema: - $ref: '#/definitions/Order' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '412': - $ref: '#/responses/PreconditionFailed' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' + $ref: "#/definitions/Order" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "412": + $ref: "#/responses/PreconditionFailed" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" x-permissions: - update.maxBillableWeight /orders/{orderID}/upload_amended_orders: @@ -789,23 +797,23 @@ paths: description: The file to upload. required: true responses: - '201': + "201": description: created upload schema: - $ref: 'definitions/Upload.yaml' - '400': + $ref: "definitions/Upload.yaml" + "400": description: invalid request schema: - $ref: '#/definitions/InvalidRequestResponsePayload' - '403': + $ref: "#/definitions/InvalidRequestResponsePayload" + "403": description: not authorized - '404': + "404": description: not found - '413': + "413": description: payload is too large - '500': + "500": description: server error - '/counseling/orders/{orderID}/allowances': + "/counseling/orders/{orderID}/allowances": parameters: - description: ID of order to use in: path @@ -828,27 +836,27 @@ paths: name: body required: true schema: - $ref: '#/definitions/CounselingUpdateAllowancePayload' + $ref: "#/definitions/CounselingUpdateAllowancePayload" - in: header name: If-Match type: string required: true responses: - '200': + "200": description: updated instance of allowance schema: - $ref: '#/definitions/Order' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '412': - $ref: '#/responses/PreconditionFailed' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' - '/move-task-orders/{moveTaskOrderID}': + $ref: "#/definitions/Order" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "412": + $ref: "#/responses/PreconditionFailed" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" + "/move-task-orders/{moveTaskOrderID}": parameters: - description: ID of move to use in: path @@ -860,26 +868,26 @@ paths: - application/json parameters: [] responses: - '200': + "200": description: Successfully retrieved move task order schema: - $ref: '#/definitions/MoveTaskOrder' - '400': - $ref: '#/responses/InvalidRequest' - '401': - $ref: '#/responses/PermissionDenied' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '500': - $ref: '#/responses/ServerError' + $ref: "#/definitions/MoveTaskOrder" + "400": + $ref: "#/responses/InvalidRequest" + "401": + $ref: "#/responses/PermissionDenied" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "500": + $ref: "#/responses/ServerError" tags: - moveTaskOrder description: Gets a move operationId: getMoveTaskOrder summary: Gets a move by ID - '/move_task_orders/{moveTaskOrderID}/mto_service_items': + "/move_task_orders/{moveTaskOrderID}/mto_service_items": parameters: - description: ID of move for mto service item to use in: path @@ -892,22 +900,22 @@ paths: - application/json parameters: [] responses: - '200': + "200": description: Successfully retrieved all line items for a move task order schema: - $ref: 'definitions/MTOServiceItems.yaml' - '404': - $ref: '#/responses/NotFound' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' + $ref: "definitions/MTOServiceItems.yaml" + "404": + $ref: "#/responses/NotFound" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" tags: - mtoServiceItem description: Gets all line items for a move operationId: listMTOServiceItems summary: Gets all line items for a move - '/mto-shipments': + "/mto-shipments": post: summary: createMTOShipment description: | @@ -934,21 +942,21 @@ paths: - in: body name: body schema: - $ref: '#/definitions/CreateMTOShipment' + $ref: "#/definitions/CreateMTOShipment" responses: - '200': + "200": description: Successfully created a MTO shipment. schema: - $ref: 'definitions/MTOShipment.yaml' - '400': - $ref: '#/responses/InvalidRequest' - '404': - $ref: '#/responses/NotFound' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' - '/move_task_orders/{moveTaskOrderID}/mto_shipments': + $ref: "definitions/MTOShipment.yaml" + "400": + $ref: "#/responses/InvalidRequest" + "404": + $ref: "#/responses/NotFound" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" + "/move_task_orders/{moveTaskOrderID}/mto_shipments": parameters: - description: ID of move task order for mto shipment to use in: path @@ -961,24 +969,24 @@ paths: - application/json parameters: [] responses: - '200': + "200": description: Successfully retrieved all mto shipments for a move task order schema: - $ref: '#/definitions/MTOShipments' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' + $ref: "#/definitions/MTOShipments" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" tags: - mtoShipment description: Gets all shipments for a move task order operationId: listMTOShipments summary: Gets all shipments for a move task order - '/shipments/{shipmentID}': + "/shipments/{shipmentID}": get: summary: fetches a shipment by ID description: fetches a shipment by ID @@ -995,20 +1003,20 @@ paths: format: uuid type: string responses: - '200': + "200": description: Successfully fetched the shipment schema: - $ref: '#/definitions/MTOShipment' - '400': - $ref: '#/responses/InvalidRequest' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' + $ref: "#/definitions/MTOShipment" + "400": + $ref: "#/responses/InvalidRequest" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" delete: summary: Soft deletes a shipment by ID description: Soft deletes a shipment by ID @@ -1025,21 +1033,21 @@ paths: format: uuid type: string responses: - '204': + "204": description: Successfully soft deleted the shipment - '400': - $ref: '#/responses/InvalidRequest' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '409': - $ref: '#/responses/Conflict' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' - '/move_task_orders/{moveTaskOrderID}/mto_shipments/{shipmentID}': + "400": + $ref: "#/responses/InvalidRequest" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "409": + $ref: "#/responses/Conflict" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" + "/move_task_orders/{moveTaskOrderID}/mto_shipments/{shipmentID}": patch: summary: updateMTOShipment description: | @@ -1093,27 +1101,27 @@ paths: - in: body name: body schema: - $ref: '#/definitions/UpdateShipment' + $ref: "#/definitions/UpdateShipment" responses: - '200': + "200": description: Successfully updated the specified MTO shipment. schema: - $ref: 'definitions/MTOShipment.yaml' - '400': - $ref: '#/responses/InvalidRequest' - '401': - $ref: '#/responses/PermissionDenied' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '412': - $ref: '#/responses/PreconditionFailed' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' - '/shipments/{shipmentID}/approve': + $ref: "definitions/MTOShipment.yaml" + "400": + $ref: "#/responses/InvalidRequest" + "401": + $ref: "#/responses/PermissionDenied" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "412": + $ref: "#/responses/PreconditionFailed" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" + "/shipments/{shipmentID}/approve": parameters: - description: ID of the shipment in: path @@ -1132,22 +1140,22 @@ paths: type: string required: true responses: - '200': + "200": description: Successfully approved the shipment schema: - $ref: 'definitions/MTOShipment.yaml' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '409': - $ref: '#/responses/Conflict' - '412': - $ref: '#/responses/PreconditionFailed' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' + $ref: "definitions/MTOShipment.yaml" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "409": + $ref: "#/responses/Conflict" + "412": + $ref: "#/responses/PreconditionFailed" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" tags: - shipment description: Approves a shipment @@ -1155,7 +1163,7 @@ paths: summary: Approves a shipment x-permissions: - update.shipment - '/shipments/{shipmentID}/request-diversion': + "/shipments/{shipmentID}/request-diversion": parameters: - description: ID of the shipment in: path @@ -1177,24 +1185,24 @@ paths: name: body required: true schema: - $ref: '#/definitions/RequestDiversion' + $ref: "#/definitions/RequestDiversion" responses: - '200': + "200": description: Successfully requested the shipment diversion schema: - $ref: 'definitions/MTOShipment.yaml' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '409': - $ref: '#/responses/Conflict' - '412': - $ref: '#/responses/PreconditionFailed' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' + $ref: "definitions/MTOShipment.yaml" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "409": + $ref: "#/responses/Conflict" + "412": + $ref: "#/responses/PreconditionFailed" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" tags: - shipment description: Requests a shipment diversion @@ -1202,7 +1210,7 @@ paths: summary: Requests a shipment diversion x-permissions: - create.shipmentDiversionRequest - '/shipments/{shipmentID}/approve-diversion': + "/shipments/{shipmentID}/approve-diversion": parameters: - description: ID of the shipment in: path @@ -1221,22 +1229,22 @@ paths: type: string required: true responses: - '200': + "200": description: Successfully approved the shipment diversion schema: - $ref: 'definitions/MTOShipment.yaml' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '409': - $ref: '#/responses/Conflict' - '412': - $ref: '#/responses/PreconditionFailed' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' + $ref: "definitions/MTOShipment.yaml" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "409": + $ref: "#/responses/Conflict" + "412": + $ref: "#/responses/PreconditionFailed" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" x-permissions: - update.shipment tags: @@ -1244,7 +1252,7 @@ paths: description: Approves a shipment diversion operationId: approveShipmentDiversion summary: Approves a shipment diversion - '/shipments/{shipmentID}/reject': + "/shipments/{shipmentID}/reject": parameters: - description: ID of the shipment in: path @@ -1266,30 +1274,30 @@ paths: name: body required: true schema: - $ref: '#/definitions/RejectShipment' + $ref: "#/definitions/RejectShipment" responses: - '200': + "200": description: Successfully rejected the shipment schema: - $ref: 'definitions/MTOShipment.yaml' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '409': - $ref: '#/responses/Conflict' - '412': - $ref: '#/responses/PreconditionFailed' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' + $ref: "definitions/MTOShipment.yaml" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "409": + $ref: "#/responses/Conflict" + "412": + $ref: "#/responses/PreconditionFailed" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" tags: - shipment description: rejects a shipment operationId: rejectShipment summary: rejects a shipment - '/shipments/{shipmentID}/request-cancellation': + "/shipments/{shipmentID}/request-cancellation": parameters: - description: ID of the shipment in: path @@ -1308,22 +1316,22 @@ paths: type: string required: true responses: - '200': + "200": description: Successfully requested the shipment cancellation schema: - $ref: 'definitions/MTOShipment.yaml' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '409': - $ref: '#/responses/Conflict' - '412': - $ref: '#/responses/PreconditionFailed' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' + $ref: "definitions/MTOShipment.yaml" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "409": + $ref: "#/responses/Conflict" + "412": + $ref: "#/responses/PreconditionFailed" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" tags: - shipment description: Requests a shipment cancellation @@ -1331,7 +1339,7 @@ paths: summary: Requests a shipment cancellation x-permissions: - create.shipmentCancellation - '/shipments/{shipmentID}/request-reweigh': + "/shipments/{shipmentID}/request-reweigh": parameters: - description: ID of the shipment in: path @@ -1345,22 +1353,22 @@ paths: produces: - application/json responses: - '200': + "200": description: Successfully requested a reweigh of the shipment schema: - $ref: 'definitions/Reweigh.yaml' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '409': - $ref: '#/responses/Conflict' - '412': - $ref: '#/responses/PreconditionFailed' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' + $ref: "definitions/Reweigh.yaml" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "409": + $ref: "#/responses/Conflict" + "412": + $ref: "#/responses/PreconditionFailed" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" tags: - shipment - reweigh @@ -1369,7 +1377,7 @@ paths: summary: Requests a shipment reweigh x-permissions: - create.reweighRequest - '/shipments/{shipmentID}/review-shipment-address-update': + "/shipments/{shipmentID}/review-shipment-address-update": parameters: - description: ID of the shipment in: path @@ -1403,30 +1411,30 @@ paths: - officeRemarks - status responses: - '200': + "200": description: Successfully requested a shipment address update schema: - $ref: 'definitions/ShipmentAddressUpdate.yaml' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '409': - $ref: '#/responses/Conflict' - '412': - $ref: '#/responses/PreconditionFailed' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' + $ref: "definitions/ShipmentAddressUpdate.yaml" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "409": + $ref: "#/responses/Conflict" + "412": + $ref: "#/responses/PreconditionFailed" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" tags: - shipment - - shipment_address_updates - description: This endpoint is used to approve a address update request. Office remarks are required. + description: + This endpoint is used to approve a address update request. Office remarks are required. Approving the address update will update the Destination Final Address of the associated service item operationId: reviewShipmentAddressUpdate summary: Allows TOO to review a shipment address update - '/shipments/{shipmentID}/sit-extensions': + "/shipments/{shipmentID}/sit-extensions": post: summary: Create an approved SIT Duration Update description: TOO can creates an already-approved SIT Duration Update on behalf of a customer @@ -1448,7 +1456,7 @@ paths: - in: body name: body schema: - $ref: '#/definitions/CreateApprovedSITDurationUpdate' + $ref: "#/definitions/CreateApprovedSITDurationUpdate" required: true - in: header description: We want the shipment's eTag rather than the SIT Duration Update eTag as the SIT Duration Update is always associated with a shipment @@ -1456,23 +1464,23 @@ paths: type: string required: true responses: - '200': + "200": description: Successfully created a SIT Extension. schema: - $ref: 'definitions/MTOShipment.yaml' - '400': - $ref: '#/responses/InvalidRequest' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' + $ref: "definitions/MTOShipment.yaml" + "400": + $ref: "#/responses/InvalidRequest" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" x-permissions: - create.SITExtension - '/shipments/{shipmentID}/sit-extensions/{sitExtensionID}/approve': + "/shipments/{shipmentID}/sit-extensions/{sitExtensionID}/approve": parameters: - description: ID of the shipment in: path @@ -1496,29 +1504,29 @@ paths: name: body required: true schema: - $ref: '#/definitions/ApproveSITExtension' + $ref: "#/definitions/ApproveSITExtension" - in: header description: We want the shipment's eTag rather than the SIT extension eTag as the SIT extension is always associated with a shipment name: If-Match type: string required: true responses: - '200': + "200": description: Successfully approved a SIT extension schema: - $ref: 'definitions/MTOShipment.yaml' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '409': - $ref: '#/responses/Conflict' - '412': - $ref: '#/responses/PreconditionFailed' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' + $ref: "definitions/MTOShipment.yaml" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "409": + $ref: "#/responses/Conflict" + "412": + $ref: "#/responses/PreconditionFailed" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" tags: - shipment - sitExtension @@ -1527,7 +1535,7 @@ paths: summary: Approves a SIT extension x-permissions: - update.SITExtension - '/shipments/{shipmentID}/sit-extensions/{sitExtensionID}/deny': + "/shipments/{shipmentID}/sit-extensions/{sitExtensionID}/deny": parameters: - description: ID of the shipment in: path @@ -1551,28 +1559,28 @@ paths: name: body required: true schema: - $ref: '#/definitions/DenySITExtension' + $ref: "#/definitions/DenySITExtension" - in: header name: If-Match type: string required: true responses: - '200': + "200": description: Successfully denied a SIT extension schema: - $ref: 'definitions/MTOShipment.yaml' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '409': - $ref: '#/responses/Conflict' - '412': - $ref: '#/responses/PreconditionFailed' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' + $ref: "definitions/MTOShipment.yaml" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "409": + $ref: "#/responses/Conflict" + "412": + $ref: "#/responses/PreconditionFailed" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" tags: - shipment - sitExtension @@ -1581,7 +1589,7 @@ paths: summary: Denies a SIT extension x-permissions: - update.SITExtension - '/shipments/{shipmentID}/sit-service-item/convert-to-customer-expense': + "/shipments/{shipmentID}/sit-service-item/convert-to-customer-expense": parameters: - description: ID of the shipment in: path @@ -1599,28 +1607,28 @@ paths: name: body required: true schema: - $ref: '#/definitions/UpdateSITServiceItemCustomerExpense' + $ref: "#/definitions/UpdateSITServiceItemCustomerExpense" - in: header name: If-Match type: string required: true responses: - '200': + "200": description: Successfully converted to customer expense schema: - $ref: 'definitions/MTOShipment.yaml' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '409': - $ref: '#/responses/Conflict' - '412': - $ref: '#/responses/PreconditionFailed' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' + $ref: "definitions/MTOShipment.yaml" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "409": + $ref: "#/responses/Conflict" + "412": + $ref: "#/responses/PreconditionFailed" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" tags: - shipment - mtoServiceItem @@ -1650,22 +1658,22 @@ paths: produces: - application/json responses: - '200': + "200": description: All PPM documents and associated uploads for the specified PPM shipment. schema: - $ref: 'definitions/PPMDocuments.yaml' - '401': - $ref: '#/responses/PermissionDenied' - '403': - $ref: '#/responses/PermissionDenied' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' + $ref: "definitions/PPMDocuments.yaml" + "401": + $ref: "#/responses/PermissionDenied" + "403": + $ref: "#/responses/PermissionDenied" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" /ppm-shipments/{ppmShipmentId}/weight-ticket/{weightTicketId}: parameters: - - $ref: 'parameters/ppmShipmentId.yaml' - - $ref: 'parameters/weightTicketId.yaml' + - $ref: "parameters/ppmShipmentId.yaml" + - $ref: "parameters/weightTicketId.yaml" patch: summary: Updates a weight ticket document description: | @@ -1679,35 +1687,35 @@ paths: produces: - application/json parameters: - - $ref: 'parameters/ifMatch.yaml' + - $ref: "parameters/ifMatch.yaml" - in: body name: updateWeightTicketPayload required: true schema: - $ref: '#/definitions/UpdateWeightTicket' + $ref: "#/definitions/UpdateWeightTicket" responses: - '200': + "200": description: returns an updated weight ticket object schema: - $ref: 'definitions/WeightTicket.yaml' - '400': - $ref: '#/responses/InvalidRequest' - '401': - $ref: '#/responses/PermissionDenied' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '412': - $ref: '#/responses/PreconditionFailed' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' + $ref: "definitions/WeightTicket.yaml" + "400": + $ref: "#/responses/InvalidRequest" + "401": + $ref: "#/responses/PermissionDenied" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "412": + $ref: "#/responses/PreconditionFailed" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" /ppm-shipments/{ppmShipmentId}/moving-expenses/{movingExpenseId}: parameters: - - $ref: 'parameters/ppmShipmentId.yaml' - - $ref: 'parameters/movingExpenseId.yaml' + - $ref: "parameters/ppmShipmentId.yaml" + - $ref: "parameters/movingExpenseId.yaml" patch: summary: Updates the moving expense description: | @@ -1721,35 +1729,35 @@ paths: produces: - application/json parameters: - - $ref: 'parameters/ifMatch.yaml' + - $ref: "parameters/ifMatch.yaml" - in: body name: updateMovingExpense required: true schema: - $ref: '#/definitions/UpdateMovingExpense' + $ref: "#/definitions/UpdateMovingExpense" responses: - '200': + "200": description: returns an updated moving expense object schema: - $ref: 'definitions/MovingExpense.yaml' - '400': - $ref: '#/responses/InvalidRequest' - '401': - $ref: '#/responses/PermissionDenied' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '412': - $ref: '#/responses/PreconditionFailed' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' - /ppm-shipments/{ppmShipmentId}/pro-gear-weight-tickets/{proGearWeightTicketId}: - parameters: - - $ref: 'parameters/ppmShipmentId.yaml' - - $ref: 'parameters/proGearWeightTicketId.yaml' + $ref: "definitions/MovingExpense.yaml" + "400": + $ref: "#/responses/InvalidRequest" + "401": + $ref: "#/responses/PermissionDenied" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "412": + $ref: "#/responses/PreconditionFailed" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" + ? /ppm-shipments/{ppmShipmentId}/pro-gear-weight-tickets/{proGearWeightTicketId} + : parameters: + - $ref: "parameters/ppmShipmentId.yaml" + - $ref: "parameters/proGearWeightTicketId.yaml" patch: summary: Updates a pro-gear weight ticket description: | @@ -1763,31 +1771,31 @@ paths: produces: - application/json parameters: - - $ref: 'parameters/ifMatch.yaml' + - $ref: "parameters/ifMatch.yaml" - in: body name: updateProGearWeightTicket required: true schema: - $ref: '#/definitions/UpdateProGearWeightTicket' + $ref: "#/definitions/UpdateProGearWeightTicket" responses: - '200': + "200": description: returns an updated pro-gear weight ticket object schema: - $ref: 'definitions/ProGearWeightTicket.yaml' - '400': - $ref: '#/responses/InvalidRequest' - '401': - $ref: '#/responses/PermissionDenied' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '412': - $ref: '#/responses/PreconditionFailed' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' + $ref: "definitions/ProGearWeightTicket.yaml" + "400": + $ref: "#/responses/InvalidRequest" + "401": + $ref: "#/responses/PermissionDenied" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "412": + $ref: "#/responses/PreconditionFailed" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" /ppm-shipments/{ppmShipmentId}/aoa-packet: parameters: - description: the id for the ppmshipment with aoa to be downloaded @@ -1809,7 +1817,7 @@ paths: produces: - application/pdf responses: - '200': + "200": headers: Content-Disposition: type: string @@ -1818,19 +1826,19 @@ paths: schema: format: binary type: file - '400': - $ref: '#/responses/InvalidRequest' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' + "400": + $ref: "#/responses/InvalidRequest" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" /ppm-shipments/{ppmShipmentId}/finish-document-review: parameters: - - $ref: 'parameters/ppmShipmentId.yaml' + - $ref: "parameters/ppmShipmentId.yaml" patch: summary: Updates a PPM shipment's status after document review description: | @@ -1848,26 +1856,26 @@ paths: type: string required: true responses: - '200': + "200": description: Successfully finished document review schema: - $ref: 'definitions/PPMShipment.yaml' - '400': - $ref: '#/responses/InvalidRequest' - '401': - $ref: '#/responses/PermissionDenied' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '409': - $ref: '#/responses/Conflict' - '412': - $ref: '#/responses/PreconditionFailed' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' + $ref: "definitions/PPMShipment.yaml" + "400": + $ref: "#/responses/InvalidRequest" + "401": + $ref: "#/responses/PermissionDenied" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "409": + $ref: "#/responses/Conflict" + "412": + $ref: "#/responses/PreconditionFailed" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" x-permissions: - update.shipment /ppm-shipments/{ppmShipmentId}/ppm-sit: @@ -1883,7 +1891,7 @@ paths: produces: - application/json parameters: - - $ref: 'parameters/ppmShipmentId.yaml' + - $ref: "parameters/ppmShipmentId.yaml" - in: header name: If-Match type: string @@ -1891,27 +1899,27 @@ paths: - in: body name: body schema: - $ref: 'definitions/PPMShipmentSIT.yaml' + $ref: "definitions/PPMShipmentSIT.yaml" responses: - '200': + "200": description: Successfully finished PPM SIT update schema: - $ref: 'definitions/PPMShipment.yaml' - '400': - $ref: '#/responses/InvalidRequest' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '412': - $ref: '#/responses/PreconditionFailed' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' + $ref: "definitions/PPMShipment.yaml" + "400": + $ref: "#/responses/InvalidRequest" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "412": + $ref: "#/responses/PreconditionFailed" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" /ppm-shipments/{ppmShipmentId}/closeout: parameters: - - $ref: 'parameters/ppmShipmentId.yaml' + - $ref: "parameters/ppmShipmentId.yaml" get: summary: Get the closeout calcuations for the specified PPM shipment description: | @@ -1922,23 +1930,23 @@ paths: produces: - application/json responses: - '200': + "200": description: Returns closeout for the specified PPM shipment. schema: - $ref: 'definitions/PPMCloseout.yaml' - '400': - $ref: '#/responses/InvalidRequest' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' + $ref: "definitions/PPMCloseout.yaml" + "400": + $ref: "#/responses/InvalidRequest" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" /ppm-shipments/{ppmShipmentId}/actual-weight: parameters: - - $ref: 'parameters/ppmShipmentId.yaml' + - $ref: "parameters/ppmShipmentId.yaml" get: summary: Get the actual weight for a PPM shipment description: | @@ -1949,23 +1957,23 @@ paths: produces: - application/json responses: - '200': + "200": description: Returns actual weight for the specified PPM shipment. schema: - $ref: 'definitions/PPMActualWeight.yaml' - '400': - $ref: '#/responses/InvalidRequest' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' + $ref: "definitions/PPMActualWeight.yaml" + "400": + $ref: "#/responses/InvalidRequest" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" /ppm-shipments/{ppmShipmentId}/sit_location/{sitLocation}/sit-estimated-cost: parameters: - - $ref: 'parameters/ppmShipmentId.yaml' + - $ref: "parameters/ppmShipmentId.yaml" - in: path format: string description: location of sit @@ -2003,20 +2011,20 @@ paths: produces: - application/json responses: - '200': + "200": description: Calculates and returns the SIT estimated cost for the specified PPM shipment. schema: - $ref: 'definitions/PPMSITEstimatedCost.yaml' - '400': - $ref: '#/responses/InvalidRequest' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' + $ref: "definitions/PPMSITEstimatedCost.yaml" + "400": + $ref: "#/responses/InvalidRequest" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" /ppm-shipments/{ppmShipmentId}/payment-packet: get: summary: Returns PPM payment packet @@ -2034,7 +2042,7 @@ paths: produces: - application/pdf responses: - '200': + "200": headers: Content-Disposition: type: string @@ -2043,17 +2051,17 @@ paths: schema: format: binary type: file - '400': + "400": description: invalid request - '401': + "401": description: request requires user authentication - '403': + "403": description: user is not authorized - '404': + "404": description: ppm not found - '500': + "500": description: internal server error - '/move_task_orders/{moveTaskOrderID}/mto_shipments/{shipmentID}/mto-agents': + "/move_task_orders/{moveTaskOrderID}/mto_shipments/{shipmentID}/mto-agents": parameters: - description: ID of move task order in: path @@ -2072,22 +2080,22 @@ paths: - application/json parameters: [] responses: - '200': + "200": description: Successfully retrieved all agents for a move task order schema: - $ref: 'definitions/MTOAgents.yaml' - '404': - $ref: '#/responses/NotFound' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' + $ref: "definitions/MTOAgents.yaml" + "404": + $ref: "#/responses/NotFound" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" tags: - mtoAgent description: Fetches a list of agents associated with a move task order. operationId: fetchMTOAgentList summary: Fetch move task order agents. - '/move-task-orders/{moveTaskOrderID}/service-items/{mtoServiceItemID}': + "/move-task-orders/{moveTaskOrderID}/service-items/{mtoServiceItemID}": parameters: - description: ID of move to use in: path @@ -2104,26 +2112,26 @@ paths: - application/json parameters: [] responses: - '200': + "200": description: Successfully retrieved a line item for a move task order by ID schema: - $ref: 'definitions/MTOServiceItemSingle.yaml' - '400': - $ref: '#/responses/InvalidRequest' - '401': - $ref: '#/responses/PermissionDenied' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '500': - $ref: '#/responses/ServerError' + $ref: "definitions/MTOServiceItemSingle.yaml" + "400": + $ref: "#/responses/InvalidRequest" + "401": + $ref: "#/responses/PermissionDenied" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "500": + $ref: "#/responses/ServerError" tags: - mtoServiceItem description: Gets a line item by ID for a move by ID operationId: getMTOServiceItem summary: Gets a line item by ID for a move by ID - '/move-task-orders/{moveTaskOrderID}/service-items/{mtoServiceItemID}/status': + "/move-task-orders/{moveTaskOrderID}/service-items/{mtoServiceItemID}/status": parameters: - description: ID of move to use in: path @@ -2145,32 +2153,32 @@ paths: name: body required: true schema: - $ref: '#/definitions/PatchMTOServiceItemStatusPayload' + $ref: "#/definitions/PatchMTOServiceItemStatusPayload" - in: header name: If-Match type: string required: true responses: - '200': + "200": description: >- Successfully updated status for a line item for a move task order by ID schema: - $ref: 'definitions/MTOServiceItem.yaml' - '400': - $ref: '#/responses/InvalidRequest' - '401': - $ref: '#/responses/PermissionDenied' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '412': - $ref: '#/responses/PreconditionFailed' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' + $ref: "definitions/MTOServiceItem.yaml" + "400": + $ref: "#/responses/InvalidRequest" + "401": + $ref: "#/responses/PermissionDenied" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "412": + $ref: "#/responses/PreconditionFailed" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" tags: - mtoServiceItem description: Changes the status of a line item for a move by ID @@ -2178,7 +2186,7 @@ paths: summary: Change the status of a line item for a move by ID x-permissions: - update.MTOServiceItem - '/service-item/{mtoServiceItemID}/entry-date-update': + "/service-item/{mtoServiceItemID}/entry-date-update": parameters: - description: ID of the service item in: path @@ -2195,32 +2203,32 @@ paths: name: body required: true schema: - $ref: 'definitions/ServiceItemSitEntryDate.yaml' + $ref: "definitions/ServiceItemSitEntryDate.yaml" responses: - '200': + "200": description: Successfully updated SIT entry date schema: - $ref: 'definitions/MTOServiceItemSingle.yaml' - '400': - $ref: '#/responses/InvalidRequest' - '401': - $ref: '#/responses/PermissionDenied' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '412': - $ref: '#/responses/PreconditionFailed' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' + $ref: "definitions/MTOServiceItemSingle.yaml" + "400": + $ref: "#/responses/InvalidRequest" + "401": + $ref: "#/responses/PermissionDenied" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "412": + $ref: "#/responses/PreconditionFailed" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" tags: - mtoServiceItem description: Locates the service item in the database and updates the SIT entry date for the selected service item and returns the service item operationId: updateServiceItemSitEntryDate summary: Updates a service item's SIT entry date by ID - '/move-task-orders/{moveTaskOrderID}/status': + "/move-task-orders/{moveTaskOrderID}/status": patch: consumes: - application/json @@ -2239,29 +2247,29 @@ paths: - in: body name: serviceItemCodes schema: - $ref: '#/definitions/MTOApprovalServiceItemCodes' + $ref: "#/definitions/MTOApprovalServiceItemCodes" required: true responses: - '200': + "200": description: Successfully updated move task order status schema: - $ref: '#/definitions/Move' - '400': - $ref: '#/responses/InvalidRequest' - '401': - $ref: '#/responses/PermissionDenied' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '409': - $ref: '#/responses/Conflict' - '412': - $ref: '#/responses/PreconditionFailed' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' + $ref: "#/definitions/Move" + "400": + $ref: "#/responses/InvalidRequest" + "401": + $ref: "#/responses/PermissionDenied" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "409": + $ref: "#/responses/Conflict" + "412": + $ref: "#/responses/PreconditionFailed" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" tags: - moveTaskOrder description: Changes move task order status to make it available to prime @@ -2270,7 +2278,7 @@ paths: x-permissions: - update.move - create.serviceItem - '/move-task-orders/{moveTaskOrderID}/status/service-counseling-completed': + "/move-task-orders/{moveTaskOrderID}/status/service-counseling-completed": patch: consumes: - application/json @@ -2287,33 +2295,33 @@ paths: type: string required: true responses: - '200': + "200": description: Successfully updated move task order status schema: - $ref: '#/definitions/Move' - '400': - $ref: '#/responses/InvalidRequest' - '401': - $ref: '#/responses/PermissionDenied' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '409': - $ref: '#/responses/Conflict' - '412': - $ref: '#/responses/PreconditionFailed' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' + $ref: "#/definitions/Move" + "400": + $ref: "#/responses/InvalidRequest" + "401": + $ref: "#/responses/PermissionDenied" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "409": + $ref: "#/responses/Conflict" + "412": + $ref: "#/responses/PreconditionFailed" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" tags: - moveTaskOrder description: Changes move (move task order) status to service counseling completed operationId: updateMTOStatusServiceCounselingCompleted summary: Changes move (move task order) status to service counseling completed - '/move-task-orders/{moveTaskOrderID}/payment-service-items/{paymentServiceItemID}/status': - parameters: + ? "/move-task-orders/{moveTaskOrderID}/payment-service-items/{paymentServiceItemID}/status" + : parameters: - description: ID of move to use in: path name: moveTaskOrderID @@ -2334,32 +2342,32 @@ paths: name: body required: true schema: - $ref: '#/definitions/PaymentServiceItem' + $ref: "#/definitions/PaymentServiceItem" - in: header name: If-Match type: string required: true responses: - '200': + "200": description: >- Successfully updated status for a line item for a move task order by ID schema: - $ref: '#/definitions/PaymentServiceItem' - '400': - $ref: '#/responses/InvalidRequest' - '401': - $ref: '#/responses/PermissionDenied' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '412': - $ref: '#/responses/PreconditionFailed' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' + $ref: "#/definitions/PaymentServiceItem" + "400": + $ref: "#/responses/InvalidRequest" + "401": + $ref: "#/responses/PermissionDenied" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "412": + $ref: "#/responses/PreconditionFailed" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" tags: - paymentServiceItem description: Changes the status of a line item for a move by ID @@ -2367,7 +2375,7 @@ paths: summary: Change the status of a payment service item for a move by ID x-permissions: - update.paymentServiceItemStatus - '/move-task-orders/{moveTaskOrderID}/billable-weights-reviewed-at': + "/move-task-orders/{moveTaskOrderID}/billable-weights-reviewed-at": patch: consumes: - application/json @@ -2384,31 +2392,31 @@ paths: type: string required: true responses: - '200': + "200": description: Successfully updated move task order billableWeightsReviewedAt field schema: - $ref: '#/definitions/Move' - '400': - $ref: '#/responses/InvalidRequest' - '401': - $ref: '#/responses/PermissionDenied' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '409': - $ref: '#/responses/Conflict' - '412': - $ref: '#/responses/PreconditionFailed' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' + $ref: "#/definitions/Move" + "400": + $ref: "#/responses/InvalidRequest" + "401": + $ref: "#/responses/PermissionDenied" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "409": + $ref: "#/responses/Conflict" + "412": + $ref: "#/responses/PreconditionFailed" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" tags: - moveTaskOrder description: Changes move (move task order) billableWeightsReviewedAt field to a timestamp operationId: updateMTOReviewedBillableWeightsAt - '/move-task-orders/{moveTaskOrderID}/tio-remarks': + "/move-task-orders/{moveTaskOrderID}/tio-remarks": patch: consumes: - application/json @@ -2428,33 +2436,33 @@ paths: name: body required: true schema: - $ref: '#/definitions/Move' + $ref: "#/definitions/Move" responses: - '200': + "200": description: Successfully updated move task order tioRemarks field schema: - $ref: '#/definitions/Move' - '400': - $ref: '#/responses/InvalidRequest' - '401': - $ref: '#/responses/PermissionDenied' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '409': - $ref: '#/responses/Conflict' - '412': - $ref: '#/responses/PreconditionFailed' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' + $ref: "#/definitions/Move" + "400": + $ref: "#/responses/InvalidRequest" + "401": + $ref: "#/responses/PermissionDenied" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "409": + $ref: "#/responses/Conflict" + "412": + $ref: "#/responses/PreconditionFailed" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" tags: - moveTaskOrder description: Changes move (move task order) billableWeightsReviewedAt field to a timestamp operationId: updateMoveTIORemarks - '/move-task-orders/{moveTaskOrderID}/entitlements': + "/move-task-orders/{moveTaskOrderID}/entitlements": parameters: - description: ID of move to use in: path @@ -2468,24 +2476,24 @@ paths: tags: - moveTaskOrder responses: - '200': + "200": description: Successfully retrieved entitlements schema: - $ref: '#/definitions/Entitlements' - '400': - $ref: '#/responses/InvalidRequest' - '401': - $ref: '#/responses/PermissionDenied' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '500': - $ref: '#/responses/ServerError' + $ref: "#/definitions/Entitlements" + "400": + $ref: "#/responses/InvalidRequest" + "401": + $ref: "#/responses/PermissionDenied" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "500": + $ref: "#/responses/ServerError" description: Gets entitlements operationId: getEntitlements summary: Gets entitlements for a move by ID - '/payment-requests/{paymentRequestID}': + "/payment-requests/{paymentRequestID}": parameters: - description: UUID of payment request format: uuid @@ -2498,20 +2506,20 @@ paths: - application/json parameters: [] responses: - '200': + "200": description: fetched instance of payment request schema: - $ref: '#/definitions/PaymentRequest' - '400': - $ref: '#/responses/InvalidRequest' - '401': - $ref: '#/responses/PermissionDenied' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '500': - $ref: '#/responses/ServerError' + $ref: "#/definitions/PaymentRequest" + "400": + $ref: "#/responses/InvalidRequest" + "401": + $ref: "#/responses/PermissionDenied" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "500": + $ref: "#/responses/ServerError" tags: - paymentRequests description: Fetches an instance of a payment request by id @@ -2519,7 +2527,7 @@ paths: summary: Fetches a payment request by id x-permissions: - read.paymentRequest - '/moves/{locator}/closeout-office': + "/moves/{locator}/closeout-office": parameters: - description: move code to identify a move to update the PPM shipment's closeout office for Army and Air Force service members format: string @@ -2554,25 +2562,25 @@ paths: type: string required: true responses: - '200': + "200": description: Successfully set the closeout office for the move schema: - $ref: '#/definitions/Move' - '400': - $ref: '#/responses/InvalidRequest' - '401': - $ref: '#/responses/PermissionDenied' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '412': - $ref: '#/responses/PreconditionFailed' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' - '/moves/{locator}/customer-support-remarks': + $ref: "#/definitions/Move" + "400": + $ref: "#/responses/InvalidRequest" + "401": + $ref: "#/responses/PermissionDenied" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "412": + $ref: "#/responses/PreconditionFailed" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" + "/moves/{locator}/customer-support-remarks": parameters: - description: move code to identify a move for customer support remarks format: string @@ -2589,20 +2597,20 @@ paths: - in: body name: body schema: - $ref: '#/definitions/CreateCustomerSupportRemark' + $ref: "#/definitions/CreateCustomerSupportRemark" responses: - '200': + "200": description: Successfully created customer support remark schema: - $ref: 'definitions/CustomerSupportRemark.yaml' - '400': - $ref: '#/responses/InvalidRequest' - '404': - $ref: '#/responses/NotFound' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' + $ref: "definitions/CustomerSupportRemark.yaml" + "400": + $ref: "#/responses/InvalidRequest" + "404": + $ref: "#/responses/NotFound" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" tags: - customerSupportRemarks description: Creates a customer support remark for a move @@ -2613,24 +2621,24 @@ paths: - application/json parameters: [] responses: - '200': + "200": description: Successfully retrieved all line items for a move task order schema: - $ref: 'definitions/CustomerSupportRemarks.yaml' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' + $ref: "definitions/CustomerSupportRemarks.yaml" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" tags: - customerSupportRemarks description: Fetches customer support remarks for a move operationId: getCustomerSupportRemarksForMove summary: Fetches customer support remarks using the move code (locator). - '/customer-support-remarks/{customerSupportRemarkID}': + "/customer-support-remarks/{customerSupportRemarkID}": parameters: - in: path description: the customer support remark ID to be modified @@ -2653,22 +2661,22 @@ paths: name: body required: true schema: - $ref: '#/definitions/UpdateCustomerSupportRemarkPayload' + $ref: "#/definitions/UpdateCustomerSupportRemarkPayload" responses: - '200': + "200": description: Successfully updated customer support remark schema: - $ref: 'definitions/CustomerSupportRemark.yaml' - '400': - $ref: '#/responses/InvalidRequest' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' + $ref: "definitions/CustomerSupportRemark.yaml" + "400": + $ref: "#/responses/InvalidRequest" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" delete: summary: Soft deletes a customer support remark by ID description: Soft deletes a customer support remark by ID @@ -2678,21 +2686,21 @@ paths: produces: - application/json responses: - '204': + "204": description: Successfully soft deleted the shipment - '400': - $ref: '#/responses/InvalidRequest' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '409': - $ref: '#/responses/Conflict' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' - '/moves/{locator}/evaluation-reports': + "400": + $ref: "#/responses/InvalidRequest" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "409": + $ref: "#/responses/Conflict" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" + "/moves/{locator}/evaluation-reports": parameters: - in: path name: locator @@ -2707,20 +2715,20 @@ paths: - in: body name: body schema: - $ref: '#/definitions/CreateEvaluationReport' + $ref: "#/definitions/CreateEvaluationReport" responses: - '200': + "200": description: Successfully created evaluation report schema: - $ref: '#/definitions/EvaluationReport' - '400': - $ref: '#/responses/InvalidRequest' - '404': - $ref: '#/responses/NotFound' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' + $ref: "#/definitions/EvaluationReport" + "400": + $ref: "#/responses/InvalidRequest" + "404": + $ref: "#/responses/NotFound" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" x-permissions: - create.evaluationReport tags: @@ -2728,7 +2736,7 @@ paths: description: Creates an evaluation report operationId: createEvaluationReport summary: Creates an evaluation report - '/evaluation-reports/{reportID}/download': + "/evaluation-reports/{reportID}/download": parameters: - in: path description: the evaluation report ID to be downloaded @@ -2745,7 +2753,7 @@ paths: produces: - application/pdf responses: - '200': + "200": headers: Content-Disposition: type: string @@ -2754,13 +2762,13 @@ paths: schema: format: binary type: file - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '500': - $ref: '#/responses/ServerError' - '/evaluation-reports/{reportID}': + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "500": + $ref: "#/responses/ServerError" + "/evaluation-reports/{reportID}": parameters: - in: path description: the evaluation report ID to be modified @@ -2777,18 +2785,18 @@ paths: produces: - application/json responses: - '200': + "200": description: Successfully got the report schema: - $ref: '#/definitions/EvaluationReport' - '400': - $ref: '#/responses/InvalidRequest' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '500': - $ref: '#/responses/ServerError' + $ref: "#/definitions/EvaluationReport" + "400": + $ref: "#/responses/InvalidRequest" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "500": + $ref: "#/responses/ServerError" delete: summary: Deletes an evaluation report by ID description: Deletes an evaluation report by ID @@ -2800,20 +2808,20 @@ paths: produces: - application/json responses: - '204': + "204": description: Successfully deleted the report - '400': - $ref: '#/responses/InvalidRequest' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '409': - $ref: '#/responses/Conflict' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' + "400": + $ref: "#/responses/InvalidRequest" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "409": + $ref: "#/responses/Conflict" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" put: summary: Saves an evaluation report as a draft description: Saves an evaluation report as a draft @@ -2830,7 +2838,7 @@ paths: - in: body name: body schema: - $ref: '#/definitions/EvaluationReport' + $ref: "#/definitions/EvaluationReport" - in: header name: If-Match type: string @@ -2839,23 +2847,23 @@ paths: Optimistic locking is implemented via the `If-Match` header. If the ETag header does not match the value of the resource on the server, the server rejects the change with a `412 Precondition Failed` error. responses: - '204': + "204": description: Successfully saved the report - '400': - $ref: '#/responses/InvalidRequest' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '409': - $ref: '#/responses/Conflict' - '412': - $ref: '#/responses/PreconditionFailed' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' - '/evaluation-reports/{reportID}/submit': + "400": + $ref: "#/responses/InvalidRequest" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "409": + $ref: "#/responses/Conflict" + "412": + $ref: "#/responses/PreconditionFailed" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" + "/evaluation-reports/{reportID}/submit": parameters: - in: path description: the evaluation report ID to be modified @@ -2880,21 +2888,21 @@ paths: Optimistic locking is implemented via the `If-Match` header. If the ETag header does not match the value of the resource on the server, the server rejects the change with a `412 Precondition Failed` error. responses: - '204': + "204": description: Successfully submitted an evaluation report with the provided ID - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '412': - $ref: '#/responses/PreconditionFailed' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "412": + $ref: "#/responses/PreconditionFailed" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" x-permissions: - update.evaluationReport - '/evaluation-reports/{reportID}/appeal/add': + "/evaluation-reports/{reportID}/appeal/add": parameters: - in: path description: the evaluation report ID @@ -2916,23 +2924,23 @@ paths: - in: body name: body schema: - $ref: '#/definitions/CreateAppeal' + $ref: "#/definitions/CreateAppeal" responses: - '204': + "204": description: Successfully added an appeal to a serious incident - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '412': - $ref: '#/responses/PreconditionFailed' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "412": + $ref: "#/responses/PreconditionFailed" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" x-permissions: - update.evaluationReport - '/evaluation-reports/{reportID}/{reportViolationID}/appeal/add': + "/evaluation-reports/{reportID}/{reportViolationID}/appeal/add": parameters: - in: path description: the evaluation report ID @@ -2960,23 +2968,23 @@ paths: - in: body name: body schema: - $ref: '#/definitions/CreateAppeal' + $ref: "#/definitions/CreateAppeal" responses: - '204': + "204": description: Successfully added an appeal to a violation - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '412': - $ref: '#/responses/PreconditionFailed' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "412": + $ref: "#/responses/PreconditionFailed" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" x-permissions: - update.evaluationReport - '/pws-violations': + "/pws-violations": get: summary: Fetch the possible PWS violations for an evaluation report description: Fetch the possible PWS violations for an evaluation report @@ -2986,19 +2994,19 @@ paths: produces: - application/json responses: - '200': + "200": description: Successfully retrieved the PWS violations schema: - $ref: '#/definitions/PWSViolations' - '400': - $ref: '#/responses/InvalidRequest' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '500': - $ref: '#/responses/ServerError' - '/report-violations/{reportID}': + $ref: "#/definitions/PWSViolations" + "400": + $ref: "#/responses/InvalidRequest" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "500": + $ref: "#/responses/ServerError" + "/report-violations/{reportID}": parameters: - in: path description: the evaluation report ID that has associated violations @@ -3015,18 +3023,18 @@ paths: produces: - application/json responses: - '200': + "200": description: Successfully retrieved the report violations schema: - $ref: '#/definitions/ReportViolations' - '400': - $ref: '#/responses/InvalidRequest' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '500': - $ref: '#/responses/ServerError' + $ref: "#/definitions/ReportViolations" + "400": + $ref: "#/responses/InvalidRequest" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "500": + $ref: "#/responses/ServerError" post: summary: Associate violations with an evaluation report description: >- @@ -3041,29 +3049,29 @@ paths: - application/json consumes: - application/json - parameters: - - in: body - name: body - schema: - $ref: '#/definitions/AssociateReportViolations' - responses: - '204': - description: Successfully saved the report violations - '400': - $ref: '#/responses/InvalidRequest' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '409': - $ref: '#/responses/Conflict' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' + parameters: + - in: body + name: body + schema: + $ref: "#/definitions/AssociateReportViolations" + responses: + "204": + description: Successfully saved the report violations + "400": + $ref: "#/responses/InvalidRequest" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "409": + $ref: "#/responses/Conflict" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" x-permissions: - create.reportViolation - '/moves/{locator}/payment-requests': + "/moves/{locator}/payment-requests": parameters: - description: move code to identify a move for payment requests format: string @@ -3076,18 +3084,18 @@ paths: - application/json parameters: [] responses: - '200': + "200": description: Successfully retrieved all line items for a move task order schema: - $ref: '#/definitions/PaymentRequests' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' + $ref: "#/definitions/PaymentRequests" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" tags: - paymentRequests description: Fetches payment requests for a move @@ -3095,7 +3103,7 @@ paths: summary: Fetches payment requests using the move code (locator). x-permissions: - read.paymentRequest - '/moves/{moveID}/financial-review-flag': + "/moves/{moveID}/financial-review-flag": parameters: - description: ID of move to flag in: path @@ -3133,20 +3141,20 @@ paths: example: false type: boolean responses: - '200': + "200": description: updated Move schema: - $ref: '#/definitions/Move' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '412': - $ref: '#/responses/PreconditionFailed' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' + $ref: "#/definitions/Move" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "412": + $ref: "#/responses/PreconditionFailed" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" x-permissions: - update.financialReviewFlag /moves/{moveID}/uploadAdditionalDocuments: @@ -3171,25 +3179,25 @@ paths: description: The file to upload. required: true responses: - '201': + "201": description: created upload schema: - $ref: 'definitions/Upload.yaml' - '400': + $ref: "definitions/Upload.yaml" + "400": description: invalid request schema: - $ref: '#/definitions/InvalidRequestResponsePayload' - '403': + $ref: "#/definitions/InvalidRequestResponsePayload" + "403": description: not authorized - '404': + "404": description: not found - '413': + "413": description: payload is too large - '500': + "500": description: server error x-permissions: - create.supportingDocuments - '/payment-requests/{paymentRequestID}/shipments-payment-sit-balance': + "/payment-requests/{paymentRequestID}/shipments-payment-sit-balance": parameters: - description: payment request ID of the payment request with SIT service items being reviewed name: paymentRequestID @@ -3202,18 +3210,18 @@ paths: - application/json parameters: [] responses: - '200': + "200": description: Successfully retrieved shipments and their SIT days balance from all payment requests on the move schema: - $ref: '#/definitions/ShipmentsPaymentSITBalance' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' + $ref: "#/definitions/ShipmentsPaymentSITBalance" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" tags: - paymentRequests description: Returns all shipment payment request SIT usage to support partial SIT invoicing @@ -3221,7 +3229,7 @@ paths: summary: Returns all shipment payment request SIT usage to support partial SIT invoicing x-permissions: - read.shipmentsPaymentSITBalance - '/payment-requests/{paymentRequestID}/status': + "/payment-requests/{paymentRequestID}/status": patch: consumes: - application/json @@ -3238,30 +3246,30 @@ paths: name: body required: true schema: - $ref: '#/definitions/UpdatePaymentRequestStatusPayload' + $ref: "#/definitions/UpdatePaymentRequestStatusPayload" - in: header name: If-Match type: string required: true responses: - '200': + "200": description: updated payment request schema: - $ref: '#/definitions/PaymentRequest' - '400': - $ref: '#/responses/InvalidRequest' - '401': - $ref: '#/responses/PermissionDenied' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '412': - $ref: '#/responses/PreconditionFailed' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' + $ref: "#/definitions/PaymentRequest" + "400": + $ref: "#/responses/InvalidRequest" + "401": + $ref: "#/responses/PermissionDenied" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "412": + $ref: "#/responses/PreconditionFailed" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" tags: - paymentRequests description: Updates status of a payment request by id @@ -3269,7 +3277,7 @@ paths: summary: Updates status of a payment request by id x-permissions: - update.paymentRequest - '/payment-requests/{paymentRequestID}/bulkDownload': + "/payment-requests/{paymentRequestID}/bulkDownload": parameters: - description: the id for the payment-request with files to be downloaded in: path @@ -3286,7 +3294,7 @@ paths: produces: - application/pdf responses: - '200': + "200": headers: Content-Disposition: type: string @@ -3295,10 +3303,10 @@ paths: schema: format: binary type: file - '400': - $ref: '#/responses/InvalidRequest' - '500': - $ref: '#/responses/ServerError' + "400": + $ref: "#/responses/InvalidRequest" + "500": + $ref: "#/responses/ServerError" /documents/{documentId}: get: summary: Returns a document @@ -3314,24 +3322,24 @@ paths: required: true description: UUID of the document to return responses: - '200': + "200": description: the requested document schema: - $ref: 'definitions/Document.yaml' - '400': - $ref: '#/responses/InvalidRequest' - '401': - $ref: '#/responses/PermissionDenied' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '412': - $ref: '#/responses/PreconditionFailed' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' + $ref: "definitions/Document.yaml" + "400": + $ref: "#/responses/InvalidRequest" + "401": + $ref: "#/responses/PermissionDenied" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "412": + $ref: "#/responses/PreconditionFailed" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" /documents: post: summary: Create a new document @@ -3344,17 +3352,17 @@ paths: name: documentPayload required: true schema: - $ref: '#/definitions/PostDocumentPayload' + $ref: "#/definitions/PostDocumentPayload" responses: - '201': + "201": description: created document schema: - $ref: 'definitions/Document.yaml' - '400': + $ref: "definitions/Document.yaml" + "400": description: invalid request - '403': - $ref: '#/responses/PermissionDenied' - '500': + "403": + $ref: "#/responses/PermissionDenied" + "500": description: server error /queues/counseling: get: @@ -3504,15 +3512,19 @@ paths: type: string description: | Used to illustrate which user is assigned to this payment request. + - in: query + name: activeRole + type: string + description: user's actively logged in role responses: - '200': + "200": description: Successfully returned all moves matching the criteria schema: - $ref: '#/definitions/QueueMovesResult' - '403': - $ref: '#/responses/PermissionDenied' - '500': - $ref: '#/responses/ServerError' + $ref: "#/definitions/QueueMovesResult" + "403": + $ref: "#/responses/PermissionDenied" + "500": + $ref: "#/responses/ServerError" /queues/bulk-assignment: get: produces: @@ -3534,16 +3546,16 @@ paths: - TASK_ORDER - PAYMENT_REQUEST responses: - '200': + "200": description: Successfully returned bulk assignment data schema: - $ref: '#/definitions/BulkAssignmentData' - '401': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '500': - $ref: '#/responses/ServerError' + $ref: "#/definitions/BulkAssignmentData" + "401": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "500": + $ref: "#/responses/ServerError" /queues/counseling/origin-list: get: produces: @@ -3564,14 +3576,14 @@ paths: type: string description: Used to return an origins list for a GBLOC other than the default of the current user. Requires the HQ role or a secondary transportation office assignment. The parameter is ignored if the requesting user does not have the necessary role or assignment. responses: - '200': + "200": description: Successfully returned all moves matching the criteria schema: - $ref: '#/definitions/Locations' - '403': - $ref: '#/responses/PermissionDenied' - '500': - $ref: '#/responses/ServerError' + $ref: "#/definitions/Locations" + "403": + $ref: "#/responses/PermissionDenied" + "500": + $ref: "#/responses/ServerError" /queues/prime-moves: get: summary: getPrimeMovesQueue @@ -3613,14 +3625,14 @@ paths: type: string description: order type responses: - '200': + "200": description: Successfully retrieved moves. A successful fetch might still return zero moves. schema: - $ref: '#/definitions/ListPrimeMovesResult' - '403': - $ref: '#/responses/PermissionDenied' - '500': - $ref: '#/responses/ServerError' + $ref: "#/definitions/ListPrimeMovesResult" + "403": + $ref: "#/responses/PermissionDenied" + "500": + $ref: "#/responses/ServerError" /queues/moves: get: produces: @@ -3726,15 +3738,19 @@ paths: name: counselingOffice type: string description: filters using a counselingOffice name of the move + - in: query + name: activeRole + type: string + description: user's actively logged in role responses: - '200': + "200": description: Successfully returned all moves matching the criteria schema: - $ref: '#/definitions/QueueMovesResult' - '403': - $ref: '#/responses/PermissionDenied' - '500': - $ref: '#/responses/ServerError' + $ref: "#/definitions/QueueMovesResult" + "403": + $ref: "#/responses/PermissionDenied" + "500": + $ref: "#/responses/ServerError" /queues/payment-requests: get: produces: @@ -3749,7 +3765,20 @@ paths: - in: query name: sort type: string - enum: [customerName, locator, submittedAt, branch, status, edipi, emplid, age, originDutyLocation, assignedTo, counselingOffice] + enum: + [ + customerName, + locator, + submittedAt, + branch, + status, + edipi, + emplid, + age, + originDutyLocation, + assignedTo, + counselingOffice, + ] description: field that results should be sorted by - in: query name: order @@ -3822,15 +3851,19 @@ paths: type: string description: | Used to return a queue for a GBLOC other than the default of the current user. Requires the HQ role or a secondary transportation office assignment. The parameter is ignored if the requesting user does not have the necessary role or assignment. + - in: query + name: activeRole + type: string + description: user's actively logged in role responses: - '200': + "200": description: Successfully returned all moves matching the criteria schema: - $ref: '#/definitions/QueuePaymentRequestsResult' - '403': - $ref: '#/responses/PermissionDenied' - '500': - $ref: '#/responses/ServerError' + $ref: "#/definitions/QueuePaymentRequestsResult" + "403": + $ref: "#/responses/PermissionDenied" + "500": + $ref: "#/responses/ServerError" /moves/search: post: produces: @@ -3935,15 +3968,15 @@ paths: enum: [asc, desc] description: field that results should be sorted by responses: - '200': + "200": description: Successfully returned all moves matching the criteria schema: - $ref: '#/definitions/SearchMovesResult' - '403': - $ref: '#/responses/PermissionDenied' - '500': - $ref: '#/responses/ServerError' - '/tac/valid': + $ref: "#/definitions/SearchMovesResult" + "403": + $ref: "#/responses/PermissionDenied" + "500": + $ref: "#/responses/ServerError" + "/tac/valid": get: summary: Validation of a TAC value description: Returns a boolean based on whether a tac value is valid or not @@ -3958,23 +3991,23 @@ paths: required: true description: The tac value to validate responses: - '200': + "200": description: Successfully retrieved validation status schema: - $ref: '#/definitions/TacValid' - '400': - $ref: '#/responses/InvalidRequest' - '401': - $ref: '#/responses/PermissionDenied' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '500': - $ref: '#/responses/ServerError' + $ref: "#/definitions/TacValid" + "400": + $ref: "#/responses/InvalidRequest" + "401": + $ref: "#/responses/PermissionDenied" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "500": + $ref: "#/responses/ServerError" /lines-of-accounting: post: - summary: 'Fetch line of accounting' + summary: "Fetch line of accounting" description: > Fetches a line of accounting based on provided service member affiliation, effective date, and Transportation Accounting Code (TAC). It uses these parameters to filter the correct Line of Accounting for the provided TAC. It does this by filtering @@ -3986,33 +4019,33 @@ paths: tags: - linesOfAccounting consumes: - - 'application/json' + - "application/json" produces: - - 'application/json' + - "application/json" parameters: - - in: 'body' - name: 'body' - description: 'Service member affiliation, effective date, and TAC code.' + - in: "body" + name: "body" + description: "Service member affiliation, effective date, and TAC code." required: true schema: - $ref: '#/definitions/FetchLineOfAccountingPayload' + $ref: "#/definitions/FetchLineOfAccountingPayload" responses: - '200': - description: 'Successfully retrieved line of accounting' - schema: - $ref: 'definitions/LineOfAccounting.yaml' - '400': - $ref: '#/responses/InvalidRequest' - '401': - $ref: '#/responses/PermissionDenied' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '422': - $ref: '#/responses/UnprocessableEntity' - '500': - $ref: '#/responses/ServerError' + "200": + description: "Successfully retrieved line of accounting" + schema: + $ref: "definitions/LineOfAccounting.yaml" + "400": + $ref: "#/responses/InvalidRequest" + "401": + $ref: "#/responses/PermissionDenied" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "422": + $ref: "#/responses/UnprocessableEntity" + "500": + $ref: "#/responses/ServerError" /transportation-offices: get: produces: @@ -4030,20 +4063,20 @@ paths: minLength: 2 description: Search string for transportation offices responses: - '200': + "200": description: Successfully retrieved transportation offices schema: - $ref: '#/definitions/TransportationOffices' - '400': - $ref: '#/responses/InvalidRequest' - '401': - $ref: '#/responses/PermissionDenied' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '500': - $ref: '#/responses/ServerError' + $ref: "#/definitions/TransportationOffices" + "400": + $ref: "#/responses/InvalidRequest" + "401": + $ref: "#/responses/PermissionDenied" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "500": + $ref: "#/responses/ServerError" /open/transportation-offices: get: produces: @@ -4061,20 +4094,20 @@ paths: minLength: 2 description: Search string for transportation offices responses: - '200': + "200": description: Successfully retrieved transportation offices schema: - $ref: '#/definitions/TransportationOffices' - '400': - $ref: '#/responses/InvalidRequest' - '401': - $ref: '#/responses/PermissionDenied' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '500': - $ref: '#/responses/ServerError' + $ref: "#/definitions/TransportationOffices" + "400": + $ref: "#/responses/InvalidRequest" + "401": + $ref: "#/responses/PermissionDenied" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "500": + $ref: "#/responses/ServerError" /transportation-offices/gblocs: get: produces: @@ -4085,20 +4118,20 @@ paths: tags: - transportationOffice responses: - '200': + "200": description: Successfully retrieved transportation offices schema: - $ref: '#/definitions/GBLOCs' - '400': - $ref: '#/responses/InvalidRequest' - '401': - $ref: '#/responses/PermissionDenied' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '500': - $ref: '#/responses/ServerError' + $ref: "#/definitions/GBLOCs" + "400": + $ref: "#/responses/InvalidRequest" + "401": + $ref: "#/responses/PermissionDenied" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "500": + $ref: "#/responses/ServerError" /addresses/zip-city-lookup/{search}: get: summary: Returns city, state, postal code, and county associated with the specified full/partial postal code or city and state string @@ -4107,15 +4140,50 @@ paths: tags: - addresses parameters: - - in: path - name: search - type: string - required: true + - in: path + name: search + type: string + required: true responses: - '200': + "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" + /transportation_offices/{dutyLocationId}/counseling_offices/{serviceMemberId}: + get: + summary: Returns the counseling locations in the GBLOC matching the duty location + description: Returns the counseling locations matching the GBLOC from the selected duty location + operationId: showCounselingOffices + tags: + - transportationOffice + parameters: + - in: path + name: dutyLocationId + format: uuid + type: string + required: true + description: UUID of the duty location + - in: path + name: serviceMemberId + format: uuid + type: string + required: true + description: UUID of the service member, some counseling offices are branch specific + produces: + - application/json + responses: + '200': + description: Successfully retrieved counseling offices + schema: + $ref: '#/definitions/CounselingOffices' '400': $ref: '#/responses/InvalidRequest' '403': @@ -4123,7 +4191,7 @@ paths: '404': $ref: '#/responses/NotFound' '500': - $ref: '#/responses/ServerError' + description: internal server error /uploads: post: summary: Create a new upload @@ -4148,19 +4216,19 @@ paths: description: The file to upload. required: true responses: - '201': + "201": description: created upload schema: - $ref: 'definitions/Upload.yaml' - '400': + $ref: "definitions/Upload.yaml" + "400": description: invalid request - '403': + "403": description: not authorized - '404': + "404": description: not found - '413': + "413": description: payload is too large - '500': + "500": description: server error /re-service-items: get: @@ -4172,18 +4240,18 @@ paths: tags: - reServiceItems responses: - '200': + "200": description: Successfully retrieved all ReServiceItems. schema: - $ref: '#/definitions/ReServiceItems' - '400': - $ref: '#/responses/InvalidRequest' - '401': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '500': - $ref: '#/responses/ServerError' + $ref: "#/definitions/ReServiceItems" + "400": + $ref: "#/responses/InvalidRequest" + "401": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "500": + $ref: "#/responses/ServerError" /uploads/{uploadID}: delete: summary: Deletes an upload @@ -4204,17 +4272,17 @@ paths: format: uuid description: ID of the order that the upload belongs to responses: - '204': + "204": description: deleted - '400': + "400": description: invalid request schema: - $ref: '#/definitions/InvalidRequestResponsePayload' - '403': + $ref: "#/definitions/InvalidRequestResponsePayload" + "403": description: not authorized - '404': + "404": description: not found - '500': + "500": description: server error /uploads/get/: get: @@ -4222,20 +4290,20 @@ paths: - application/json parameters: [] responses: - '200': + "200": description: Successfully retrieved upload schema: - $ref: 'definitions/Upload.yaml' - '400': - $ref: '#/responses/InvalidRequest' - '401': - $ref: '#/responses/PermissionDenied' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '500': - $ref: '#/responses/ServerError' + $ref: "definitions/Upload.yaml" + "400": + $ref: "#/responses/InvalidRequest" + "401": + $ref: "#/responses/PermissionDenied" + "403": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "500": + $ref: "#/responses/ServerError" tags: - uploads description: Gets an upload @@ -4270,19 +4338,19 @@ paths: minimum: 0 maximum: 3 responses: - '201': + "201": description: updated upload schema: - $ref: 'definitions/Upload.yaml' - '400': + $ref: "definitions/Upload.yaml" + "400": description: invalid request - '403': + "403": description: not authorized - '404': + "404": description: not found - '413': + "413": description: payload is too large - '500': + "500": description: server error /application_parameters/{parameterName}: get: @@ -4299,15 +4367,15 @@ paths: required: true description: Parameter Name responses: - '200': + "200": description: Application Parameters schema: - $ref: '#/definitions/ApplicationParameters' - '400': + $ref: "#/definitions/ApplicationParameters" + "400": description: invalid request - '401': + "401": description: request requires user authentication - '500': + "500": description: server error /calendar/{countryCode}/is-weekend-holiday/{date}: get: @@ -4334,18 +4402,18 @@ paths: type: string format: date responses: - '200': + "200": description: Successfully determine if given date is weekend and/or holiday for given country. schema: - $ref: '#/definitions/IsDateWeekendHolidayInfo' - '400': - $ref: '#/responses/InvalidRequest' - '401': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' - '500': - $ref: '#/responses/ServerError' + $ref: "#/definitions/IsDateWeekendHolidayInfo" + "400": + $ref: "#/responses/InvalidRequest" + "401": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "500": + $ref: "#/responses/ServerError" /moves/{moveID}/assignOfficeUser: parameters: - description: ID of the move @@ -4364,16 +4432,16 @@ paths: name: body required: true schema: - $ref: '#/definitions/AssignOfficeUserBody' + $ref: "#/definitions/AssignOfficeUserBody" responses: - '200': + "200": description: Successfully assigned office user to the move schema: - $ref: '#/definitions/Move' - '404': - $ref: '#/responses/NotFound' - '500': - $ref: '#/responses/ServerError' + $ref: "#/definitions/Move" + "404": + $ref: "#/responses/NotFound" + "500": + $ref: "#/responses/ServerError" tags: - move description: assigns either a services counselor, task ordering officer, or task invoicing officer to the move @@ -4400,12 +4468,12 @@ paths: produces: - application/json responses: - '200': + "200": description: Successfully unassigned office user from the move schema: - $ref: '#/definitions/Move' - '500': - $ref: '#/responses/ServerError' + $ref: "#/definitions/Move" + "500": + $ref: "#/responses/ServerError" tags: - move description: unassigns either a services counselor, task ordering officer, or task invoicing officer from the move @@ -4424,7 +4492,7 @@ paths: produces: - application/json responses: - '200': + "200": description: Successfully unlocked officer's move(s). schema: type: object @@ -4432,8 +4500,8 @@ paths: successMessage: type: string example: OK - '500': - $ref: '#/responses/ServerError' + "500": + $ref: "#/responses/ServerError" tags: - move description: >- @@ -4485,7 +4553,7 @@ definitions: - instance ValidationError: allOf: - - $ref: '#/definitions/ClientError' + - $ref: "#/definitions/ClientError" - type: object properties: invalid_fields: @@ -4577,17 +4645,17 @@ definitions: type: string format: uuid transportationOffice: - $ref: 'definitions/TransportationOffice.yaml' + $ref: "definitions/TransportationOffice.yaml" transportationOfficeAssignments: type: array items: - $ref: 'definitions/TransportationOfficeAssignment.yaml' + $ref: "definitions/TransportationOfficeAssignment.yaml" active: type: boolean roles: type: array items: - $ref: '#/definitions/Role' + $ref: "#/definitions/Role" edipi: type: string otherUniqueId: @@ -4635,18 +4703,18 @@ definitions: type: string format: uuid transportationOffice: - $ref: 'definitions/TransportationOffice.yaml' + $ref: "definitions/TransportationOffice.yaml" OfficeUserCreate: type: object properties: email: type: string - example: 'user@userdomain.com' + example: "user@userdomain.com" title: Email x-nullable: false edipi: type: string - example: '1234567890' + example: "1234567890" maxLength: 10 title: EDIPI x-nullable: true @@ -4676,12 +4744,12 @@ definitions: transportationOfficeId: type: string format: uuid - example: 'c56a4180-65aa-42ec-a945-5fd21dec0538' + example: "c56a4180-65aa-42ec-a945-5fd21dec0538" x-nullable: false roles: type: array items: - $ref: '#/definitions/OfficeUserRole' + $ref: "#/definitions/OfficeUserRole" x-nullable: false required: - firstName @@ -4695,12 +4763,12 @@ definitions: properties: name: type: string - example: 'Task Ordering Officer' + example: "Task Ordering Officer" x-nullable: true title: name roleType: type: string - example: 'task_ordering_officer' + example: "task_ordering_officer" x-nullable: true title: roleType Customer: @@ -4734,9 +4802,9 @@ definitions: example: David x-nullable: true current_address: - $ref: 'definitions/Address.yaml' + $ref: "definitions/Address.yaml" backup_contact: - $ref: '#/definitions/BackupContact' + $ref: "#/definitions/BackupContact" id: type: string format: uuid @@ -4759,7 +4827,7 @@ definitions: pattern: '^[2-9]\d{2}-\d{3}-\d{4}$|^$' x-nullable: true backupAddress: - $ref: 'definitions/Address.yaml' + $ref: "definitions/Address.yaml" cacValidated: type: boolean x-nullable: true @@ -4796,9 +4864,9 @@ definitions: example: David x-nullable: true residentialAddress: - $ref: 'definitions/Address.yaml' + $ref: "definitions/Address.yaml" backupContact: - $ref: '#/definitions/BackupContact' + $ref: "#/definitions/BackupContact" id: type: string format: uuid @@ -4824,7 +4892,7 @@ definitions: pattern: '^[2-9]\d{2}-\d{3}-\d{4}$' x-nullable: true backupAddress: - $ref: 'definitions/Address.yaml' + $ref: "definitions/Address.yaml" cacValidated: type: boolean UpdateCustomerPayload: @@ -4856,9 +4924,9 @@ definitions: x-nullable: true current_address: allOf: - - $ref: 'definitions/Address.yaml' + - $ref: "definitions/Address.yaml" backup_contact: - $ref: '#/definitions/BackupContact' + $ref: "#/definitions/BackupContact" phoneIsPreferred: type: boolean emailIsPreferred: @@ -4870,22 +4938,22 @@ definitions: x-nullable: true backupAddress: allOf: - - $ref: 'definitions/Address.yaml' + - $ref: "definitions/Address.yaml" cac_validated: type: boolean CreateCustomerPayload: type: object properties: affiliation: - $ref: 'definitions/Affiliation.yaml' + $ref: "definitions/Affiliation.yaml" edipi: type: string - example: '1234567890' + example: "1234567890" maxLength: 10 x-nullable: false emplid: type: string - example: '9485155' + example: "9485155" maxLength: 7 x-nullable: true firstName: @@ -4923,12 +4991,12 @@ definitions: type: boolean residentialAddress: allOf: - - $ref: 'definitions/Address.yaml' + - $ref: "definitions/Address.yaml" backupContact: - $ref: '#/definitions/BackupContact' + $ref: "#/definitions/BackupContact" backupMailingAddress: allOf: - - $ref: 'definitions/Address.yaml' + - $ref: "definitions/Address.yaml" createOktaAccount: type: boolean cacUser: @@ -4937,7 +5005,7 @@ definitions: type: object properties: departmentIndicator: - $ref: 'definitions/DepartmentIndicator.yaml' + $ref: "definitions/DepartmentIndicator.yaml" effectiveDate: description: > The effective date for the Line Of Accounting (LOA) being fetched. Eg, the orders issue date or the Non-Temporary Storage (NTS) Move Task Order (MTO) approval date. @@ -4948,12 +5016,12 @@ definitions: users per customer request). type: string format: date - example: '2023-01-01' + example: "2023-01-01" tacCode: type: string minLength: 4 maxLength: 4 - example: 'F8J1' + example: "F8J1" SearchCustomersResult: type: object properties: @@ -4964,11 +5032,11 @@ definitions: totalCount: type: integer searchCustomers: - $ref: '#/definitions/SearchCustomers' + $ref: "#/definitions/SearchCustomers" SearchCustomers: type: array items: - $ref: '#/definitions/SearchCustomer' + $ref: "#/definitions/SearchCustomer" SearchCustomer: type: object properties: @@ -5020,6 +5088,11 @@ definitions: gunSafe: type: boolean example: false + weightRestriction: + type: integer + example: 1500 + x-formatting: weight + x-nullable: true nonTemporaryStorage: example: false type: boolean @@ -5175,22 +5248,22 @@ definitions: format: uuid x-nullable: true contractor: - $ref: '#/definitions/Contractor' + $ref: "#/definitions/Contractor" locator: type: string - example: '1K43AR' + example: "1K43AR" ordersId: type: string format: uuid example: c56a4180-65aa-42ec-a945-5fd21dec0538 orders: - $ref: '#/definitions/Order' + $ref: "#/definitions/Order" referenceId: example: 1001-3456 type: string x-nullable: true status: - $ref: '#/definitions/MoveStatus' + $ref: "#/definitions/MoveStatus" excessUnaccompaniedBaggageWeightQualifiedAt: type: string format: date-time @@ -5227,12 +5300,19 @@ definitions: x-nullable: true readOnly: true closeoutOffice: - $ref: 'definitions/TransportationOffice.yaml' + $ref: "definitions/TransportationOffice.yaml" closeoutOfficeId: type: string format: uuid description: The transportation office that will handle reviewing PPM Closeout documentation for Army and Air Force service members x-nullable: true + counselingOffice: + $ref: "definitions/TransportationOffice.yaml" + counselingOfficeId: + type: string + format: uuid + description: The transportation office that will handle services counseling for this move + x-nullable: true approvalsRequestedAt: type: string format: date-time @@ -5251,26 +5331,26 @@ definitions: eTag: type: string shipmentGBLOC: - $ref: '#/definitions/GBLOC' + $ref: "#/definitions/GBLOC" lockedByOfficeUserID: type: string format: uuid x-nullable: true lockedByOfficeUser: - $ref: '#/definitions/LockedOfficeUser' + $ref: "#/definitions/LockedOfficeUser" x-nullable: true lockExpiresAt: type: string format: date-time x-nullable: true additionalDocuments: - $ref: 'definitions/Document.yaml' + $ref: "definitions/Document.yaml" SCAssignedUser: - $ref: '#/definitions/AssignedOfficeUser' + $ref: "#/definitions/AssignedOfficeUser" TOOAssignedUser: - $ref: '#/definitions/AssignedOfficeUser' + $ref: "#/definitions/AssignedOfficeUser" TIOAssignedUser: - $ref: '#/definitions/AssignedOfficeUser' + $ref: "#/definitions/AssignedOfficeUser" MoveHistory: properties: id: @@ -5280,11 +5360,11 @@ definitions: type: string historyRecords: description: A list of MoveAuditHistory's connected to the move. - $ref: '#/definitions/MoveAuditHistories' + $ref: "#/definitions/MoveAuditHistories" locator: description: move locator type: string - example: '1K43AR' + example: "1K43AR" referenceId: description: move referenceID example: 1001-3456 @@ -5306,11 +5386,11 @@ definitions: type: string historyRecords: description: A list of MoveAuditHistory's connected to the move. - $ref: '#/definitions/MoveAuditHistories' + $ref: "#/definitions/MoveAuditHistories" locator: description: move locator type: string - example: '1K43AR' + example: "1K43AR" referenceId: description: move referenceID example: 1001-3456 @@ -5319,7 +5399,7 @@ definitions: MoveAuditHistories: type: array items: - $ref: '#/definitions/MoveAuditHistory' + $ref: "#/definitions/MoveAuditHistory" MoveAuditHistory: properties: id: @@ -5416,7 +5496,7 @@ definitions: MoveAuditHistoryItems: type: array items: - $ref: '#/definitions/MoveAuditHistoryItem' + $ref: "#/definitions/MoveAuditHistoryItem" MoveAuditHistoryItem: properties: columnName: @@ -5493,10 +5573,10 @@ definitions: format: uuid type: string customer: - $ref: '#/definitions/Customer' + $ref: "#/definitions/Customer" moveCode: type: string - example: 'H2XFJF' + example: "H2XFJF" first_name: type: string example: John @@ -5506,19 +5586,19 @@ definitions: example: Doe readOnly: true grade: - $ref: '#/definitions/Grade' + $ref: "#/definitions/Grade" agency: - $ref: 'definitions/Affiliation.yaml' + $ref: "definitions/Affiliation.yaml" entitlement: - $ref: '#/definitions/Entitlements' + $ref: "#/definitions/Entitlements" destinationDutyLocation: - $ref: 'definitions/DutyLocation.yaml' + $ref: "definitions/DutyLocation.yaml" destinationDutyLocationGBLOC: - $ref: '#/definitions/GBLOC' + $ref: "#/definitions/GBLOC" originDutyLocation: - $ref: 'definitions/DutyLocation.yaml' + $ref: "definitions/DutyLocation.yaml" originDutyLocationGBLOC: - $ref: '#/definitions/GBLOC' + $ref: "#/definitions/GBLOC" moveTaskOrderID: example: c56a4180-65aa-42ec-a945-5fd21dec0538 format: uuid @@ -5539,42 +5619,42 @@ definitions: order_number: type: string x-nullable: true - example: '030-00362' + example: "030-00362" order_type: - $ref: 'definitions/OrdersType.yaml' + $ref: "definitions/OrdersType.yaml" order_type_detail: - $ref: '#/definitions/OrdersTypeDetail' + $ref: "#/definitions/OrdersTypeDetail" x-nullable: true date_issued: type: string format: date - example: '2020-01-01' + example: "2020-01-01" report_by_date: type: string format: date - example: '2020-01-01' + example: "2020-01-01" department_indicator: - $ref: '#/definitions/DeptIndicator' + $ref: "#/definitions/DeptIndicator" x-nullable: true tac: type: string title: TAC - example: 'F8J1' + example: "F8J1" x-nullable: true sac: type: string title: SAC - example: 'N002214CSW32Y9' + example: "N002214CSW32Y9" x-nullable: true ntsTac: type: string title: NTS TAC - example: 'F8J1' + example: "F8J1" x-nullable: true ntsSac: type: string title: NTS SAC - example: 'N002214CSW32Y9' + example: "N002214CSW32Y9" x-nullable: true has_dependents: type: boolean @@ -5593,7 +5673,7 @@ definitions: naics: type: string orders_type: - $ref: 'definitions/OrdersType.yaml' + $ref: "definitions/OrdersType.yaml" eTag: type: string type: object @@ -5612,7 +5692,7 @@ definitions: Locations: type: array items: - $ref: '#/definitions/Location' + $ref: "#/definitions/Location" OrderBody: type: object properties: @@ -5637,9 +5717,9 @@ definitions: format: date title: Report-by date ordersType: - $ref: 'definitions/OrdersType.yaml' + $ref: "definitions/OrdersType.yaml" ordersTypeDetail: - $ref: '#/definitions/OrdersTypeDetail' + $ref: "#/definitions/OrdersTypeDetail" hasDependents: type: boolean title: Are dependents included in your orders? @@ -5650,25 +5730,30 @@ definitions: type: string format: uuid example: c56a4180-65aa-42ec-a945-5fd21dec0538 + counselingOfficeId: + type: string + format: uuid + example: cf1addea-a4f9-4173-8506-2bb82a064cb7 + x-nullable: true ordersNumber: type: string title: Orders Number x-nullable: true - example: '030-00362' + example: "030-00362" tac: type: string title: TAC - example: 'F8J1' + example: "F8J1" x-nullable: true sac: type: string title: SAC - example: 'N002214CSW32Y9' + example: "N002214CSW32Y9" x-nullable: true departmentIndicator: - $ref: '#/definitions/DeptIndicator' + $ref: "#/definitions/DeptIndicator" grade: - $ref: '#/definitions/Grade' + $ref: "#/definitions/Grade" originDutyLocationId: type: string format: uuid @@ -5703,25 +5788,25 @@ definitions: type: string description: The date and time that these orders were cut. format: date - example: '2018-04-26' + example: "2018-04-26" title: Orders date reportByDate: type: string description: Report By Date format: date - example: '2018-04-26' + example: "2018-04-26" title: Report-by date ordersType: - $ref: 'definitions/OrdersType.yaml' + $ref: "definitions/OrdersType.yaml" ordersTypeDetail: - $ref: '#/definitions/OrdersTypeDetail' + $ref: "#/definitions/OrdersTypeDetail" ordersNumber: type: string title: Orders Number x-nullable: true - example: '030-00362' + example: "030-00362" departmentIndicator: - $ref: '#/definitions/DeptIndicator' + $ref: "#/definitions/DeptIndicator" x-nullable: true originDutyLocationId: type: string @@ -5736,24 +5821,24 @@ definitions: title: HHG TAC minLength: 4 maxLength: 4 - example: 'F8J1' + example: "F8J1" x-nullable: true sac: title: HHG SAC - example: 'N002214CSW32Y9' + example: "N002214CSW32Y9" $ref: definitions/NullableString.yaml ntsTac: title: NTS TAC minLength: 4 maxLength: 4 - example: 'F8J1' + example: "F8J1" $ref: definitions/NullableString.yaml ntsSac: title: NTS SAC - example: 'N002214CSW32Y9' + example: "N002214CSW32Y9" $ref: definitions/NullableString.yaml grade: - $ref: '#/definitions/Grade' + $ref: "#/definitions/Grade" hasDependents: type: boolean title: Are dependents included in your orders? @@ -5771,18 +5856,18 @@ definitions: type: string description: The date and time that these orders were cut. format: date - example: '2018-04-26' + example: "2018-04-26" title: Orders date reportByDate: type: string description: Report By Date format: date - example: '2018-04-26' + example: "2018-04-26" title: Report-by date ordersType: - $ref: 'definitions/OrdersType.yaml' + $ref: "definitions/OrdersType.yaml" ordersTypeDetail: - $ref: '#/definitions/OrdersTypeDetail' + $ref: "#/definitions/OrdersTypeDetail" originDutyLocationId: type: string format: uuid @@ -5795,37 +5880,37 @@ definitions: type: string title: Orders Number x-nullable: true - example: '030-00362' + example: "030-00362" tac: type: string title: HHG TAC minLength: 4 maxLength: 4 - example: 'F8J1' + example: "F8J1" x-nullable: true sac: title: HHG SAC - example: 'N002214CSW32Y9' + example: "N002214CSW32Y9" $ref: definitions/NullableString.yaml ntsTac: title: NTS TAC minLength: 4 maxLength: 4 - example: 'F8J1' + example: "F8J1" $ref: definitions/NullableString.yaml ntsSac: title: NTS SAC - example: 'N002214CSW32Y9' + example: "N002214CSW32Y9" $ref: definitions/NullableString.yaml departmentIndicator: - $ref: '#/definitions/DeptIndicator' + $ref: "#/definitions/DeptIndicator" x-nullable: true ordersAcknowledgement: description: Confirmation that the new amended orders were reviewed after previously approving the original orders type: boolean x-nullable: true grade: - $ref: '#/definitions/Grade' + $ref: "#/definitions/Grade" required: - issueDate - reportByDate @@ -5836,12 +5921,12 @@ definitions: type: object properties: grade: - $ref: '#/definitions/Grade' + $ref: "#/definitions/Grade" dependentsAuthorized: type: boolean x-nullable: true agency: - $ref: 'definitions/Affiliation.yaml' + $ref: "definitions/Affiliation.yaml" proGearWeight: description: unit is in lbs example: 2000 @@ -5895,6 +5980,11 @@ definitions: example: 500 type: integer x-nullable: true + weightRestriction: + example: 1500 + type: integer + x-nullable: true + description: Indicates the weight restriction for the move to a particular location. UpdateBillableWeightPayload: type: object properties: @@ -5928,12 +6018,12 @@ definitions: type: object properties: grade: - $ref: '#/definitions/Grade' + $ref: "#/definitions/Grade" dependentsAuthorized: type: boolean x-nullable: true agency: - $ref: 'definitions/Affiliation.yaml' + $ref: "definitions/Affiliation.yaml" proGearWeight: minimum: 0 maximum: 2000 @@ -5987,6 +6077,11 @@ definitions: example: 500 type: integer x-nullable: true + weightRestriction: + example: 1500 + type: integer + x-nullable: true + description: Indicates the weight restriction for a move to a particular location. MoveTaskOrder: description: The Move (MoveTaskOrder) properties: @@ -6003,7 +6098,7 @@ definitions: type: string locator: type: string - example: '1K43AR' + example: "1K43AR" referenceId: example: 1001-3456 type: string @@ -6023,9 +6118,9 @@ definitions: format: date-time type: string destinationAddress: - $ref: 'definitions/Address.yaml' + $ref: "definitions/Address.yaml" pickupAddress: - $ref: 'definitions/Address.yaml' + $ref: "definitions/Address.yaml" destinationDutyLocation: example: 1f2270c7-7166-40ae-981e-b200ebdf3054 format: uuid @@ -6035,7 +6130,7 @@ definitions: format: uuid type: string entitlements: - $ref: '#/definitions/Entitlements' + $ref: "#/definitions/Entitlements" requestedPickupDate: format: date type: string @@ -6048,12 +6143,12 @@ definitions: type: object MoveTaskOrders: items: - $ref: '#/definitions/MoveTaskOrder' + $ref: "#/definitions/MoveTaskOrder" type: array PaymentRequest: properties: proofOfServiceDocs: - $ref: '#/definitions/ProofOfServiceDocs' + $ref: "#/definitions/ProofOfServiceDocs" id: example: c56a4180-65aa-42ec-a945-5fd21dec0538 format: uuid @@ -6063,7 +6158,7 @@ definitions: default: false type: boolean moveTaskOrder: - $ref: '#/definitions/Move' + $ref: "#/definitions/Move" moveTaskOrderID: example: c56a4180-65aa-42ec-a945-5fd21dec0538 format: uuid @@ -6073,9 +6168,9 @@ definitions: type: string x-nullable: true serviceItems: - $ref: '#/definitions/PaymentServiceItems' + $ref: "#/definitions/PaymentServiceItems" status: - $ref: '#/definitions/PaymentRequestStatus' + $ref: "#/definitions/PaymentRequestStatus" paymentRequestNumber: example: 1234-5678-1 readOnly: true @@ -6128,11 +6223,11 @@ definitions: type: object PaymentRequests: items: - $ref: '#/definitions/PaymentRequest' + $ref: "#/definitions/PaymentRequest" type: array PaymentServiceItems: items: - $ref: '#/definitions/PaymentServiceItem' + $ref: "#/definitions/PaymentServiceItem" type: array PaymentServiceItem: properties: @@ -6159,14 +6254,14 @@ definitions: example: Move management type: string mtoShipmentType: - $ref: 'definitions/MTOShipmentType.yaml' + $ref: "definitions/MTOShipmentType.yaml" mtoShipmentID: type: string format: uuid example: c56a4180-65aa-42ec-a945-5fd21dec0538 x-nullable: true status: - $ref: 'definitions/PaymentServiceItemStatus.yaml' + $ref: "definitions/PaymentServiceItemStatus.yaml" priceCents: type: integer format: cents @@ -6181,7 +6276,7 @@ definitions: readOnly: true format: string paymentServiceItemParams: - $ref: 'definitions/PaymentServiceItemParams.yaml' + $ref: "definitions/PaymentServiceItemParams.yaml" eTag: type: string tppsInvoiceAmountPaidPerServiceItemMillicents: @@ -6191,10 +6286,10 @@ definitions: x-nullable: true type: object PaymentRequestStatus: - $ref: 'definitions/PaymentRequestStatus.yaml' + $ref: "definitions/PaymentRequestStatus.yaml" ProofOfServiceDocs: items: - $ref: '#/definitions/ProofOfServiceDoc' + $ref: "#/definitions/ProofOfServiceDoc" type: array ProofOfServiceDoc: properties: @@ -6202,11 +6297,11 @@ definitions: type: boolean uploads: items: - $ref: 'definitions/Upload.yaml' + $ref: "definitions/Upload.yaml" type: array ShipmentsPaymentSITBalance: items: - $ref: '#/definitions/ShipmentPaymentSITBalance' + $ref: "#/definitions/ShipmentPaymentSITBalance" type: array ShipmentPaymentSITBalance: properties: @@ -6246,7 +6341,7 @@ definitions: type: object properties: shipmentType: - $ref: 'definitions/MTOShipmentType.yaml' + $ref: "definitions/MTOShipmentType.yaml" requestedPickupDate: format: date type: string @@ -6275,16 +6370,16 @@ definitions: x-nullable: true pickupAddress: allOf: - - $ref: 'definitions/Address.yaml' + - $ref: "definitions/Address.yaml" destinationAddress: allOf: - - $ref: 'definitions/Address.yaml' + - $ref: "definitions/Address.yaml" secondaryDeliveryAddress: allOf: - - $ref: 'definitions/Address.yaml' + - $ref: "definitions/Address.yaml" secondaryPickupAddress: allOf: - - $ref: 'definitions/Address.yaml' + - $ref: "definitions/Address.yaml" hasSecondaryPickupAddress: type: boolean x-nullable: true @@ -6295,10 +6390,10 @@ definitions: x-omitempty: false tertiaryDeliveryAddress: allOf: - - $ref: 'definitions/Address.yaml' + - $ref: "definitions/Address.yaml" tertiaryPickupAddress: allOf: - - $ref: 'definitions/Address.yaml' + - $ref: "definitions/Address.yaml" hasTertiaryPickupAddress: type: boolean x-nullable: true @@ -6316,14 +6411,14 @@ definitions: x-nullable: true x-omitempty: false destinationType: - $ref: 'definitions/DestinationType.yaml' + $ref: "definitions/DestinationType.yaml" agents: - $ref: 'definitions/MTOAgents.yaml' + $ref: "definitions/MTOAgents.yaml" x-nullable: true tacType: - $ref: 'definitions/LOATypeNullable.yaml' + $ref: "definitions/LOATypeNullable.yaml" sacType: - $ref: 'definitions/LOATypeNullable.yaml' + $ref: "definitions/LOATypeNullable.yaml" usesExternalVendor: type: boolean example: false @@ -6339,13 +6434,13 @@ definitions: x-nullable: true storageFacility: x-nullable: true - $ref: 'definitions/StorageFacility.yaml' + $ref: "definitions/StorageFacility.yaml" ppmShipment: - $ref: '#/definitions/UpdatePPMShipment' + $ref: "#/definitions/UpdatePPMShipment" boatShipment: - $ref: '#/definitions/UpdateBoatShipment' + $ref: "#/definitions/UpdateBoatShipment" mobileHomeShipment: - $ref: '#/definitions/UpdateMobileHomeShipment' + $ref: "#/definitions/UpdateMobileHomeShipment" UpdatePPMShipment: type: object properties: @@ -6361,34 +6456,34 @@ definitions: x-nullable: true pickupAddress: allOf: - - $ref: 'definitions/Address.yaml' + - $ref: "definitions/Address.yaml" actualPickupPostalCode: description: > The actual postal code where the PPM shipment started. To be filled once the customer has moved the shipment. format: zip type: string title: ZIP - example: '90210' + example: "90210" pattern: ^(\d{5})$ x-nullable: true secondaryPickupAddress: allOf: - - $ref: 'definitions/Address.yaml' + - $ref: "definitions/Address.yaml" destinationAddress: allOf: - - $ref: 'definitions/PPMDestinationAddress.yaml' + - $ref: "definitions/PPMDestinationAddress.yaml" actualDestinationPostalCode: description: > The actual postal code where the PPM shipment ended. To be filled once the customer has moved the shipment. format: zip type: string title: ZIP - example: '90210' + example: "90210" pattern: ^(\d{5})$ x-nullable: true secondaryDestinationAddress: allOf: - - $ref: 'definitions/Address.yaml' + - $ref: "definitions/Address.yaml" hasSecondaryPickupAddress: type: boolean x-nullable: true @@ -6399,10 +6494,10 @@ definitions: x-omitempty: false tertiaryPickupAddress: allOf: - - $ref: 'definitions/Address.yaml' + - $ref: "definitions/Address.yaml" tertiaryDestinationAddress: allOf: - - $ref: 'definitions/Address.yaml' + - $ref: "definitions/Address.yaml" hasTertiaryPickupAddress: type: boolean x-nullable: true @@ -6413,13 +6508,13 @@ definitions: x-omitempty: false w2Address: x-nullable: true - $ref: 'definitions/Address.yaml' + $ref: "definitions/Address.yaml" sitExpected: type: boolean x-nullable: true sitLocation: allOf: - - $ref: 'definitions/SITLocationType.yaml' + - $ref: "definitions/SITLocationType.yaml" - x-nullable: true sitEstimatedWeight: type: integer @@ -6477,7 +6572,7 @@ definitions: format: cents x-nullable: true advanceStatus: - $ref: 'definitions/PPMAdvanceStatus.yaml' + $ref: "definitions/PPMAdvanceStatus.yaml" x-nullable: true isActualExpenseReimbursement: description: Used for PPM shipments only. Denotes if this shipment uses the Actual Expense Reimbursement method. @@ -6571,7 +6666,7 @@ definitions: description: Indicates if the trailer that the customer used meets all the criteria to be claimable. type: boolean status: - $ref: 'definitions/PPMDocumentStatus.yaml' + $ref: "definitions/PPMDocumentStatus.yaml" reason: description: The reason the services counselor has excluded or rejected the item. type: string @@ -6586,7 +6681,7 @@ definitions: type: object properties: movingExpenseType: - $ref: 'definitions/OmittableMovingExpenseType.yaml' + $ref: "definitions/OmittableMovingExpenseType.yaml" description: description: A brief description of the expense. type: string @@ -6604,7 +6699,7 @@ definitions: type: string format: date status: - $ref: 'definitions/PPMDocumentStatus.yaml' + $ref: "definitions/PPMDocumentStatus.yaml" reason: description: The reason the services counselor has excluded or rejected the item. type: string @@ -6613,7 +6708,7 @@ definitions: type: integer sitLocation: allOf: - - $ref: 'definitions/SITLocationType.yaml' + - $ref: "definitions/SITLocationType.yaml" - x-nullable: true sitEstimatedCost: description: The estimated amount that the government will pay the service member to put their goods into storage. This estimated storage cost is separate from the estimated incentive. @@ -6641,13 +6736,13 @@ definitions: type: integer minimum: 0 status: - $ref: 'definitions/PPMDocumentStatus.yaml' + $ref: "definitions/PPMDocumentStatus.yaml" reason: description: The reason the services counselor has excluded or rejected the item. type: string MTOShipments: items: - $ref: 'definitions/MTOShipment.yaml' + $ref: "definitions/MTOShipment.yaml" type: array CreateMTOShipment: type: object @@ -6693,17 +6788,17 @@ definitions: example: handle with care x-nullable: true agents: - $ref: 'definitions/MTOAgents.yaml' + $ref: "definitions/MTOAgents.yaml" mtoServiceItems: - $ref: 'definitions/MTOServiceItems.yaml' + $ref: "definitions/MTOServiceItems.yaml" pickupAddress: description: The address where the movers should pick up this shipment. allOf: - - $ref: 'definitions/Address.yaml' + - $ref: "definitions/Address.yaml" destinationAddress: description: Where the movers should deliver this shipment. allOf: - - $ref: 'definitions/Address.yaml' + - $ref: "definitions/Address.yaml" hasSecondaryPickupAddress: type: boolean x-nullable: true @@ -6711,7 +6806,7 @@ definitions: secondaryPickupAddress: description: The address where the movers should pick up this shipment. allOf: - - $ref: 'definitions/Address.yaml' + - $ref: "definitions/Address.yaml" hasSecondaryDeliveryAddress: type: boolean x-nullable: true @@ -6719,7 +6814,7 @@ definitions: secondaryDeliveryAddress: description: Where the movers should deliver this shipment. allOf: - - $ref: 'definitions/Address.yaml' + - $ref: "definitions/Address.yaml" hasTertiaryPickupAddress: type: boolean x-nullable: true @@ -6727,7 +6822,7 @@ definitions: tertiaryPickupAddress: description: The address where the movers should pick up this shipment. allOf: - - $ref: 'definitions/Address.yaml' + - $ref: "definitions/Address.yaml" hasTertiaryDeliveryAddress: type: boolean x-nullable: true @@ -6735,18 +6830,18 @@ definitions: tertiaryDeliveryAddress: description: Where the movers should deliver this shipment. allOf: - - $ref: 'definitions/Address.yaml' + - $ref: "definitions/Address.yaml" destinationType: - $ref: 'definitions/DestinationType.yaml' + $ref: "definitions/DestinationType.yaml" shipmentType: - $ref: 'definitions/MTOShipmentType.yaml' + $ref: "definitions/MTOShipmentType.yaml" tacType: allOf: - - $ref: 'definitions/LOAType.yaml' + - $ref: "definitions/LOAType.yaml" - x-nullable: true sacType: allOf: - - $ref: 'definitions/LOAType.yaml' + - $ref: "definitions/LOAType.yaml" - x-nullable: true usesExternalVendor: type: boolean @@ -6763,13 +6858,13 @@ definitions: x-formatting: weight storageFacility: x-nullable: true - $ref: 'definitions/StorageFacility.yaml' + $ref: "definitions/StorageFacility.yaml" mobileHomeShipment: - $ref: '#/definitions/CreateMobileHomeShipment' + $ref: "#/definitions/CreateMobileHomeShipment" ppmShipment: - $ref: '#/definitions/CreatePPMShipment' + $ref: "#/definitions/CreatePPMShipment" boatShipment: - $ref: '#/definitions/CreateBoatShipment' + $ref: "#/definitions/CreateBoatShipment" required: - moveTaskOrderID - shipmentType @@ -6783,22 +6878,22 @@ definitions: type: string pickupAddress: allOf: - - $ref: 'definitions/Address.yaml' + - $ref: "definitions/Address.yaml" secondaryPickupAddress: allOf: - - $ref: 'definitions/Address.yaml' + - $ref: "definitions/Address.yaml" tertiaryPickupAddress: allOf: - - $ref: 'definitions/Address.yaml' + - $ref: "definitions/Address.yaml" destinationAddress: allOf: - - $ref: 'definitions/PPMDestinationAddress.yaml' + - $ref: "definitions/PPMDestinationAddress.yaml" secondaryDestinationAddress: allOf: - - $ref: 'definitions/Address.yaml' + - $ref: "definitions/Address.yaml" tertiaryDestinationAddress: allOf: - - $ref: 'definitions/Address.yaml' + - $ref: "definitions/Address.yaml" hasSecondaryPickupAddress: type: boolean x-nullable: true @@ -6819,7 +6914,7 @@ definitions: type: boolean sitLocation: allOf: - - $ref: 'definitions/SITLocationType.yaml' + - $ref: "definitions/SITLocationType.yaml" - x-nullable: true sitEstimatedWeight: type: integer @@ -6952,7 +7047,7 @@ definitions: minimum: 1 requestReason: description: Reason from service counselor-provided picklist for SIT Duration Update - example: 'AWAITING_COMPLETION_OF_RESIDENCE' + example: "AWAITING_COMPLETION_OF_RESIDENCE" type: string enum: - SERIOUS_ILLNESS_MEMBER @@ -6999,7 +7094,7 @@ definitions: properties: requestReason: description: Reason from service counselor-provided picklist for SIT Duration Update - example: 'AWAITING_COMPLETION_OF_RESIDENCE' + example: "AWAITING_COMPLETION_OF_RESIDENCE" type: string enum: - SERIOUS_ILLNESS_MEMBER @@ -7060,14 +7155,14 @@ definitions: type: string x-nullable: true status: - $ref: '#/definitions/PaymentRequestStatus' + $ref: "#/definitions/PaymentRequestStatus" eTag: type: string type: object BulkAssignmentMoveIDs: type: array items: - $ref: '#/definitions/BulkAssignmentMoveID' + $ref: "#/definitions/BulkAssignmentMoveID" BulkAssignmentMoveID: type: string format: uuid @@ -7075,7 +7170,7 @@ definitions: AvailableOfficeUsers: type: array items: - $ref: '#/definitions/AvailableOfficeUser' + $ref: "#/definitions/AvailableOfficeUser" AvailableOfficeUser: type: object properties: @@ -7091,17 +7186,18 @@ definitions: type: boolean workload: type: integer + x-omitempty: false BulkAssignmentData: type: object properties: availableOfficeUsers: - $ref: '#/definitions/AvailableOfficeUsers' + $ref: "#/definitions/AvailableOfficeUsers" bulkAssignmentMoveIDs: - $ref: '#/definitions/BulkAssignmentMoveIDs' + $ref: "#/definitions/BulkAssignmentMoveIDs" QueueMoves: type: array items: - $ref: '#/definitions/QueueMove' + $ref: "#/definitions/QueueMove" QueueMove: type: object properties: @@ -7109,9 +7205,9 @@ definitions: type: string format: uuid customer: - $ref: '#/definitions/Customer' + $ref: "#/definitions/Customer" status: - $ref: '#/definitions/MoveStatus' + $ref: "#/definitions/MoveStatus" locator: type: string submittedAt: @@ -7127,15 +7223,15 @@ definitions: type: string x-nullable: true departmentIndicator: - $ref: '#/definitions/DeptIndicator' + $ref: "#/definitions/DeptIndicator" shipmentsCount: type: integer originDutyLocation: - $ref: 'definitions/DutyLocation.yaml' + $ref: "definitions/DutyLocation.yaml" destinationDutyLocation: - $ref: 'definitions/DutyLocation.yaml' + $ref: "definitions/DutyLocation.yaml" originGBLOC: - $ref: '#/definitions/GBLOC' + $ref: "#/definitions/GBLOC" ppmType: type: string enum: [FULL, PARTIAL] @@ -7155,14 +7251,14 @@ definitions: format: uuid x-nullable: true lockedByOfficeUser: - $ref: '#/definitions/LockedOfficeUser' + $ref: "#/definitions/LockedOfficeUser" x-nullable: true lockExpiresAt: type: string format: date-time x-nullable: true ppmStatus: - $ref: '#/definitions/PPMStatus' + $ref: "#/definitions/PPMStatus" x-nullable: true counselingOffice: type: string @@ -7172,10 +7268,10 @@ definitions: format: uuid x-nullable: true assignedTo: - $ref: '#/definitions/AssignedOfficeUser' + $ref: "#/definitions/AssignedOfficeUser" x-nullable: true availableOfficeUsers: - $ref: '#/definitions/AvailableOfficeUsers' + $ref: "#/definitions/AvailableOfficeUsers" assignable: type: boolean QueueMovesResult: @@ -7188,7 +7284,7 @@ definitions: totalCount: type: integer queueMoves: - $ref: '#/definitions/QueueMoves' + $ref: "#/definitions/QueueMoves" ListPrimeMove: description: > An abbreviated definition for a move, without all the nested information (shipments, service items, etc). Used to @@ -7201,7 +7297,7 @@ definitions: type: string moveCode: type: string - example: 'HYXFJF' + example: "HYXFJF" readOnly: true createdAt: format: date-time @@ -7212,10 +7308,10 @@ definitions: format: uuid type: string destinationGBLOC: - example: 'AGFM' + example: "AGFM" type: string destinationPostalCode: - example: '90210' + example: "90210" type: string referenceId: example: 1001-3456 @@ -7247,7 +7343,7 @@ definitions: ListPrimeMoves: type: array items: - $ref: '#/definitions/ListPrimeMove' + $ref: "#/definitions/ListPrimeMove" ListPrimeMovesResult: type: object properties: @@ -7258,7 +7354,7 @@ definitions: totalCount: type: integer queueMoves: - $ref: '#/definitions/ListPrimeMoves' + $ref: "#/definitions/ListPrimeMoves" QueuePaymentRequest: type: object properties: @@ -7269,9 +7365,9 @@ definitions: type: string format: uuid customer: - $ref: '#/definitions/Customer' + $ref: "#/definitions/Customer" status: - $ref: '#/definitions/QueuePaymentRequestStatus' + $ref: "#/definitions/QueuePaymentRequestStatus" age: type: number format: double @@ -7282,11 +7378,11 @@ definitions: locator: type: string departmentIndicator: - $ref: '#/definitions/DeptIndicator' + $ref: "#/definitions/DeptIndicator" originGBLOC: - $ref: '#/definitions/GBLOC' + $ref: "#/definitions/GBLOC" originDutyLocation: - $ref: 'definitions/DutyLocation.yaml' + $ref: "definitions/DutyLocation.yaml" orderType: type: string x-nullable: true @@ -7299,10 +7395,10 @@ definitions: format: date-time x-nullable: true assignedTo: - $ref: '#/definitions/AssignedOfficeUser' + $ref: "#/definitions/AssignedOfficeUser" x-nullable: true availableOfficeUsers: - $ref: '#/definitions/AvailableOfficeUsers' + $ref: "#/definitions/AvailableOfficeUsers" assignable: type: boolean counselingOffice: @@ -7311,7 +7407,7 @@ definitions: QueuePaymentRequests: type: array items: - $ref: '#/definitions/QueuePaymentRequest' + $ref: "#/definitions/QueuePaymentRequest" QueuePaymentRequestsResult: type: object properties: @@ -7322,7 +7418,7 @@ definitions: totalCount: type: integer queuePaymentRequests: - $ref: '#/definitions/QueuePaymentRequests' + $ref: "#/definitions/QueuePaymentRequests" QueuePaymentRequestStatus: enum: - Payment requested @@ -7334,7 +7430,7 @@ definitions: SearchMoves: type: array items: - $ref: '#/definitions/SearchMove' + $ref: "#/definitions/SearchMove" SearchMove: type: object properties: @@ -7358,7 +7454,7 @@ definitions: example: 9551-6199-2 x-nullable: true status: - $ref: '#/definitions/MoveStatus' + $ref: "#/definitions/MoveStatus" locator: type: string branch: @@ -7369,13 +7465,13 @@ definitions: format: zip type: string title: ZIP - example: '90210' + example: "90210" pattern: ^(\d{5})$ destinationPostalCode: format: zip type: string title: ZIP - example: '90210' + example: "90210" pattern: ^(\d{5})$ requestedPickupDate: type: string @@ -7388,9 +7484,9 @@ definitions: format: date x-nullable: true originGBLOC: - $ref: '#/definitions/GBLOC' + $ref: "#/definitions/GBLOC" destinationGBLOC: - $ref: '#/definitions/GBLOC' + $ref: "#/definitions/GBLOC" lockedByOfficeUserID: type: string format: uuid @@ -7412,7 +7508,7 @@ definitions: totalCount: type: integer searchMoves: - $ref: '#/definitions/SearchMoves' + $ref: "#/definitions/SearchMoves" GBLOC: type: string enum: @@ -7507,7 +7603,7 @@ definitions: EvaluationReportList: type: array items: - $ref: '#/definitions/EvaluationReport' + $ref: "#/definitions/EvaluationReport" EvaluationReport: type: object description: An evaluation report @@ -7529,28 +7625,28 @@ definitions: x-nullable: true readOnly: true type: - $ref: '#/definitions/EvaluationReportType' + $ref: "#/definitions/EvaluationReportType" inspectionType: - $ref: '#/definitions/EvaluationReportInspectionType' + $ref: "#/definitions/EvaluationReportInspectionType" x-nullable: true inspectionDate: type: string format: date x-nullable: true officeUser: - $ref: '#/definitions/EvaluationReportOfficeUser' + $ref: "#/definitions/EvaluationReportOfficeUser" location: - $ref: '#/definitions/EvaluationReportLocation' + $ref: "#/definitions/EvaluationReportLocation" x-nullable: true reportViolations: - $ref: '#/definitions/ReportViolations' + $ref: "#/definitions/ReportViolations" x-nullable: true gsrAppeals: - $ref: '#/definitions/GSRAppeals' + $ref: "#/definitions/GSRAppeals" x-nullable: true locationDescription: type: string - example: 'Route 66 at crash inspection site 3' + example: "Route 66 at crash inspection site 3" x-nullable: true observedShipmentDeliveryDate: type: string @@ -7563,18 +7659,18 @@ definitions: timeDepart: type: string x-nullable: true - pattern: '^(0[0-9]|1[0-9]|2[0-3]):[0-5][0-9]$' - example: '14:30' + pattern: "^(0[0-9]|1[0-9]|2[0-3]):[0-5][0-9]$" + example: "14:30" evalStart: type: string x-nullable: true - pattern: '^(0[0-9]|1[0-9]|2[0-3]):[0-5][0-9]$' - example: '15:00' + pattern: "^(0[0-9]|1[0-9]|2[0-3]):[0-5][0-9]$" + example: "15:00" evalEnd: type: string x-nullable: true - pattern: '^(0[0-9]|1[0-9]|2[0-3]):[0-5][0-9]$' - example: '18:00' + pattern: "^(0[0-9]|1[0-9]|2[0-3]):[0-5][0-9]$" + example: "18:00" violationsObserved: type: boolean x-nullable: true @@ -7686,7 +7782,7 @@ definitions: PWSViolations: type: array items: - $ref: '#/definitions/PWSViolation' + $ref: "#/definitions/PWSViolation" AssociateReportViolations: type: object description: A list of PWS violation string ids to associate with an evaluation report @@ -7713,14 +7809,14 @@ definitions: format: uuid type: string violation: - $ref: '#/definitions/PWSViolation' + $ref: "#/definitions/PWSViolation" gsrAppeals: - $ref: '#/definitions/GSRAppeals' + $ref: "#/definitions/GSRAppeals" x-nullable: true ReportViolations: type: array items: - $ref: '#/definitions/ReportViolation' + $ref: "#/definitions/ReportViolation" GSRAppealStatusType: type: string enum: @@ -7729,7 +7825,7 @@ definitions: GSRAppeals: type: array items: - $ref: '#/definitions/GSRAppeal' + $ref: "#/definitions/GSRAppeal" GSRAppeal: type: object description: An object associating appeals on violations and serious incidents @@ -7751,12 +7847,12 @@ definitions: format: uuid type: string officeUser: - $ref: '#/definitions/EvaluationReportOfficeUser' + $ref: "#/definitions/EvaluationReportOfficeUser" isSeriousIncident: type: boolean example: false appealStatus: - $ref: '#/definitions/GSRAppealStatusType' + $ref: "#/definitions/GSRAppealStatusType" remarks: type: string example: Office user remarks @@ -7764,22 +7860,39 @@ definitions: type: string format: date-time readOnly: true + ReServiceItems: + type: array + items: + $ref: "definitions/ReServiceItem.yaml" TransportationOffices: type: array items: - $ref: 'definitions/TransportationOffice.yaml' + $ref: "definitions/TransportationOffice.yaml" VLocations: type: array items: $ref: "definitions/VLocation.yaml" - ReServiceItems: - type: array - items: - $ref: "definitions/ReServiceItem.yaml" GBLOCs: type: array items: type: string + CounselingOffices: + type: array + items: + $ref: "#/definitions/CounselingOffice" + CounselingOffice: + type: object + properties: + id: + type: string + format: uuid + example: c56a4180-65aa-42ec-a945-5fd21dec0538 + name: + type: string + example: Fort Bragg North Station + required: + - id + - name MovePayload: type: object properties: @@ -7798,9 +7911,9 @@ definitions: readOnly: true locator: type: string - example: '12432' + example: "12432" status: - $ref: '#/definitions/MoveStatus' + $ref: "#/definitions/MoveStatus" created_at: type: string format: date-time @@ -7812,9 +7925,9 @@ definitions: format: date-time x-nullable: true mto_shipments: - $ref: '#/definitions/MTOShipments' + $ref: "#/definitions/MTOShipments" closeout_office: - $ref: '#/definitions/TransportationOffice' + $ref: "#/definitions/TransportationOffice" cancel_reason: type: string example: Change of orders @@ -7826,7 +7939,7 @@ definitions: type: string readOnly: true additionalDocuments: - $ref: 'definitions/Document.yaml' + $ref: "definitions/Document.yaml" required: - id - orders_id @@ -7844,7 +7957,7 @@ definitions: date: type: string format: date - example: '2018-09-25' + example: "2018-09-25" is_weekend: type: boolean is_holiday: @@ -7883,28 +7996,28 @@ responses: InvalidRequest: description: The request payload is invalid schema: - $ref: '#/definitions/Error' + $ref: "#/definitions/Error" NotFound: description: The requested resource wasn't found schema: - $ref: '#/definitions/Error' + $ref: "#/definitions/Error" Conflict: description: Conflict error schema: - $ref: '#/definitions/Error' + $ref: "#/definitions/Error" PermissionDenied: description: The request was denied schema: - $ref: '#/definitions/Error' + $ref: "#/definitions/Error" ServerError: description: A server error occurred schema: - $ref: '#/definitions/Error' + $ref: "#/definitions/Error" PreconditionFailed: description: Precondition failed schema: - $ref: '#/definitions/Error' + $ref: "#/definitions/Error" UnprocessableEntity: description: The payload was unprocessable. schema: - $ref: '#/definitions/ValidationError' + $ref: "#/definitions/ValidationError" diff --git a/swagger-def/prime.yaml b/swagger-def/prime.yaml index f34788446eb..5ca10ba69f1 100644 --- a/swagger-def/prime.yaml +++ b/swagger-def/prime.yaml @@ -808,9 +808,9 @@ paths: To create a service item, please use [createMTOServiceItem](#mtoServiceItem/createMTOServiceItem)) endpoint. - * Resubmitting rejected SIT service items: This endpoint will handle the logic of changing the status of rejected SIT service items from + * Resubmitting rejected SIT/Accessorial service items: This endpoint will handle the logic of changing the status of rejected SIT/Accessorial service items from REJECTED to SUBMITTED. Please provide the `requestedApprovalsRequestedStatus: true` when resubmitting as this will give attention to the TOO to - review the resubmitted SIT service item. Another note, `updateReason` must have a different value than the current `reason` value on the service item. + review the resubmitted SIT/Accessorial service item. Another note, `updateReason` must have a different value than the current `reason` value on the service item. If this value is not updated, then an error will be sent back. The following SIT service items can be resubmitted following a rejection: @@ -823,7 +823,11 @@ paths: - DDSFSC - DOSFSC - At a MINIMUM, the payload for resubmitting a rejected SIT service item must look like this: + The following Accessorial service items can be resubmitted following a rejection: + - IOSHUT + - IDSHUT + + At a MINIMUM, the payload for resubmitting a rejected SIT/Accessorial service item must look like this: ```json { "reServiceCode": "DDFSIT", @@ -1243,6 +1247,29 @@ paths: ] ``` --- + + International Basic Service Items & Accepted Payment Request Parameters: + --- + **IOSHUT - International origin shuttle service** + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` + + **IDSHUT - International destination shuttle service** + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` + --- operationId: createPaymentRequest tags: - paymentRequest @@ -1693,6 +1720,8 @@ definitions: $ref: 'definitions/prime/MTOServiceItemShuttle.yaml' MTOServiceItemInternationalFuelSurcharge: # spectral oas2-unused-definition is OK here due to polymorphism $ref: 'definitions/prime/MTOServiceItemInternationalFuelSurcharge.yaml' + MTOServiceItemInternationalShuttle: # spectral oas2-unused-definition is OK here due to polymorphism + $ref: 'definitions/prime/MTOServiceItemInternationalShuttle.yaml' MTOShipment: type: object properties: @@ -1777,6 +1806,8 @@ definitions: * DOSHUT - UpdateMTOServiceItemShuttle * PODFSC - UpdateMTOServiceItemInternationalPortFSC * POEFSC - UpdateMTOServiceItemInternationalPortFSC + * IDSHUT - UpdateMTOServiceItemInternationalShuttle + * IOSHUT - UpdateMTOServiceItemInternationalShuttle The documentation will then update with the supported fields. type: string @@ -1784,6 +1815,7 @@ definitions: - UpdateMTOServiceItemSIT - UpdateMTOServiceItemShuttle - UpdateMTOServiceItemInternationalPortFSC + - UpdateMTOServiceItemInternationalShuttle UpdateMTOServiceItemShuttle: # spectral oas2-unused-definition is OK here due to polymorphism description: | Subtype used to provide the estimated weight and actual weight for shuttle. This is not creating a new service item but rather updating an existing service item. @@ -1809,6 +1841,35 @@ definitions: enum: - DDSHUT # Domestic Destination Shuttle - DOSHUT # Domestic Origin Shuttle + UpdateMTOServiceItemInternationalShuttle: # spectral oas2-unused-definition is OK here due to polymorphism + description: | + Subtype used to provide the estimated weight and actual weight for shuttle. This is not creating a new service item but rather updating an existing service item. + allOf: + - $ref: '#/definitions/UpdateMTOServiceItem' + - type: object + properties: + actualWeight: + type: integer + example: 4000 + description: Provided by the movers, based on weight tickets. Relevant for shuttling (IDSHUT & IOSHUT) service items. + x-nullable: true + x-omitempty: false + estimatedWeight: + type: integer + example: 4200 + description: An estimate of how much weight from a shipment will be included in a shuttling (IDSHUT & IOSHUT) service item. + x-nullable: true + x-omitempty: false + requestApprovalsRequestedStatus: + description: Indicates if "Approvals Requested" status is being requested. + type: boolean + x-nullable: true + reServiceCode: + type: string + description: Service code allowed for this model type. + enum: + - IDSHUT # Domestic Destination Shuttle + - IOSHUT # Domestic Origin Shuttle UpdateMTOServiceItemSIT: # spectral oas2-unused-definition is OK here due to polymorphism description: | Subtype used to provide the departure date for origin or destination SIT. This is not creating a new service item but rather updating an existing service item. diff --git a/swagger-def/prime_v2.yaml b/swagger-def/prime_v2.yaml index bf11d799eb1..5fb651099b5 100644 --- a/swagger-def/prime_v2.yaml +++ b/swagger-def/prime_v2.yaml @@ -357,6 +357,8 @@ definitions: $ref: 'definitions/prime/MTOServiceItemOriginSIT.yaml' MTOServiceItemShuttle: # spectral oas2-unused-definition is OK here due to polymorphism $ref: 'definitions/prime/MTOServiceItemShuttle.yaml' + MTOServiceItemInternationalShuttle: # spectral oas2-unused-definition is OK here due to polymorphism + $ref: 'definitions/prime/MTOServiceItemInternationalShuttle.yaml' CreateMTOShipment: type: object properties: @@ -611,12 +613,15 @@ definitions: * DOFSIT - UpdateMTOServiceItemSIT * DDSHUT - UpdateMTOServiceItemShuttle * DOSHUT - UpdateMTOServiceItemShuttle + * IDSHUT - UpdateMTOServiceItemInternationalShuttle + * IOSHUT - UpdateMTOServiceItemInternationalShuttle The documentation will then update with the supported fields. type: string enum: - UpdateMTOServiceItemSIT - UpdateMTOServiceItemShuttle + - UpdateMTOServiceItemInternationalShuttle UpdateMTOServiceItem: description: UpdateMTOServiceItem describes a base type of a service item. Polymorphic type. Both Move Task Orders and MTO Shipments will have MTO Service Items. type: object @@ -656,6 +661,35 @@ definitions: enum: - DDSHUT # Domestic Destination Shuttle - DOSHUT # Domestic Origin Shuttle + UpdateMTOServiceItemInternationalShuttle: # spectral oas2-unused-definition is OK here due to polymorphism + description: | + Subtype used to provide the estimated weight and actual weight for shuttle. This is not creating a new service item but rather updating an existing service item. + allOf: + - $ref: '#/definitions/UpdateMTOServiceItem' + - type: object + properties: + actualWeight: + type: integer + example: 4000 + description: Provided by the movers, based on weight tickets. Relevant for shuttling (IDSHUT & IOSHUT) service items. + x-nullable: true + x-omitempty: false + estimatedWeight: + type: integer + example: 4200 + description: An estimate of how much weight from a shipment will be included in a shuttling (IDSHUT & IOSHUT) service item. + x-nullable: true + x-omitempty: false + requestApprovalsRequestedStatus: + description: Indicates if "Approvals Requested" status is being requested. + type: boolean + x-nullable: true + reServiceCode: + type: string + description: Service code allowed for this model type. + enum: + - IDSHUT # Domestic Destination Shuttle + - IOSHUT # Domestic Origin Shuttle UpdatePPMShipment: description: The PPM specific fields of the shipment with values being changed type: object diff --git a/swagger-def/prime_v3.yaml b/swagger-def/prime_v3.yaml index 66a497501f4..b4296ffde49 100644 --- a/swagger-def/prime_v3.yaml +++ b/swagger-def/prime_v3.yaml @@ -348,6 +348,8 @@ definitions: $ref: 'definitions/prime/MTOServiceItemOriginSIT.yaml' MTOServiceItemShuttle: # spectral oas2-unused-definition is OK here due to polymorphism $ref: 'definitions/prime/MTOServiceItemShuttle.yaml' + MTOServiceItemInternationalShuttle: # spectral oas2-unused-definition is OK here due to polymorphism + $ref: 'definitions/prime/MTOServiceItemInternationalShuttle.yaml' MTOServiceItemInternationalFuelSurcharge: # spectral oas2-unused-definition is OK here due to polymorphism $ref: 'definitions/prime/MTOServiceItemInternationalFuelSurcharge.yaml' CreateMTOShipment: @@ -638,12 +640,15 @@ definitions: * DOFSIT - UpdateMTOServiceItemSIT * DDSHUT - UpdateMTOServiceItemShuttle * DOSHUT - UpdateMTOServiceItemShuttle + * IDSHUT - UpdateMTOServiceItemInternationalShuttle + * IOSHUT - UpdateMTOServiceItemInternationalShuttle The documentation will then update with the supported fields. type: string enum: - UpdateMTOServiceItemSIT - UpdateMTOServiceItemShuttle + - UpdateMTOServiceItemInternationalShuttle UpdateMTOServiceItem: description: UpdateMTOServiceItem describes a base type of a service item. Polymorphic type. Both Move Task Orders and MTO Shipments will have MTO Service Items. type: object @@ -683,6 +688,35 @@ definitions: enum: - DDSHUT # Domestic Destination Shuttle - DOSHUT # Domestic Origin Shuttle + UpdateMTOServiceItemInternationalShuttle: # spectral oas2-unused-definition is OK here due to polymorphism + description: | + Subtype used to provide the estimated weight and actual weight for shuttle. This is not creating a new service item but rather updating an existing service item. + allOf: + - $ref: '#/definitions/UpdateMTOServiceItem' + - type: object + properties: + actualWeight: + type: integer + example: 4000 + description: Provided by the movers, based on weight tickets. Relevant for shuttling (IDSHUT & IOSHUT) service items. + x-nullable: true + x-omitempty: false + estimatedWeight: + type: integer + example: 4200 + description: An estimate of how much weight from a shipment will be included in a shuttling (IDSHUT & IOSHUT) service item. + x-nullable: true + x-omitempty: false + requestApprovalsRequestedStatus: + description: Indicates if "Approvals Requested" status is being requested. + type: boolean + x-nullable: true + reServiceCode: + type: string + description: Service code allowed for this model type. + enum: + - IDSHUT # Domestic Destination Shuttle + - IOSHUT # Domestic Origin Shuttle UpdatePPMShipment: description: The PPM specific fields of the shipment with values being changed type: object diff --git a/swagger-def/support.yaml b/swagger-def/support.yaml index 4b230febd84..b1b34351e9a 100644 --- a/swagger-def/support.yaml +++ b/swagger-def/support.yaml @@ -878,6 +878,10 @@ definitions: gunSafe: type: boolean example: false + weightRestriction: + example: 1500 + type: integer + x-nullable: true nonTemporaryStorage: example: false type: boolean @@ -1299,6 +1303,7 @@ definitions: - MTOServiceItemOriginSIT - MTOServiceItemDestSIT - MTOServiceItemShuttle + - MTOServiceItemInternationalShuttle - MTOServiceItemDomesticCrating - MTOServiceItemInternationalCrating MTOServiceItemOriginSIT: # spectral oas2-unused-definition is OK here due to polymorphism diff --git a/swagger/admin.yaml b/swagger/admin.yaml index 4fcc51bd46d..5082f703079 100644 --- a/swagger/admin.yaml +++ b/swagger/admin.yaml @@ -1588,6 +1588,31 @@ paths: description: Not authorized to update an Office User '500': description: Server error + delete: + produces: + - application/json + summary: Deletes an Office User + description: >- + Deletes a single office user in any status. This endpoint is used in the + Admin UI that will allow the admin user to delete an office user. + operationId: deleteOfficeUser + tags: + - Office users + parameters: + - in: path + name: officeUserId + type: string + format: uuid + required: true + responses: + '204': + description: deleted + '401': + description: request requires user authentication + '404': + description: Office User not found + '500': + description: server error /admin-users: get: produces: diff --git a/swagger/ghc.yaml b/swagger/ghc.yaml index 81bca009269..fd0a75620aa 100644 --- a/swagger/ghc.yaml +++ b/swagger/ghc.yaml @@ -1474,7 +1474,6 @@ paths: $ref: '#/responses/ServerError' tags: - shipment - - shipment_address_updates description: >- This endpoint is used to approve a address update request. Office remarks are required. Approving the address update will update the @@ -3637,6 +3636,10 @@ paths: type: string description: | Used to illustrate which user is assigned to this payment request. + - in: query + name: activeRole + type: string + description: user's actively logged in role responses: '200': description: Successfully returned all moves matching the criteria @@ -3892,6 +3895,10 @@ paths: name: counselingOffice type: string description: filters using a counselingOffice name of the move + - in: query + name: activeRole + type: string + description: user's actively logged in role responses: '200': description: Successfully returned all moves matching the criteria @@ -4008,6 +4015,10 @@ paths: current user. Requires the HQ role or a secondary transportation office assignment. The parameter is ignored if the requesting user does not have the necessary role or assignment. + - in: query + name: activeRole + type: string + description: user's actively logged in role responses: '200': description: Successfully returned all moves matching the criteria @@ -4332,6 +4343,45 @@ paths: $ref: '#/responses/NotFound' '500': $ref: '#/responses/ServerError' + /transportation_offices/{dutyLocationId}/counseling_offices/{serviceMemberId}: + get: + summary: Returns the counseling locations in the GBLOC matching the duty location + description: >- + Returns the counseling locations matching the GBLOC from the selected + duty location + operationId: showCounselingOffices + tags: + - transportationOffice + parameters: + - in: path + name: dutyLocationId + format: uuid + type: string + required: true + description: UUID of the duty location + - in: path + name: serviceMemberId + format: uuid + type: string + required: true + description: >- + UUID of the service member, some counseling offices are branch + specific + produces: + - application/json + responses: + '200': + description: Successfully retrieved counseling offices + schema: + $ref: '#/definitions/CounselingOffices' + '400': + $ref: '#/responses/InvalidRequest' + '403': + $ref: '#/responses/PermissionDenied' + '404': + $ref: '#/responses/NotFound' + '500': + description: internal server error /uploads: post: summary: Create a new upload @@ -5245,6 +5295,11 @@ definitions: gunSafe: type: boolean example: false + weightRestriction: + type: integer + example: 1500 + x-formatting: weight + x-nullable: true nonTemporaryStorage: example: false type: boolean @@ -5480,6 +5535,15 @@ definitions: The transportation office that will handle reviewing PPM Closeout documentation for Army and Air Force service members x-nullable: true + counselingOffice: + $ref: '#/definitions/TransportationOffice' + counselingOfficeId: + type: string + format: uuid + description: >- + The transportation office that will handle services counseling for + this move + x-nullable: true approvalsRequestedAt: type: string format: date-time @@ -5911,6 +5975,11 @@ definitions: type: string format: uuid example: c56a4180-65aa-42ec-a945-5fd21dec0538 + counselingOfficeId: + type: string + format: uuid + example: cf1addea-a4f9-4173-8506-2bb82a064cb7 + x-nullable: true ordersNumber: type: string title: Orders Number @@ -6176,6 +6245,13 @@ definitions: example: 500 type: integer x-nullable: true + weightRestriction: + example: 1500 + type: integer + x-nullable: true + description: >- + Indicates the weight restriction for the move to a particular + location. UpdateBillableWeightPayload: type: object properties: @@ -6279,6 +6355,11 @@ definitions: example: 500 type: integer x-nullable: true + weightRestriction: + example: 1500 + type: integer + x-nullable: true + description: Indicates the weight restriction for a move to a particular location. MoveTaskOrder: description: The Move (MoveTaskOrder) properties: @@ -7440,6 +7521,7 @@ definitions: type: boolean workload: type: integer + x-omitempty: false BulkAssignmentData: type: object properties: @@ -8125,6 +8207,10 @@ definitions: type: string format: date-time readOnly: true + ReServiceItems: + type: array + items: + $ref: '#/definitions/ReServiceItem' TransportationOffices: type: array items: @@ -8133,14 +8219,27 @@ definitions: type: array items: $ref: '#/definitions/VLocation' - ReServiceItems: - type: array - items: - $ref: '#/definitions/ReServiceItem' GBLOCs: type: array items: type: string + CounselingOffices: + type: array + items: + $ref: '#/definitions/CounselingOffice' + CounselingOffice: + type: object + properties: + id: + type: string + format: uuid + example: c56a4180-65aa-42ec-a945-5fd21dec0538 + name: + type: string + example: Fort Bragg North Station + required: + - id + - name MovePayload: type: object properties: @@ -11041,6 +11140,24 @@ definitions: format: cents x-nullable: true x-omitempty: false + intlPackPrice: + description: The full price of international packing (IHPK) + type: integer + format: cents + x-nullable: true + x-omitempty: false + intlUnpackPrice: + description: The full price of international unpacking (IHUPK) + type: integer + format: cents + x-nullable: true + x-omitempty: false + intlLinehaulPrice: + description: The full price of international shipping and linehaul (ISLH) + type: integer + format: cents + x-nullable: true + x-omitempty: false SITReimbursement: description: >- The estimated amount that the government will pay the service member diff --git a/swagger/prime.yaml b/swagger/prime.yaml index 5fe5ee87f0c..19cecc3266e 100644 --- a/swagger/prime.yaml +++ b/swagger/prime.yaml @@ -1025,16 +1025,17 @@ paths: [createMTOServiceItem](#mtoServiceItem/createMTOServiceItem)) endpoint. - * Resubmitting rejected SIT service items: This endpoint will handle the - logic of changing the status of rejected SIT service items from + * Resubmitting rejected SIT/Accessorial service items: This endpoint + will handle the logic of changing the status of rejected SIT/Accessorial + service items from REJECTED to SUBMITTED. Please provide the `requestedApprovalsRequestedStatus: true` when resubmitting as this will give attention to the TOO to - review the resubmitted SIT service item. Another note, `updateReason` - must have a different value than the current `reason` value on the - service item. + review the resubmitted SIT/Accessorial service item. Another note, + `updateReason` must have a different value than the current `reason` + value on the service item. If this value is not updated, then an error will be sent back. @@ -1059,8 +1060,16 @@ paths: - DOSFSC - At a MINIMUM, the payload for resubmitting a rejected SIT service item - must look like this: + The following Accessorial service items can be resubmitted following a + rejection: + + - IOSHUT + + - IDSHUT + + + At a MINIMUM, the payload for resubmitting a rejected SIT/Accessorial + service item must look like this: ```json @@ -1601,6 +1610,36 @@ paths: ``` --- + + + International Basic Service Items & Accepted Payment Request Parameters: + + --- + + **IOSHUT - International origin shuttle service** + + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` + + + **IDSHUT - International destination shuttle service** + + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` + + --- operationId: createPaymentRequest tags: - paymentRequest @@ -2499,6 +2538,60 @@ definitions: portCode: description: A unique code for a Port type: string + MTOServiceItemInternationalShuttle: + description: Describes an international shuttle service item. + allOf: + - $ref: '#/definitions/MTOServiceItem' + - type: object + properties: + reServiceCode: + type: string + description: > + A unique code for the service item. Indicates if shuttling is + requested for the international shipment origin (`IOSHUT`) or + destination (`IDSHUT`). + enum: + - IOSHUT + - IDSHUT + reason: + type: string + example: Storage items need to be picked up. + 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. + estimatedWeight: + type: integer + example: 4200 + description: >- + An estimate of how much weight from a shipment will be included in + the shuttling service. + x-nullable: true + x-omitempty: false + actualWeight: + type: integer + example: 4000 + description: >- + A record of the actual weight that was shuttled. Provided by the + movers, based on weight tickets. + x-nullable: true + x-omitempty: false + requestApprovalsRequestedStatus: + description: Indicates if "Approvals Requested" status is being requested. + type: boolean + x-nullable: true + market: + type: string + enum: + - CONUS + - OCONUS + example: CONUS + description: >- + To identify whether the service was provided within (CONUS) or + (OCONUS) + required: + - reason + - reServiceCode MTOShipment: type: object properties: @@ -2593,6 +2686,8 @@ definitions: * DOSHUT - UpdateMTOServiceItemShuttle * PODFSC - UpdateMTOServiceItemInternationalPortFSC * POEFSC - UpdateMTOServiceItemInternationalPortFSC + * IDSHUT - UpdateMTOServiceItemInternationalShuttle + * IOSHUT - UpdateMTOServiceItemInternationalShuttle The documentation will then update with the supported fields. type: string @@ -2600,6 +2695,7 @@ definitions: - UpdateMTOServiceItemSIT - UpdateMTOServiceItemShuttle - UpdateMTOServiceItemInternationalPortFSC + - UpdateMTOServiceItemInternationalShuttle UpdateMTOServiceItemShuttle: description: > Subtype used to provide the estimated weight and actual weight for @@ -2631,6 +2727,41 @@ definitions: enum: - DDSHUT - DOSHUT + UpdateMTOServiceItemInternationalShuttle: + description: > + Subtype used to provide the estimated weight and actual weight for + shuttle. This is not creating a new service item but rather updating an + existing service item. + allOf: + - $ref: '#/definitions/UpdateMTOServiceItem' + - type: object + properties: + actualWeight: + type: integer + example: 4000 + description: >- + Provided by the movers, based on weight tickets. Relevant for + shuttling (IDSHUT & IOSHUT) service items. + x-nullable: true + x-omitempty: false + estimatedWeight: + type: integer + example: 4200 + description: >- + An estimate of how much weight from a shipment will be included in + a shuttling (IDSHUT & IOSHUT) service item. + x-nullable: true + x-omitempty: false + requestApprovalsRequestedStatus: + description: Indicates if "Approvals Requested" status is being requested. + type: boolean + x-nullable: true + reServiceCode: + type: string + description: Service code allowed for this model type. + enum: + - IDSHUT + - IOSHUT UpdateMTOServiceItemSIT: description: > Subtype used to provide the departure date for origin or destination SIT. @@ -3295,6 +3426,11 @@ definitions: totalDependents: example: 2 type: integer + weightRestriction: + example: 1500 + type: integer + x-formatting: weight + x-nullable: true eTag: type: string readOnly: true @@ -3680,6 +3816,7 @@ definitions: * DOFSIT, DOASIT - MTOServiceItemOriginSIT * DDFSIT, DDASIT - MTOServiceItemDestSIT * DOSHUT, DDSHUT - MTOServiceItemShuttle + * IOSHUT, IDSHUT - MTOServiceItemInternationalShuttle * DCRT, DUCRT - MTOServiceItemDomesticCrating * ICRT, IUCRT - MTOServiceItemInternationalCrating * PODFSC, POEFSC - MTOSerivceItemInternationalFuelSurcharge @@ -3691,6 +3828,7 @@ definitions: - MTOServiceItemOriginSIT - MTOServiceItemDestSIT - MTOServiceItemShuttle + - MTOServiceItemInternationalShuttle - MTOServiceItemDomesticCrating - MTOServiceItemInternationalCrating - MTOSerivceItemInternationalFuelSurcharge diff --git a/swagger/prime_v2.yaml b/swagger/prime_v2.yaml index 780dd5c814c..d5d7a41b687 100644 --- a/swagger/prime_v2.yaml +++ b/swagger/prime_v2.yaml @@ -686,6 +686,60 @@ definitions: required: - reason - reServiceCode + MTOServiceItemInternationalShuttle: + description: Describes an international shuttle service item. + allOf: + - $ref: '#/definitions/MTOServiceItem' + - type: object + properties: + reServiceCode: + type: string + description: > + A unique code for the service item. Indicates if shuttling is + requested for the international shipment origin (`IOSHUT`) or + destination (`IDSHUT`). + enum: + - IOSHUT + - IDSHUT + reason: + type: string + example: Storage items need to be picked up. + 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. + estimatedWeight: + type: integer + example: 4200 + description: >- + An estimate of how much weight from a shipment will be included in + the shuttling service. + x-nullable: true + x-omitempty: false + actualWeight: + type: integer + example: 4000 + description: >- + A record of the actual weight that was shuttled. Provided by the + movers, based on weight tickets. + x-nullable: true + x-omitempty: false + requestApprovalsRequestedStatus: + description: Indicates if "Approvals Requested" status is being requested. + type: boolean + x-nullable: true + market: + type: string + enum: + - CONUS + - OCONUS + example: CONUS + description: >- + To identify whether the service was provided within (CONUS) or + (OCONUS) + required: + - reason + - reServiceCode CreateMTOShipment: type: object properties: @@ -968,12 +1022,15 @@ definitions: * DOFSIT - UpdateMTOServiceItemSIT * DDSHUT - UpdateMTOServiceItemShuttle * DOSHUT - UpdateMTOServiceItemShuttle + * IDSHUT - UpdateMTOServiceItemInternationalShuttle + * IOSHUT - UpdateMTOServiceItemInternationalShuttle The documentation will then update with the supported fields. type: string enum: - UpdateMTOServiceItemSIT - UpdateMTOServiceItemShuttle + - UpdateMTOServiceItemInternationalShuttle UpdateMTOServiceItem: description: >- UpdateMTOServiceItem describes a base type of a service item. Polymorphic @@ -1021,6 +1078,41 @@ definitions: enum: - DDSHUT - DOSHUT + UpdateMTOServiceItemInternationalShuttle: + description: > + Subtype used to provide the estimated weight and actual weight for + shuttle. This is not creating a new service item but rather updating an + existing service item. + allOf: + - $ref: '#/definitions/UpdateMTOServiceItem' + - type: object + properties: + actualWeight: + type: integer + example: 4000 + description: >- + Provided by the movers, based on weight tickets. Relevant for + shuttling (IDSHUT & IOSHUT) service items. + x-nullable: true + x-omitempty: false + estimatedWeight: + type: integer + example: 4200 + description: >- + An estimate of how much weight from a shipment will be included in + a shuttling (IDSHUT & IOSHUT) service item. + x-nullable: true + x-omitempty: false + requestApprovalsRequestedStatus: + description: Indicates if "Approvals Requested" status is being requested. + type: boolean + x-nullable: true + reServiceCode: + type: string + description: Service code allowed for this model type. + enum: + - IDSHUT + - IOSHUT UpdatePPMShipment: description: The PPM specific fields of the shipment with values being changed type: object @@ -1687,6 +1779,11 @@ definitions: totalDependents: example: 2 type: integer + weightRestriction: + example: 1500 + type: integer + x-formatting: weight + x-nullable: true eTag: type: string readOnly: true @@ -2084,6 +2181,7 @@ definitions: * DOFSIT, DOASIT - MTOServiceItemOriginSIT * DDFSIT, DDASIT - MTOServiceItemDestSIT * DOSHUT, DDSHUT - MTOServiceItemShuttle + * IOSHUT, IDSHUT - MTOServiceItemInternationalShuttle * DCRT, DUCRT - MTOServiceItemDomesticCrating * ICRT, IUCRT - MTOServiceItemInternationalCrating * PODFSC, POEFSC - MTOSerivceItemInternationalFuelSurcharge @@ -2095,6 +2193,7 @@ definitions: - MTOServiceItemOriginSIT - MTOServiceItemDestSIT - MTOServiceItemShuttle + - MTOServiceItemInternationalShuttle - MTOServiceItemDomesticCrating - MTOServiceItemInternationalCrating - MTOSerivceItemInternationalFuelSurcharge diff --git a/swagger/prime_v3.yaml b/swagger/prime_v3.yaml index 72a0fe86655..cd2473feb5f 100644 --- a/swagger/prime_v3.yaml +++ b/swagger/prime_v3.yaml @@ -663,6 +663,60 @@ definitions: required: - reason - reServiceCode + MTOServiceItemInternationalShuttle: + description: Describes an international shuttle service item. + allOf: + - $ref: '#/definitions/MTOServiceItem' + - type: object + properties: + reServiceCode: + type: string + description: > + A unique code for the service item. Indicates if shuttling is + requested for the international shipment origin (`IOSHUT`) or + destination (`IDSHUT`). + enum: + - IOSHUT + - IDSHUT + reason: + type: string + example: Storage items need to be picked up. + 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. + estimatedWeight: + type: integer + example: 4200 + description: >- + An estimate of how much weight from a shipment will be included in + the shuttling service. + x-nullable: true + x-omitempty: false + actualWeight: + type: integer + example: 4000 + description: >- + A record of the actual weight that was shuttled. Provided by the + movers, based on weight tickets. + x-nullable: true + x-omitempty: false + requestApprovalsRequestedStatus: + description: Indicates if "Approvals Requested" status is being requested. + type: boolean + x-nullable: true + market: + type: string + enum: + - CONUS + - OCONUS + example: CONUS + description: >- + To identify whether the service was provided within (CONUS) or + (OCONUS) + required: + - reason + - reServiceCode MTOServiceItemInternationalFuelSurcharge: description: >- Describes a international Port of Embarkation/Debarkation fuel surcharge @@ -1006,12 +1060,15 @@ definitions: * DOFSIT - UpdateMTOServiceItemSIT * DDSHUT - UpdateMTOServiceItemShuttle * DOSHUT - UpdateMTOServiceItemShuttle + * IDSHUT - UpdateMTOServiceItemInternationalShuttle + * IOSHUT - UpdateMTOServiceItemInternationalShuttle The documentation will then update with the supported fields. type: string enum: - UpdateMTOServiceItemSIT - UpdateMTOServiceItemShuttle + - UpdateMTOServiceItemInternationalShuttle UpdateMTOServiceItem: description: >- UpdateMTOServiceItem describes a base type of a service item. Polymorphic @@ -1059,6 +1116,41 @@ definitions: enum: - DDSHUT - DOSHUT + UpdateMTOServiceItemInternationalShuttle: + description: > + Subtype used to provide the estimated weight and actual weight for + shuttle. This is not creating a new service item but rather updating an + existing service item. + allOf: + - $ref: '#/definitions/UpdateMTOServiceItem' + - type: object + properties: + actualWeight: + type: integer + example: 4000 + description: >- + Provided by the movers, based on weight tickets. Relevant for + shuttling (IDSHUT & IOSHUT) service items. + x-nullable: true + x-omitempty: false + estimatedWeight: + type: integer + example: 4200 + description: >- + An estimate of how much weight from a shipment will be included in + a shuttling (IDSHUT & IOSHUT) service item. + x-nullable: true + x-omitempty: false + requestApprovalsRequestedStatus: + description: Indicates if "Approvals Requested" status is being requested. + type: boolean + x-nullable: true + reServiceCode: + type: string + description: Service code allowed for this model type. + enum: + - IDSHUT + - IOSHUT UpdatePPMShipment: description: The PPM specific fields of the shipment with values being changed type: object @@ -1794,6 +1886,11 @@ definitions: totalDependents: example: 2 type: integer + weightRestriction: + example: 1500 + type: integer + x-formatting: weight + x-nullable: true eTag: type: string readOnly: true @@ -2191,6 +2288,7 @@ definitions: * DOFSIT, DOASIT - MTOServiceItemOriginSIT * DDFSIT, DDASIT - MTOServiceItemDestSIT * DOSHUT, DDSHUT - MTOServiceItemShuttle + * IOSHUT, IDSHUT - MTOServiceItemInternationalShuttle * DCRT, DUCRT - MTOServiceItemDomesticCrating * ICRT, IUCRT - MTOServiceItemInternationalCrating * PODFSC, POEFSC - MTOSerivceItemInternationalFuelSurcharge @@ -2202,6 +2300,7 @@ definitions: - MTOServiceItemOriginSIT - MTOServiceItemDestSIT - MTOServiceItemShuttle + - MTOServiceItemInternationalShuttle - MTOServiceItemDomesticCrating - MTOServiceItemInternationalCrating - MTOSerivceItemInternationalFuelSurcharge diff --git a/swagger/support.yaml b/swagger/support.yaml index 26b02e68d32..362742d57a9 100644 --- a/swagger/support.yaml +++ b/swagger/support.yaml @@ -953,6 +953,10 @@ definitions: gunSafe: type: boolean example: false + weightRestriction: + example: 1500 + type: integer + x-nullable: true nonTemporaryStorage: example: false type: boolean @@ -1405,6 +1409,7 @@ definitions: - MTOServiceItemOriginSIT - MTOServiceItemDestSIT - MTOServiceItemShuttle + - MTOServiceItemInternationalShuttle - MTOServiceItemDomesticCrating - MTOServiceItemInternationalCrating MTOServiceItemOriginSIT: diff --git a/yarn.lock b/yarn.lock index 13023153627..258ff336b38 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2947,11 +2947,6 @@ resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.1.3.tgz#6801033be7ff87a6b7cadaf5b337c9f366a3c4b0" integrity sha512-WiBSI6JBIhC6LRIsB2Kwh8DsGTlbBU+mLRxJmAe3LjHTdkDpwIbEOZgoXBbZilk/vlfjK8i6nKRAvIRn1XaIMw== -"@scarf/scarf@=1.4.0": - version "1.4.0" - resolved "https://registry.yarnpkg.com/@scarf/scarf/-/scarf-1.4.0.tgz#3bbb984085dbd6d982494538b523be1ce6562972" - integrity sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ== - "@sinclair/typebox@^0.23.3": version "0.23.5" resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.23.5.tgz#93f7b9f4e3285a7a9ade7557d9a8d36809cbc47d" @@ -3544,15 +3539,15 @@ ts-dedent "^2.0.0" util-deprecate "^1.0.2" -"@storybook/builder-manager@7.6.7": - version "7.6.7" - resolved "https://registry.yarnpkg.com/@storybook/builder-manager/-/builder-manager-7.6.7.tgz#03ac44dfd4127d147d69af495c1e9aba2d9eda9c" - integrity sha512-6HYpj6+g/qbDMvImVz/G/aANbkhppyBa1ozfHxLK7tRD79YvozCWmj2Z9umRekPv9VIeMxnI5EEzJXOsoMX5DQ== +"@storybook/builder-manager@7.6.20": + version "7.6.20" + resolved "https://registry.yarnpkg.com/@storybook/builder-manager/-/builder-manager-7.6.20.tgz#d550a3f209012e4e383e61320ea756cddfdb416e" + integrity sha512-e2GzpjLaw6CM/XSmc4qJRzBF8GOoOyotyu3JrSPTYOt4RD8kjUsK4QlismQM1DQRu8i39aIexxmRbiJyD74xzQ== dependencies: "@fal-works/esbuild-plugin-global-externals" "^2.1.2" - "@storybook/core-common" "7.6.7" - "@storybook/manager" "7.6.7" - "@storybook/node-logger" "7.6.7" + "@storybook/core-common" "7.6.20" + "@storybook/manager" "7.6.20" + "@storybook/node-logger" "7.6.20" "@types/ejs" "^3.1.1" "@types/find-cache-dir" "^3.2.1" "@yarnpkg/esbuild-plugin-pnp" "^3.0.0-rc.10" @@ -3646,13 +3641,13 @@ telejson "^7.0.3" tiny-invariant "^1.3.1" -"@storybook/channels@7.6.7": - version "7.6.7" - resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-7.6.7.tgz#23a0c59ebfdfbb83e4a49d8d3fafdd25a9a67140" - integrity sha512-u1hURhfQHHtZyRIDUENRCp+CRRm7IQfcjQaoWI06XCevQPuhVEtFUfXHjG+J74aA/JuuTLFUtqwNm1zGqbXTAQ== +"@storybook/channels@7.6.20": + version "7.6.20" + resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-7.6.20.tgz#33d8292b1b16d7f504bf751c57a792477d1c3a9e" + integrity sha512-4hkgPSH6bJclB2OvLnkZOGZW1WptJs09mhQ6j6qLjgBZzL/ZdD6priWSd7iXrmPiN5TzUobkG4P4Dp7FjkiO7A== dependencies: - "@storybook/client-logger" "7.6.7" - "@storybook/core-events" "7.6.7" + "@storybook/client-logger" "7.6.20" + "@storybook/core-events" "7.6.20" "@storybook/global" "^5.0.0" qs "^6.10.0" telejson "^7.2.0" @@ -3681,23 +3676,23 @@ telejson "^7.2.0" tiny-invariant "^1.3.1" -"@storybook/cli@7.6.7": - version "7.6.7" - resolved "https://registry.yarnpkg.com/@storybook/cli/-/cli-7.6.7.tgz#51f12a6cb2cdd10b78f255caca61ef12ffccebc6" - integrity sha512-DwDWzkifBH17ry+n+d+u52Sv69dZQ+04ETJdDDzghcyAcKnFzrRNukj4tJ21cm+ZAU/r0fKR9d4Qpbogca9fAg== +"@storybook/cli@7.6.20": + version "7.6.20" + resolved "https://registry.yarnpkg.com/@storybook/cli/-/cli-7.6.20.tgz#498625db5f2447e8e1ad34827a7803c5940527f0" + integrity sha512-ZlP+BJyqg7HlnXf7ypjG2CKMI/KVOn03jFIiClItE/jQfgR6kRFgtjRU7uajh427HHfjv9DRiur8nBzuO7vapA== dependencies: "@babel/core" "^7.23.2" "@babel/preset-env" "^7.23.2" "@babel/types" "^7.23.0" "@ndelangen/get-tarball" "^3.0.7" - "@storybook/codemod" "7.6.7" - "@storybook/core-common" "7.6.7" - "@storybook/core-events" "7.6.7" - "@storybook/core-server" "7.6.7" - "@storybook/csf-tools" "7.6.7" - "@storybook/node-logger" "7.6.7" - "@storybook/telemetry" "7.6.7" - "@storybook/types" "7.6.7" + "@storybook/codemod" "7.6.20" + "@storybook/core-common" "7.6.20" + "@storybook/core-events" "7.6.20" + "@storybook/core-server" "7.6.20" + "@storybook/csf-tools" "7.6.20" + "@storybook/node-logger" "7.6.20" + "@storybook/telemetry" "7.6.20" + "@storybook/types" "7.6.20" "@types/semver" "^7.3.4" "@yarnpkg/fslib" "2.10.3" "@yarnpkg/libzip" "2.3.0" @@ -3722,7 +3717,6 @@ puppeteer-core "^2.1.1" read-pkg-up "^7.0.1" semver "^7.3.7" - simple-update-notifier "^2.0.0" strip-json-comments "^3.0.1" tempy "^1.0.1" ts-dedent "^2.0.0" @@ -3750,10 +3744,10 @@ dependencies: "@storybook/global" "^5.0.0" -"@storybook/client-logger@7.6.7": - version "7.6.7" - resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-7.6.7.tgz#a2cb75a668c09bf091c1925c3403e3f2f8b1e4e1" - integrity sha512-A16zpWgsa0gSdXMR9P3bWVdC9u/1B1oG4H7Z1+JhNzgnL3CdyOYO0qFSiAtNBso4nOjIAJVb6/AoBzdRhmSVQg== +"@storybook/client-logger@7.6.20": + version "7.6.20" + resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-7.6.20.tgz#1d6e93443091cccd50e269371aa786172d0c4659" + integrity sha512-NwG0VIJQCmKrSaN5GBDFyQgTAHLNishUPLW1NrzqTDNAhfZUoef64rPQlinbopa0H4OXmlB+QxbQIb3ubeXmSQ== dependencies: "@storybook/global" "^5.0.0" @@ -3771,18 +3765,18 @@ dependencies: "@storybook/global" "^5.0.0" -"@storybook/codemod@7.6.7": - version "7.6.7" - resolved "https://registry.yarnpkg.com/@storybook/codemod/-/codemod-7.6.7.tgz#49ee0e824dcb0ae8f1d887266e82ee694d149b6f" - integrity sha512-an2pD5OHqO7CE8Wb7JxjrDnpQgeoxB22MyOs8PPJ9Rvclhpjg+Ku9RogoObYm//zR4g406l7Ec8mTltUkVCEOA== +"@storybook/codemod@7.6.20": + version "7.6.20" + resolved "https://registry.yarnpkg.com/@storybook/codemod/-/codemod-7.6.20.tgz#0aa7e0c1aacc605c7691b4b06baef0a9abefe114" + integrity sha512-8vmSsksO4XukNw0TmqylPmk7PxnfNfE21YsxFa7mnEBmEKQcZCQsNil4ZgWfG0IzdhTfhglAN4r++Ew0WE+PYA== dependencies: "@babel/core" "^7.23.2" "@babel/preset-env" "^7.23.2" "@babel/types" "^7.23.0" "@storybook/csf" "^0.1.2" - "@storybook/csf-tools" "7.6.7" - "@storybook/node-logger" "7.6.7" - "@storybook/types" "7.6.7" + "@storybook/csf-tools" "7.6.20" + "@storybook/node-logger" "7.6.20" + "@storybook/types" "7.6.20" "@types/cross-spawn" "^6.0.2" cross-spawn "^7.0.3" globby "^11.0.2" @@ -3842,14 +3836,14 @@ resolve-from "^5.0.0" ts-dedent "^2.0.0" -"@storybook/core-common@7.6.7": - version "7.6.7" - resolved "https://registry.yarnpkg.com/@storybook/core-common/-/core-common-7.6.7.tgz#69801d7a70b4ed6dab5dec589f612814628d3807" - integrity sha512-F1fJnauVSPQtAlpicbN/O4XW38Ai8kf/IoU0Hgm9gEwurIk6MF5hiVLsaTI/5GUbrepMl9d9J+iIL4lHAT8IyA== +"@storybook/core-common@7.6.20": + version "7.6.20" + resolved "https://registry.yarnpkg.com/@storybook/core-common/-/core-common-7.6.20.tgz#3a2a3ae570bd13dc34726178c0eb36cf6a64e2a4" + integrity sha512-8H1zPWPjcmeD4HbDm4FDD0WLsfAKGVr566IZ4hG+h3iWVW57II9JW9MLBtiR2LPSd8u7o0kw64lwRGmtCO1qAw== dependencies: - "@storybook/core-events" "7.6.7" - "@storybook/node-logger" "7.6.7" - "@storybook/types" "7.6.7" + "@storybook/core-events" "7.6.20" + "@storybook/node-logger" "7.6.20" + "@storybook/types" "7.6.20" "@types/find-cache-dir" "^3.2.1" "@types/node" "^18.0.0" "@types/node-fetch" "^2.6.4" @@ -3881,10 +3875,10 @@ resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-7.2.0.tgz#b53db12c966b1036d2fb22425e2ccbd59118833b" integrity sha512-Y1o8vGBnbZ/bYsukPiK33CHURSob3tywg8WRtAuwWnDaZiM9IXgkEHbOK1zfkPTnz2gSXEX19KlpTmMxm0W//w== -"@storybook/core-events@7.6.7": - version "7.6.7" - resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-7.6.7.tgz#ee8823090cc4e30fddebe72be29738e4b2e66b11" - integrity sha512-KZ5d03c47pnr5/kY26pJtWq7WpmCPXLbgyjJZDSc+TTY153BdZksvlBXRHtqM1yj2UM6QsSyIuiJaADJNAbP2w== +"@storybook/core-events@7.6.20": + version "7.6.20" + resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-7.6.20.tgz#6648d661d1c96841a4c2a710a35759b01b6a06a1" + integrity sha512-tlVDuVbDiNkvPDFAu+0ou3xBBYbx9zUURQz4G9fAq0ScgBOs/bpzcRrFb4mLpemUViBAd47tfZKdH4MAX45KVQ== dependencies: ts-dedent "^2.0.0" @@ -3903,26 +3897,26 @@ "@storybook/csf" "^0.1.7" ts-dedent "^2.0.0" -"@storybook/core-server@7.6.7": - version "7.6.7" - resolved "https://registry.yarnpkg.com/@storybook/core-server/-/core-server-7.6.7.tgz#6d1c3b0d71296bf00438ba5112156f96fc78b2cb" - integrity sha512-elKRv/DNahNNkGcQY/FdOBrLPmZF0T0fwmAmbc4qqeAisjl+to9TO77zdo2ieaEHKyRwE3B3dOB4EXomdF4N/g== +"@storybook/core-server@7.6.20": + version "7.6.20" + resolved "https://registry.yarnpkg.com/@storybook/core-server/-/core-server-7.6.20.tgz#fa143fbcad64fb7b0f0dc6d555d083c506a44ab4" + integrity sha512-qC5BdbqqwMLTdCwMKZ1Hbc3+3AaxHYWLiJaXL9e8s8nJw89xV8c8l30QpbJOGvcDmsgY6UTtXYaJ96OsTr7MrA== dependencies: "@aw-web-design/x-default-browser" "1.4.126" "@discoveryjs/json-ext" "^0.5.3" - "@storybook/builder-manager" "7.6.7" - "@storybook/channels" "7.6.7" - "@storybook/core-common" "7.6.7" - "@storybook/core-events" "7.6.7" + "@storybook/builder-manager" "7.6.20" + "@storybook/channels" "7.6.20" + "@storybook/core-common" "7.6.20" + "@storybook/core-events" "7.6.20" "@storybook/csf" "^0.1.2" - "@storybook/csf-tools" "7.6.7" + "@storybook/csf-tools" "7.6.20" "@storybook/docs-mdx" "^0.1.0" "@storybook/global" "^5.0.0" - "@storybook/manager" "7.6.7" - "@storybook/node-logger" "7.6.7" - "@storybook/preview-api" "7.6.7" - "@storybook/telemetry" "7.6.7" - "@storybook/types" "7.6.7" + "@storybook/manager" "7.6.20" + "@storybook/node-logger" "7.6.20" + "@storybook/preview-api" "7.6.20" + "@storybook/telemetry" "7.6.20" + "@storybook/types" "7.6.20" "@types/detect-port" "^1.3.0" "@types/node" "^18.0.0" "@types/pretty-hrtime" "^1.0.0" @@ -3935,7 +3929,6 @@ express "^4.17.3" fs-extra "^11.1.0" globby "^11.0.2" - ip "^2.0.0" lodash "^4.17.21" open "^8.4.0" pretty-hrtime "^1.0.3" @@ -3985,17 +3978,17 @@ recast "^0.23.1" ts-dedent "^2.0.0" -"@storybook/csf-tools@7.6.7": - version "7.6.7" - resolved "https://registry.yarnpkg.com/@storybook/csf-tools/-/csf-tools-7.6.7.tgz#1707bc5d6289ec79aeab472877aadda76def5015" - integrity sha512-hyRbUGa2Uxvz3U09BjcOfMNf/5IYgRum1L6XszqK2O8tK9DGte1r6hArCIAcqiEmFMC40d0kalPzqu6WMNn7sg== +"@storybook/csf-tools@7.6.20": + version "7.6.20" + resolved "https://registry.yarnpkg.com/@storybook/csf-tools/-/csf-tools-7.6.20.tgz#fdd9fa9459720a627e83e31d3839721dbc655f22" + integrity sha512-rwcwzCsAYh/m/WYcxBiEtLpIW5OH1ingxNdF/rK9mtGWhJxXRDV8acPkFrF8rtFWIVKoOCXu5USJYmc3f2gdYQ== dependencies: "@babel/generator" "^7.23.0" "@babel/parser" "^7.23.0" "@babel/traverse" "^7.23.2" "@babel/types" "^7.23.0" "@storybook/csf" "^0.1.2" - "@storybook/types" "7.6.7" + "@storybook/types" "7.6.20" fs-extra "^11.1.0" recast "^0.23.1" ts-dedent "^2.0.0" @@ -4085,10 +4078,10 @@ telejson "^7.0.3" ts-dedent "^2.0.0" -"@storybook/manager@7.6.7": - version "7.6.7" - resolved "https://registry.yarnpkg.com/@storybook/manager/-/manager-7.6.7.tgz#993b4dd4800496ee4161c29e853efe7ee2c1a67f" - integrity sha512-ZCrkB2zEXogzdOcVzD242ZVm4tlHqrayotnI6iOn9uiun0Pgny0m2d7s9Zge6K2dTOO1vZiOHuA/Mr6nnIDjsA== +"@storybook/manager@7.6.20": + version "7.6.20" + resolved "https://registry.yarnpkg.com/@storybook/manager/-/manager-7.6.20.tgz#eb619fe8d33446e581a7b1c3050644c196364d39" + integrity sha512-0Cf6WN0t7yEG2DR29tN5j+i7H/TH5EfPppg9h9/KiQSoFHk+6KLoy2p5do94acFU+Ro4+zzxvdCGbcYGKuArpg== "@storybook/mdx2-csf@^1.0.0": version "1.0.0" @@ -4100,10 +4093,10 @@ resolved "https://registry.yarnpkg.com/@storybook/node-logger/-/node-logger-7.2.0.tgz#ea0d20db5c39817e2f606f12b28babe34349e4db" integrity sha512-rQTmw3oSaeenUCOxOa/8+ZtxDxNPhHIURv2Qpr/q5JkcDf13I6HimqVRxeccU+g3Bq/ueceOXMcAuoH4oewtUw== -"@storybook/node-logger@7.6.7": - version "7.6.7" - resolved "https://registry.yarnpkg.com/@storybook/node-logger/-/node-logger-7.6.7.tgz#35cee2b3e4d234b0b0735715d8856dc141d4a9b0" - integrity sha512-XLih8MxylkpZG9+8tgp8sPGc2tldlWF+DpuAkUv6J3Mc81mPyc3cQKQWZ7Hb+m1LpRGqKV4wyOQj1rC+leVMoQ== +"@storybook/node-logger@7.6.20": + version "7.6.20" + resolved "https://registry.yarnpkg.com/@storybook/node-logger/-/node-logger-7.6.20.tgz#c0ca90cf68cf31d84cdcf53c76cec22769407ece" + integrity sha512-l2i4qF1bscJkOplNffcRTsgQWYR7J51ewmizj5YrTM8BK6rslWT1RntgVJWB1RgPqvx6VsCz1gyP3yW1oKxvYw== "@storybook/postinstall@7.2.0": version "7.2.0" @@ -4185,17 +4178,17 @@ ts-dedent "^2.0.0" util-deprecate "^1.0.2" -"@storybook/preview-api@7.6.7": - version "7.6.7" - resolved "https://registry.yarnpkg.com/@storybook/preview-api/-/preview-api-7.6.7.tgz#4f963c95749157f0f9db7fc92c431214057f90e8" - integrity sha512-ja85ItrT6q2TeBQ6n0CNoRi1R6L8yF2kkis9hVeTQHpwLdZyHUTRqqR5WmhtLqqQXcofyasBPOeJV06wuOhgRQ== +"@storybook/preview-api@7.6.20": + version "7.6.20" + resolved "https://registry.yarnpkg.com/@storybook/preview-api/-/preview-api-7.6.20.tgz#688a435ee2cfe57eeb1e3053c18025a9e0a03bbb" + integrity sha512-3ic2m9LDZEPwZk02wIhNc3n3rNvbi7VDKn52hDXfAxnL5EYm7yDICAkaWcVaTfblru2zn0EDJt7ROpthscTW5w== dependencies: - "@storybook/channels" "7.6.7" - "@storybook/client-logger" "7.6.7" - "@storybook/core-events" "7.6.7" + "@storybook/channels" "7.6.20" + "@storybook/client-logger" "7.6.20" + "@storybook/core-events" "7.6.20" "@storybook/csf" "^0.1.2" "@storybook/global" "^5.0.0" - "@storybook/types" "7.6.7" + "@storybook/types" "7.6.20" "@types/qs" "^6.9.5" dequal "^2.0.2" lodash "^4.17.21" @@ -4302,14 +4295,14 @@ "@storybook/client-logger" "7.2.0" "@storybook/preview-api" "7.2.0" -"@storybook/telemetry@7.6.7": - version "7.6.7" - resolved "https://registry.yarnpkg.com/@storybook/telemetry/-/telemetry-7.6.7.tgz#46aed41588fca4f081a9127c473cde1ae8b954e0" - integrity sha512-NHGzC/LGLXpK4AFbVj8ln5ab86ZiiNFvORQMn3+LNGwUt3ZdsHBzExN+WPZdw7OPtfk4ubUY89FXH2GedhTALw== +"@storybook/telemetry@7.6.20": + version "7.6.20" + resolved "https://registry.yarnpkg.com/@storybook/telemetry/-/telemetry-7.6.20.tgz#5b3705eb5100b21070d76767dde1040ed5d9b35b" + integrity sha512-dmAOCWmOscYN6aMbhCMmszQjoycg7tUPRVy2kTaWg6qX10wtMrvEtBV29W4eMvqdsoRj5kcvoNbzRdYcWBUOHQ== dependencies: - "@storybook/client-logger" "7.6.7" - "@storybook/core-common" "7.6.7" - "@storybook/csf-tools" "7.6.7" + "@storybook/client-logger" "7.6.20" + "@storybook/core-common" "7.6.20" + "@storybook/csf-tools" "7.6.20" chalk "^4.1.0" detect-package-manager "^2.0.1" fetch-retry "^5.0.2" @@ -4355,12 +4348,12 @@ "@types/express" "^4.7.0" file-system-cache "2.3.0" -"@storybook/types@7.6.7": - version "7.6.7" - resolved "https://registry.yarnpkg.com/@storybook/types/-/types-7.6.7.tgz#f3935fbd3ba7f958e18106fd1626452a8961ef8c" - integrity sha512-VcGwrI4AkBENxkoAUJ+Z7SyMK73hpoY0TTtw2J7tc05/xdiXhkQTX15Qa12IBWIkoXCyNrtaU+q7KR8Tjzi+uw== +"@storybook/types@7.6.20": + version "7.6.20" + resolved "https://registry.yarnpkg.com/@storybook/types/-/types-7.6.20.tgz#b8d62b30914b35e6750b1f4937da532432f02890" + integrity sha512-GncdY3x0LpbhmUAAJwXYtJDUQEwfF175gsjH0/fxPkxPoV7Sef9TM41jQLJW/5+6TnZoCZP/+aJZTJtq3ni23Q== dependencies: - "@storybook/channels" "7.6.7" + "@storybook/channels" "7.6.20" "@types/babel__core" "^7.0.0" "@types/express" "^4.7.0" file-system-cache "2.3.0" @@ -10538,11 +10531,6 @@ invariant@^2.2.4: dependencies: loose-envify "^1.0.0" -ip@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.1.tgz#e8f3595d33a3ea66490204234b77636965307105" - integrity sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ== - ipaddr.js@1.9.1: version "1.9.1" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" @@ -15897,7 +15885,7 @@ semver-diff@^3.1.1: dependencies: semver "^6.3.0" -"semver@2 || 3 || 4 || 5", semver@^5.6.0, semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0, semver@^6.3.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.2, semver@^7.5.3, semver@^7.5.4: +"semver@2 || 3 || 4 || 5", semver@^5.6.0, semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0, semver@^6.3.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.2, semver@^7.5.4: version "7.5.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== @@ -16081,13 +16069,6 @@ simple-get@^4.0.0: once "^1.3.1" simple-concat "^1.0.0" -simple-update-notifier@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz#d70b92bdab7d6d90dfd73931195a30b6e3d7cebb" - integrity sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w== - dependencies: - semver "^7.5.3" - sirv@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/sirv/-/sirv-2.0.2.tgz#128b9a628d77568139cff85703ad5497c46a4760" @@ -16316,12 +16297,12 @@ store2@^2.14.2: resolved "https://registry.yarnpkg.com/store2/-/store2-2.14.2.tgz#56138d200f9fe5f582ad63bc2704dbc0e4a45068" integrity sha512-siT1RiqlfQnGqgT/YzXVUNsom9S0H1OX+dpdGN1xkyYATo4I6sep5NmsRD/40s3IIOvlCq6akxkqG82urIZW1w== -storybook@^7.6.7: - version "7.6.7" - resolved "https://registry.yarnpkg.com/storybook/-/storybook-7.6.7.tgz#90865997e806cbce278481fce1bb5be1984964fc" - integrity sha512-1Cd895dqYIT5MOUOCDlD73OTWoJubLq/sWC7AMzkMrLu76yD4Cu6f+wv1HDrRAheRaCaeT3yhYEhsMB6qHIcaA== +storybook@^7.6.20: + version "7.6.20" + resolved "https://registry.yarnpkg.com/storybook/-/storybook-7.6.20.tgz#6204ff0c28471536a1a64cb16d1c97872dd33f95" + integrity sha512-Wt04pPTO71pwmRmsgkyZhNo4Bvdb/1pBAMsIFb9nQLykEdzzpXjvingxFFvdOG4nIowzwgxD+CLlyRqVJqnATw== dependencies: - "@storybook/cli" "7.6.7" + "@storybook/cli" "7.6.20" stream-http@^3.2.0: version "3.2.0" @@ -16655,12 +16636,10 @@ swagger-client@^3.18.5: traverse "~0.6.6" url "~0.11.0" -swagger-ui-dist@^5.18.2: - version "5.18.2" - resolved "https://registry.yarnpkg.com/swagger-ui-dist/-/swagger-ui-dist-5.18.2.tgz#62013074374d272c04ed3030704b88db5aa8c0b7" - integrity sha512-J+y4mCw/zXh1FOj5wGJvnAajq6XgHOyywsa9yITmwxIlJbMqITq3gYRZHaeqLVH/eV/HOPphE6NjF+nbSNC5Zw== - dependencies: - "@scarf/scarf" "=1.4.0" +swagger-ui-dist@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/swagger-ui-dist/-/swagger-ui-dist-5.2.0.tgz#175e112b3aea756fdbbbb035d4cffef26ac579d1" + integrity sha512-rLvJBgualxNZcwKOmTFzy4zF1nHy+3S0pUDDR/ageDRZgi8aITSe7pVYiAy03xGQZtqEifjwEtHQE+eF14gveg== swc-loader@^0.2.3: version "0.2.3"