diff --git a/.github/workflows/analyze-bundle.yml b/.github/workflows/analyze-bundle.yml index cebc2e9c0a2..d1fc116e595 100644 --- a/.github/workflows/analyze-bundle.yml +++ b/.github/workflows/analyze-bundle.yml @@ -46,7 +46,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out branch - uses: actions/checkout@v3.5.3 + uses: actions/checkout@v3.6.0 - name: Set up node uses: actions/setup-node@v3 @@ -78,7 +78,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out base branch - uses: actions/checkout@v3.5.3 + uses: actions/checkout@v3.6.0 with: ref: ${{ github.base_ref }} @@ -115,7 +115,7 @@ jobs: pull-requests: write steps: - uses: actions/download-artifact@v3 - - uses: github/webpack-bundlesize-compare-action@v1.8.1 + - uses: github/webpack-bundlesize-compare-action@v1.8.2 with: github-token: ${{ secrets.GITHUB_TOKEN }} current-stats-json-path: ./head-stats/bundle-stats.json diff --git a/.github/workflows/front-end-linter.yml b/.github/workflows/front-end-linter.yml index b89dea49e8a..0ac4d90d692 100644 --- a/.github/workflows/front-end-linter.yml +++ b/.github/workflows/front-end-linter.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@v3.5.3 + uses: actions/checkout@v3.6.0 - name: Set up node uses: actions/setup-node@v3 diff --git a/.github/workflows/go-auto-approve.yml b/.github/workflows/go-auto-approve.yml index 5108c718723..547e2235b0b 100644 --- a/.github/workflows/go-auto-approve.yml +++ b/.github/workflows/go-auto-approve.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest steps: - name: checkout - uses: actions/checkout@v3.5.3 + uses: actions/checkout@v3.6.0 with: fetch-depth: 0 if: github.actor == 'dependabot[bot]' || github.actor == 'dependabot-preview[bot]' diff --git a/.github/workflows/happo-tests-main.yml b/.github/workflows/happo-tests-main.yml index 2553077ea62..2e40cbcbbed 100644 --- a/.github/workflows/happo-tests-main.yml +++ b/.github/workflows/happo-tests-main.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@v3.5.3 + uses: actions/checkout@v3.6.0 - name: Set up node uses: actions/setup-node@v3 diff --git a/.github/workflows/happo-tests.yml b/.github/workflows/happo-tests.yml index b74ee587872..12d8074a5d3 100644 --- a/.github/workflows/happo-tests.yml +++ b/.github/workflows/happo-tests.yml @@ -26,7 +26,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@v3.5.3 + uses: actions/checkout@v3.6.0 - name: Set up node uses: actions/setup-node@v3 diff --git a/Dockerfile.e2e b/Dockerfile.e2e index ec7b405c8e4..1e914001481 100644 --- a/Dockerfile.e2e +++ b/Dockerfile.e2e @@ -1,4 +1,4 @@ -FROM alpine:3.18.2 +FROM alpine:3.18.3 # hadolint ignore=DL3017 RUN apk upgrade --no-cache busybox diff --git a/Dockerfile.migrations b/Dockerfile.migrations index 2e2fb81bac1..940312bc0c8 100644 --- a/Dockerfile.migrations +++ b/Dockerfile.migrations @@ -1,4 +1,4 @@ -FROM alpine:3.18.2 +FROM alpine:3.18.3 # hadolint ignore=DL3017 RUN apk upgrade --no-cache busybox diff --git a/Dockerfile.migrations_local b/Dockerfile.migrations_local index 791dd40900c..26449170fd3 100644 --- a/Dockerfile.migrations_local +++ b/Dockerfile.migrations_local @@ -18,7 +18,7 @@ RUN rm -f bin/milmove && make bin/milmove # FINAL # ######### -FROM alpine:3.18.2 +FROM alpine:3.18.3 # hadolint ignore=DL3017 RUN apk upgrade --no-cache busybox diff --git a/Dockerfile.reviewapp b/Dockerfile.reviewapp index 602f6cc2d02..aed076fb59b 100644 --- a/Dockerfile.reviewapp +++ b/Dockerfile.reviewapp @@ -45,7 +45,7 @@ RUN set -x \ && make bin/generate-test-data # define migrations before client build since it doesn't need client -FROM alpine:3.18.2 as migrate +FROM alpine:3.18.3 as migrate COPY --from=server_builder /build/bin/rds-ca-2019-root.pem /bin/rds-ca-2019-root.pem COPY --from=server_builder /build/bin/milmove /bin/milmove diff --git a/Dockerfile.tools b/Dockerfile.tools index c737a4b5fd8..3b23897853f 100644 --- a/Dockerfile.tools +++ b/Dockerfile.tools @@ -1,4 +1,4 @@ -FROM alpine:3.18.2 +FROM alpine:3.18.3 # hadolint ignore=DL3017 RUN apk upgrade --no-cache busybox diff --git a/Dockerfile.tools_local b/Dockerfile.tools_local index 638fd327923..22a4ab6a459 100644 --- a/Dockerfile.tools_local +++ b/Dockerfile.tools_local @@ -18,7 +18,7 @@ RUN rm -f bin/prime-api-client && make bin/prime-api-client # FINAL # ######### -FROM alpine:3.18.2 +FROM alpine:3.18.3 # hadolint ignore=DL3017 RUN apk upgrade --no-cache busybox diff --git a/cmd/load-spreadsheet-data/load-updated-loa-trnsn-ids.py b/cmd/load-spreadsheet-data/load-updated-loa-trnsn-ids.py new file mode 100644 index 00000000000..de712353044 --- /dev/null +++ b/cmd/load-spreadsheet-data/load-updated-loa-trnsn-ids.py @@ -0,0 +1,63 @@ +import os, sys, pandas as pd +from datetime import datetime + +if len(sys.argv) < 2: + sys.exit("Input file required.") + + +# Generate to gitignored tmp directory to prevent committing secure data +current_dir = os.getcwd() +destination_directory = 'tmp/generated-secure-migrations' +filename = f"{datetime.now().strftime('%Y%m%d%H%M%S')}_update_loa_trnsn_ids.up.sql" +secure_migration_filename = ( + f'{current_dir}/{destination_directory}/{filename}' +) + +destination_path = f'{current_dir}/{destination_directory}' +if not os.path.exists(destination_path): + os.makedirs(destination_path) + +with open(secure_migration_filename, "w+") as f: + f.write('-- Update loa_trnsn_id column constraint\n') + f.write('ALTER TABLE lines_of_accounting ALTER COLUMN loa_trnsn_id TYPE varchar (3);\n') + + # Skip the first and last rows which are just "unclassified" + input_file = pd.read_excel(sys.argv[1], skiprows=1, skipfooter=1) + + # Missing values should be NULL + input_file = input_file.fillna('NULL') + + f.write('-- Update lines_of_accounting with updated loa_trnsn_id values, mapped by loa_sys_id\n') + f.write('UPDATE lines_of_accounting AS loas SET\n') + f.write('\tloa_trnsn_id = updated.loa_trnsn_id\n') + f.write('FROM (VALUES\n') + + has_written_at_least_one_value_to_file = False + for index, row in input_file.iterrows(): + loa_sys_id = row['LOA_SYS_ID'] + loa_hs_gds_cd = row['LOA_HS_GDS_CD'] + loa_trnsn_id = row['LOA_TRNSN_ID'] + + # Ignore rows where loa_sys_id is missing + if loa_sys_id == 'NULL': + continue + + # Ignore rows where the loa_hs_gds_cd does not have a value, as we did during the original import + if loa_hs_gds_cd == 'NULL': + continue + + # Add single quotes around non-null values, otherwise, just use NULL + loa_trnsn_id_write_value = loa_trnsn_id if loa_trnsn_id == 'NULL' else f"'{loa_trnsn_id}'" + + if has_written_at_least_one_value_to_file: + # prepend next line with a comma and a newline + f.write(',\n') + + f.write(f'\t({loa_sys_id}, {loa_trnsn_id_write_value})') + + # Now that at least one entry has been added to the file, we know to prepend the rest with `,\n` + has_written_at_least_one_value_to_file = True + + f.write('\n) AS updated(loa_sys_id, loa_trnsn_id)\n') + f.write('WHERE updated.loa_sys_id = loas.loa_sys_id;\n') +sys.exit() diff --git a/config/tls/api.exp.dp3.us.chain.der.p7b b/config/tls/api.exp.dp3.us.chain.der.p7b index 7c4fcc693cb..8e90d3ed5da 100644 Binary files a/config/tls/api.exp.dp3.us.chain.der.p7b and b/config/tls/api.exp.dp3.us.chain.der.p7b differ diff --git a/config/tls/api.exp.dp3.us.crt b/config/tls/api.exp.dp3.us.crt index a491d3a32dc..94410d76dba 100644 --- a/config/tls/api.exp.dp3.us.crt +++ b/config/tls/api.exp.dp3.us.crt @@ -1,33 +1,35 @@ -----BEGIN CERTIFICATE----- -MIIFrDCCBJSgAwIBAgIQY5O3DBUKQmj8fdDkgfjuPTANBgkqhkiG9w0BAQsFADCB -jzELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G -A1UEBxMHU2FsZm9yZDEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTcwNQYDVQQD -Ey5TZWN0aWdvIFJTQSBEb21haW4gVmFsaWRhdGlvbiBTZWN1cmUgU2VydmVyIENB -MB4XDTIzMDgxNjAwMDAwMFoXDTIzMDkyMzIzNTk1OVowGTEXMBUGA1UEAxMOYXBp -LmV4cC5kcDMudXMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDAGT7Y -MsWEQ4NAPjszIB9wqUwJn2/8ykCePtQYJsfeBu95nKRynPHgajHGi/BgB1rNMNWV -rxfsKbtwamps5PkaeLh3M18PMolFJ/rXW+VRKO0ELIUdvOAUqx88glfEhk7f0X9o -ZK5EtNlr+qIhecg4HuyIs36HbN9sBvvspZAIuND/8JM8OHnWPbBrkrjkZdQVwKPI -oTGaBBBgc2wRlL0mYUtNGTFw8JhqdtSQtJy0cjmgqnGSuG0iGAOE+YVSrD9Tam5/ -wHvUWN9fSBSaWk+EOK3bQGFxvJbZOB4YSK8iD24rQcKLRlu1TQFxoibKOellv7hU -baphs6DNJWXQlTZXAgMBAAGjggJ3MIICczAfBgNVHSMEGDAWgBSNjF7EVK2K4Xfp -m/mbBeG4AY1h4TAdBgNVHQ4EFgQUYQsWnsAmKfIe3wOZ+w8K1AXZOkIwDgYDVR0P -AQH/BAQDAgWgMAwGA1UdEwEB/wQCMAAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsG -AQUFBwMCMEkGA1UdIARCMEAwNAYLKwYBBAGyMQECAgcwJTAjBggrBgEFBQcCARYX -aHR0cHM6Ly9zZWN0aWdvLmNvbS9DUFMwCAYGZ4EMAQIBMIGEBggrBgEFBQcBAQR4 -MHYwTwYIKwYBBQUHMAKGQ2h0dHA6Ly9jcnQuc2VjdGlnby5jb20vU2VjdGlnb1JT -QURvbWFpblZhbGlkYXRpb25TZWN1cmVTZXJ2ZXJDQS5jcnQwIwYIKwYBBQUHMAGG -F2h0dHA6Ly9vY3NwLnNlY3RpZ28uY29tMBkGA1UdEQQSMBCCDmFwaS5leHAuZHAz -LnVzMIIBBQYKKwYBBAHWeQIEAgSB9gSB8wDxAHYArfe++nz/EMiLnT2cHj4YarRn -KV3PsQwkyoWGNOvcgooAAAGJ/56GTgAABAMARzBFAiBvXgzKBkSZ+d+QL5mKdSSR -omM7sBvCF60TBspxebUoWwIhAPz6UfNZgLEQ0jNFQIjWdgNFj2AeWmmvdlRVAbH/ -5Au1AHcAejKMVNi3LbYg6jjgUh7phBZwMhOFTTvSK8E6V6NS61IAAAGJ/56GpAAA -BAMASDBGAiEA2ZJhgP//nZDuKl0yhq8dqEThD8Wi1MoMewQeZy5BVikCIQD92/V7 -sTtr3Xwo2w/DXXPNqxh8g7DqwyWY357j/zh53zANBgkqhkiG9w0BAQsFAAOCAQEA -oLDWLRi4VsKrZbMW8rzpRaj5n5NSaT/SozqvNFYc+CLXZg9tw5Sht+0ES7oN4jnj -NkX5FfGMmjDChKdwZnlJqQlk0lfr5MwOwUFbFhd2QCdb9DIgMdKIxtA69eDsoClC -wLa4H7iwneqrJqdBoazEFIfgLx2eVDLFvd1CMTOz17NcOhQfu6e3sowVEE8w1k7b -6BbDgaof1RH6D2orCCR+5Lt/HLGHqCIkCLE29+I6OMy98XjDuFRiuOmCEs/XnvRs -sCHTOar8AhS0mfhTwDM65NoH73YGu0x9yrue63ggEQFxyEwLAkYkDBZzflBDxGs/ -2yHosMRiVcZdsf4MnAOlZA== +MIIGJzCCBQ+gAwIBAgIRAJnYZ2WO1CLAL7Jyc6GFkaYwDQYJKoZIhvcNAQELBQAw +gY8xCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO +BgNVBAcTB1NhbGZvcmQxGDAWBgNVBAoTD1NlY3RpZ28gTGltaXRlZDE3MDUGA1UE +AxMuU2VjdGlnbyBSU0EgRG9tYWluIFZhbGlkYXRpb24gU2VjdXJlIFNlcnZlciBD +QTAeFw0yMzA4MjUwMDAwMDBaFw0yNDA5MjMyMzU5NTlaMBkxFzAVBgNVBAMTDmFw +aS5leHAuZHAzLnVzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwBk+ +2DLFhEODQD47MyAfcKlMCZ9v/MpAnj7UGCbH3gbveZykcpzx4GoxxovwYAdazTDV +la8X7Cm7cGpqbOT5Gni4dzNfDzKJRSf611vlUSjtBCyFHbzgFKsfPIJXxIZO39F/ +aGSuRLTZa/qiIXnIOB7siLN+h2zfbAb77KWQCLjQ//CTPDh51j2wa5K45GXUFcCj +yKExmgQQYHNsEZS9JmFLTRkxcPCYanbUkLSctHI5oKpxkrhtIhgDhPmFUqw/U2pu +f8B71FjfX0gUmlpPhDit20BhcbyW2TgeGEivIg9uK0HCi0ZbtU0BcaImyjnpZb+4 +VG2qYbOgzSVl0JU2VwIDAQABo4IC8TCCAu0wHwYDVR0jBBgwFoAUjYxexFStiuF3 +6Zv5mwXhuAGNYeEwHQYDVR0OBBYEFGELFp7AJinyHt8DmfsPCtQF2TpCMA4GA1Ud +DwEB/wQEAwIFoDAMBgNVHRMBAf8EAjAAMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggr +BgEFBQcDAjBJBgNVHSAEQjBAMDQGCysGAQQBsjEBAgIHMCUwIwYIKwYBBQUHAgEW +F2h0dHBzOi8vc2VjdGlnby5jb20vQ1BTMAgGBmeBDAECATCBhAYIKwYBBQUHAQEE +eDB2ME8GCCsGAQUFBzAChkNodHRwOi8vY3J0LnNlY3RpZ28uY29tL1NlY3RpZ29S +U0FEb21haW5WYWxpZGF0aW9uU2VjdXJlU2VydmVyQ0EuY3J0MCMGCCsGAQUFBzAB +hhdodHRwOi8vb2NzcC5zZWN0aWdvLmNvbTAZBgNVHREEEjAQgg5hcGkuZXhwLmRw +My51czCCAX8GCisGAQQB1nkCBAIEggFvBIIBawFpAHUAdv+IPwq2+5VRwmHM9Ye6 +NLSkzbsp3GhCCp/mZ0xaOnQAAAGKKg5KNQAABAMARjBEAiA9H/mNZeEsJkfgZpmS +CcwbpSM66uNB4ucavhWhfxuFnQIgZ4IF9XyIIxvjg1S/Xt0cbhkIWhRErlaE1FME +nMvlMuoAdwDatr9rP7W2Ip+bwrtca+hwkXFsu1GEhTS9pD0wSNf7qwAAAYoqDkqF +AAAEAwBIMEYCIQCG+ADYuRQqVwkVSWK+vLoTfnqiToRGb4Ldnug3v6/LPAIhAIt0 +nS6CvRvnUkaCABua5iTL11PLaAVXA0b2+TDIvvSPAHcA7s3QZNXbGs7FXLedtM0T +ojKHRny87N7DUUhZRnEftZsAAAGKKg5KWwAABAMASDBGAiEA2oKWPOxwiao9+frp +eWum121dFJJS9bYbYoReuttH0H4CIQCOmzDdVIgOROhYAT84NT2qNEZEx+B/Au2g ++3aQglpf+jANBgkqhkiG9w0BAQsFAAOCAQEAv6+uGv+Jwip9QqmNfgiY7wZWtZSP +HAi3fzjsgVoWTry6XwPdwud7Vi1YHaK1e9TA3sMMibzbsNiezsFUTk/05Bruh6lk +2F6jIxVhRTZzRE0qMz//t8x5hBxQgH9A+9KaQhZXc/zAhZGsbsz0CmBnNFsyULJj +DBVrZkjAjL41iIwO2iMcDvIRd0mWnSbd9TPPKyn6z79f4nVVDNbr1t/+dSnuKI22 +Y2GT0DMMtthe1zSRnbXdu1zfdNI3iGq2i6MGtWOoUY1ROYuyOP5bUH1k1ftnm+Oo +KqiuYXKdvu+seL14OHYaP5TDZTce7OGn8iZD4ZY6nLLA919Cfz0zt9T3YA== -----END CERTIFICATE----- diff --git a/config/tls/api.loadtest.dp3.us.chain.der.p7b b/config/tls/api.loadtest.dp3.us.chain.der.p7b index 7c4fcc693cb..8e90d3ed5da 100644 Binary files a/config/tls/api.loadtest.dp3.us.chain.der.p7b and b/config/tls/api.loadtest.dp3.us.chain.der.p7b differ diff --git a/config/tls/api.loadtest.dp3.us.crt b/config/tls/api.loadtest.dp3.us.crt index 3d142c4f506..721026c6f42 100644 --- a/config/tls/api.loadtest.dp3.us.crt +++ b/config/tls/api.loadtest.dp3.us.crt @@ -1,36 +1,36 @@ -----BEGIN CERTIFICATE----- -MIIGLTCCBRWgAwIBAgIQbKZa26vVIPynjPDPxS+9mDANBgkqhkiG9w0BAQsFADCB -jzELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G -A1UEBxMHU2FsZm9yZDEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTcwNQYDVQQD -Ey5TZWN0aWdvIFJTQSBEb21haW4gVmFsaWRhdGlvbiBTZWN1cmUgU2VydmVyIENB -MB4XDTIzMDEwNjAwMDAwMFoXDTIzMDkyMjIzNTk1OVowHjEcMBoGA1UEAxMTYXBp -LmxvYWR0ZXN0LmRwMy51czCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB -ALMUQbd0XxvhzWMmTUsL0hwCQRP8tzn0zK1m34k7oxD8QtFtqcDtMBdJIKdcZoJM -cTN+uPIAKFzeLeG0GlP2tQId6iPwtxHNmqvntpdhY8XAM3Tbnd6kJE7TC8ocanvi -LWyEz+yjaX67FjnMv7izzr7vja3dt8vHOTnrObkGOzrUSNR0TdxX0Jp1h0fkiv0L -DzHyAwIa3eE7A376nT4ILHAiGlJWlGz0CS35JRTtBwDZZvu1nRtQXn87aNzhR5CQ -01Z6wLhnUU1JFsi90Hrrna4Ns5AQ2oqnzwXVHJ28A4EOevOr6VWm/C9Rkky1RnqM -zxEpqkveqvI4yHmIu35epcMCAwEAAaOCAvMwggLvMB8GA1UdIwQYMBaAFI2MXsRU -rYrhd+mb+ZsF4bgBjWHhMB0GA1UdDgQWBBSQx3Kn2vf9NyJzNGu5So/Qq8LvDDAO -BgNVHQ8BAf8EBAMCBaAwDAYDVR0TAQH/BAIwADAdBgNVHSUEFjAUBggrBgEFBQcD -AQYIKwYBBQUHAwIwSQYDVR0gBEIwQDA0BgsrBgEEAbIxAQICBzAlMCMGCCsGAQUF -BwIBFhdodHRwczovL3NlY3RpZ28uY29tL0NQUzAIBgZngQwBAgEwgYQGCCsGAQUF -BwEBBHgwdjBPBggrBgEFBQcwAoZDaHR0cDovL2NydC5zZWN0aWdvLmNvbS9TZWN0 -aWdvUlNBRG9tYWluVmFsaWRhdGlvblNlY3VyZVNlcnZlckNBLmNydDAjBggrBgEF -BQcwAYYXaHR0cDovL29jc3Auc2VjdGlnby5jb20wHgYDVR0RBBcwFYITYXBpLmxv -YWR0ZXN0LmRwMy51czCCAXwGCisGAQQB1nkCBAIEggFsBIIBaAFmAHYArfe++nz/ -EMiLnT2cHj4YarRnKV3PsQwkyoWGNOvcgooAAAGFiKPcggAABAMARzBFAiEAll7N -CypOd/TEEpXsxsTeAfKMcnWthQGDaw6TKBdGwAACIG/oarTEPEXD6fYL8V9FgBAT -FUTfU79vgE+8KfqmIeqkAHUAejKMVNi3LbYg6jjgUh7phBZwMhOFTTvSK8E6V6NS -61IAAAGFiKPcdQAABAMARjBEAiBwU60fnC680c80zae9SUnYoMqBywK2hmfucIXf -Jo2SVAIgeiFb15Awef4Kf2MwBeDHhB85Ev2v6Zdlu2nJE22/1XQAdQDoPtDaPvUG -NTLnVyi8iWvJA9PL0RFr7Otp4Xd9bQa9bgAAAYWIo9w6AAAEAwBGMEQCIGRHjXrC -i4yhkHW6/8NUc9ez/SxdqE3NANCVhCdZaIRqAiBgdC/bnsw6uf7cynS1nCt9B5AG -L5xeYOVtu6fgB4JERzANBgkqhkiG9w0BAQsFAAOCAQEASiG9H47kMcEaT6M39ELK -JtI0Sz9Y6wkKcPvuGN0T6nEaPAgjRS+ZG3NGsXjhjrLnQrs3PxWEduKdLj47xAq8 -Gr3j+LLv+EeSU3wIsey8FM8FxmN2HC9NKNQtcIuhSUO12+d5a8EcNQBtLA7ncHa9 -diT7h2ZfkYv3nsq8lGSlY//PtduwSsvYM+V0RQz+Dq0TZrygBOABiH+f7E4ZKQlj -KTCbEhFC5jDbm4IE88zBbE29naidgWAttJD6jAdvQ+7lY3yhX2q5z7HcmAAMVxGh -9aaR4FLkc3clgwvr+j0IIqQgtic56PhX6NsC8xXhesuPRWOOVH6b+3FqI0SSMh2h -Ag== +MIIGMTCCBRmgAwIBAgIRAObSiatB91zjrlkLzaI42EgwDQYJKoZIhvcNAQELBQAw +gY8xCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO +BgNVBAcTB1NhbGZvcmQxGDAWBgNVBAoTD1NlY3RpZ28gTGltaXRlZDE3MDUGA1UE +AxMuU2VjdGlnbyBSU0EgRG9tYWluIFZhbGlkYXRpb24gU2VjdXJlIFNlcnZlciBD +QTAeFw0yMzA4MjQwMDAwMDBaFw0yNDA5MjIyMzU5NTlaMB4xHDAaBgNVBAMTE2Fw +aS5sb2FkdGVzdC5kcDMudXMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQCzFEG3dF8b4c1jJk1LC9IcAkET/Lc59MytZt+JO6MQ/ELRbanA7TAXSSCnXGaC +THEzfrjyAChc3i3htBpT9rUCHeoj8LcRzZqr57aXYWPFwDN0253epCRO0wvKHGp7 +4i1shM/so2l+uxY5zL+4s86+742t3bfLxzk56zm5Bjs61EjUdE3cV9CadYdH5Ir9 +Cw8x8gMCGt3hOwN++p0+CCxwIhpSVpRs9Akt+SUU7QcA2Wb7tZ0bUF5/O2jc4UeQ +kNNWesC4Z1FNSRbIvdB6652uDbOQENqKp88F1RydvAOBDnrzq+lVpvwvUZJMtUZ6 +jM8RKapL3qryOMh5iLt+XqXDAgMBAAGjggL2MIIC8jAfBgNVHSMEGDAWgBSNjF7E +VK2K4Xfpm/mbBeG4AY1h4TAdBgNVHQ4EFgQUkMdyp9r3/TciczRruUqP0KvC7www +DgYDVR0PAQH/BAQDAgWgMAwGA1UdEwEB/wQCMAAwHQYDVR0lBBYwFAYIKwYBBQUH +AwEGCCsGAQUFBwMCMEkGA1UdIARCMEAwNAYLKwYBBAGyMQECAgcwJTAjBggrBgEF +BQcCARYXaHR0cHM6Ly9zZWN0aWdvLmNvbS9DUFMwCAYGZ4EMAQIBMIGEBggrBgEF +BQcBAQR4MHYwTwYIKwYBBQUHMAKGQ2h0dHA6Ly9jcnQuc2VjdGlnby5jb20vU2Vj +dGlnb1JTQURvbWFpblZhbGlkYXRpb25TZWN1cmVTZXJ2ZXJDQS5jcnQwIwYIKwYB +BQUHMAGGF2h0dHA6Ly9vY3NwLnNlY3RpZ28uY29tMB4GA1UdEQQXMBWCE2FwaS5s +b2FkdGVzdC5kcDMudXMwggF/BgorBgEEAdZ5AgQCBIIBbwSCAWsBaQB3AHb/iD8K +tvuVUcJhzPWHujS0pM27KdxoQgqf5mdMWjp0AAABiiTn6VQAAAQDAEgwRgIhAMZs +IwyQArMxkQ8Etk0qHpc5TQPa83xrUIFqrgRX6hf2AiEAis9+SX2fuFCYTSwfsLLP +a7F2aifKyrQSPSzZVQx60QUAdgDatr9rP7W2Ip+bwrtca+hwkXFsu1GEhTS9pD0w +SNf7qwAAAYok5+mmAAAEAwBHMEUCICWFyZh0szQKM6Golr0s4eHXBaXAf4lXGGLE +CPP/qqkcAiEA6i5BknyCyI77pV1TvOZtnKUflh6ciXFCa5YpxbP9SssAdgDuzdBk +1dsazsVct520zROiModGfLzs3sNRSFlGcR+1mwAAAYok5+mBAAAEAwBHMEUCIGVc +4duwRf7mYB0fPNmDfY3A99wMdwaUHIVjjqe2kXtQAiEAh13YuzUs2Tv2zY5yxaR7 +dNX3P5so0npliQEaJhauDsEwDQYJKoZIhvcNAQELBQADggEBAH0mHBtGdQcBaY+k +s42T/QZUgiIyALY7B79pCNSIBTK4x5DhXbbCezhnhU2oB/5xqhbhMAy/q80OE1GB +e8YWIjM8i5DG+st/8SxPMcouHsMPz1A5yQ2Lql+JGDn+5Ht6HrU9SjXVQA0ByoOP +iPrNOUZ8hnNkN+zB9N4CmfkHtJo39MKw+V2mCWZWgPmaRdI27CeVU9kkDVZ2C93k +0VbirQCQF18h9AQ1tVtBOvlj5shiIiJ0DAqlkNKRDT1uvjFhtIt/wUV+qrb6lGtu +BYhyP8QJgl12e9qzYS3gT75CJU+hVROGSZz+01XNJONsTpXL2kzj+/iLgBkdD26l +Dk5VUhk= -----END CERTIFICATE----- diff --git a/go.mod b/go.mod index 1bc78308089..93e9964621e 100644 --- a/go.mod +++ b/go.mod @@ -3,23 +3,23 @@ module github.com/transcom/mymove go 1.20 require ( - github.com/DATA-DOG/go-txdb v0.1.5 + github.com/DATA-DOG/go-txdb v0.1.7 github.com/XSAM/otelsql v0.23.0 github.com/alexedwards/scs/redisstore v0.0.0-20221223131519-238b052508b6 github.com/alexedwards/scs/v2 v2.5.1 - github.com/aws/aws-sdk-go-v2 v1.18.1 - github.com/aws/aws-sdk-go-v2/config v1.18.27 - github.com/aws/aws-sdk-go-v2/credentials v1.13.26 - github.com/aws/aws-sdk-go-v2/feature/rds/auth v1.2.12 + github.com/aws/aws-sdk-go-v2 v1.21.0 + github.com/aws/aws-sdk-go-v2/config v1.18.37 + github.com/aws/aws-sdk-go-v2/credentials v1.13.35 + github.com/aws/aws-sdk-go-v2/feature/rds/auth v1.2.19 github.com/aws/aws-sdk-go-v2/service/cloudwatchevents v1.15.13 - github.com/aws/aws-sdk-go-v2/service/ecr v1.18.13 + github.com/aws/aws-sdk-go-v2/service/ecr v1.19.5 github.com/aws/aws-sdk-go-v2/service/ecs v1.27.4 github.com/aws/aws-sdk-go-v2/service/rds v1.45.2 - github.com/aws/aws-sdk-go-v2/service/s3 v1.35.0 - github.com/aws/aws-sdk-go-v2/service/ses v1.15.11 - github.com/aws/aws-sdk-go-v2/service/ssm v1.36.6 - github.com/aws/aws-sdk-go-v2/service/sts v1.19.2 - github.com/aws/smithy-go v1.13.5 + github.com/aws/aws-sdk-go-v2/service/s3 v1.38.5 + github.com/aws/aws-sdk-go-v2/service/ses v1.16.7 + github.com/aws/aws-sdk-go-v2/service/ssm v1.37.5 + github.com/aws/aws-sdk-go-v2/service/sts v1.21.5 + github.com/aws/smithy-go v1.14.2 github.com/benbjohnson/clock v1.3.5 github.com/codegangsta/gin v0.0.0-20211113050330-71f90109db02 github.com/disintegration/imaging v1.6.2 @@ -36,7 +36,7 @@ require ( github.com/go-openapi/strfmt v0.21.7 github.com/go-openapi/swag v0.22.4 github.com/go-openapi/validate v0.22.1 - github.com/go-playground/validator/v10 v10.14.1 + github.com/go-playground/validator/v10 v10.15.1 github.com/go-swagger/go-swagger v0.30.5 github.com/gobuffalo/envy v1.10.2 github.com/gobuffalo/fizz v1.14.4 @@ -51,7 +51,6 @@ require ( github.com/gorilla/csrf v1.7.1 github.com/imdario/mergo v0.3.16 github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa - github.com/jackc/pgx/v4 v4.18.1 github.com/jessevdk/go-flags v1.5.0 github.com/jinzhu/copier v0.3.5 github.com/jmoiron/sqlx v1.3.5 @@ -62,7 +61,7 @@ require ( github.com/pdfcpu/pdfcpu v0.2.5 github.com/pkg/errors v0.9.1 github.com/pkg/sftp v1.13.5 - github.com/pterm/pterm v0.12.63 + github.com/pterm/pterm v0.12.66 github.com/rickar/cal/v2 v2.1.13 github.com/spf13/afero v1.9.5 github.com/spf13/cobra v1.7.0 @@ -72,30 +71,30 @@ require ( github.com/tcnksm/go-input v0.0.0-20180404061846-548a7d7a8ee8 github.com/tealeg/xlsx/v3 v3.3.0 github.com/tiaguinho/gosoap v1.4.4 - github.com/vektra/mockery/v2 v2.32.0 - go.flipt.io/flipt/rpc/flipt v1.22.0 - go.flipt.io/flipt/sdk/go v0.3.0 + github.com/vektra/mockery/v2 v2.33.0 + go.flipt.io/flipt/rpc/flipt v1.25.0 + go.flipt.io/flipt/sdk/go v0.5.0 go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 go.opentelemetry.io/contrib/detectors/aws/ecs v1.17.0 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.42.0 go.opentelemetry.io/contrib/instrumentation/runtime v0.42.0 go.opentelemetry.io/contrib/propagators/aws v1.17.0 - go.opentelemetry.io/otel v1.16.0 + go.opentelemetry.io/otel v1.17.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.39.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.16.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.16.0 go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v0.39.0 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.16.0 - go.opentelemetry.io/otel/metric v1.16.0 + go.opentelemetry.io/otel/metric v1.17.0 go.opentelemetry.io/otel/sdk v1.16.0 go.opentelemetry.io/otel/sdk/metric v0.39.0 - go.opentelemetry.io/otel/trace v1.16.0 - go.uber.org/zap v1.24.0 - golang.org/x/crypto v0.11.0 - golang.org/x/net v0.12.0 + go.opentelemetry.io/otel/trace v1.17.0 + go.uber.org/zap v1.25.0 + golang.org/x/crypto v0.12.0 + golang.org/x/net v0.14.0 golang.org/x/oauth2 v0.10.0 - golang.org/x/text v0.11.0 - golang.org/x/tools v0.11.0 + golang.org/x/text v0.12.0 + golang.org/x/tools v0.12.0 google.golang.org/grpc v1.57.0 gopkg.in/dnaeon/go-vcr.v3 v3.1.2 gotest.tools/gotestsum v1.10.1 @@ -103,26 +102,26 @@ require ( ) require ( - atomicgo.dev/cursor v0.1.3 // indirect + atomicgo.dev/cursor v0.2.0 // indirect atomicgo.dev/keyboard v0.2.9 // indirect - atomicgo.dev/schedule v0.0.2 // indirect + atomicgo.dev/schedule v0.1.0 // indirect github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.2.1 // indirect github.com/Masterminds/sprig/v3 v3.2.3 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.4 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.34 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.28 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.3.35 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.26 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.29 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.28 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.3 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.12.12 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.12 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.13 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.11 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.3.42 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.14 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.36 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.35 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.4 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.13.5 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.15.5 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/brunoscheufler/aws-ecs-metadata-go v0.0.0-20221221133751-67e37ae746cd // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect @@ -145,7 +144,7 @@ require ( github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-sql-driver/mysql v1.7.0 // indirect + github.com/go-sql-driver/mysql v1.7.1 // indirect github.com/gobuffalo/attrs v1.0.3 // indirect github.com/gobuffalo/genny/v2 v2.1.0 // indirect github.com/gobuffalo/github_flavored_markdown v1.1.4 // indirect @@ -161,7 +160,7 @@ require ( github.com/google/go-querystring v1.0.0 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.3.0 // indirect - github.com/gookit/color v1.5.3 // indirect + github.com/gookit/color v1.5.4 // indirect github.com/gorilla/css v1.0.0 // indirect github.com/gorilla/handlers v1.5.1 // indirect github.com/gorilla/securecookie v1.1.1 // indirect @@ -180,6 +179,7 @@ require ( github.com/jackc/pgproto3/v2 v2.3.2 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/pgtype v1.14.0 // indirect + github.com/jackc/pgx/v4 v4.18.1 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/joho/godotenv v1.5.1 // indirect github.com/josharian/intern v1.0.0 // indirect @@ -194,7 +194,7 @@ require ( github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.17 // indirect - github.com/mattn/go-runewidth v0.0.14 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mattn/go-shellwords v1.0.12 // indirect github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect github.com/microcosm-cc/bluemonday v1.0.23 // indirect @@ -231,14 +231,13 @@ require ( go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.16.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.39.0 // indirect go.opentelemetry.io/proto/otlp v0.19.0 // indirect - go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/exp v0.0.0-20230212135524-a684f29349b6 // indirect golang.org/x/image v0.5.0 // indirect golang.org/x/mod v0.12.0 // indirect golang.org/x/sync v0.3.0 // indirect - golang.org/x/sys v0.10.0 // indirect - golang.org/x/term v0.10.0 // indirect + golang.org/x/sys v0.11.0 // indirect + golang.org/x/term v0.11.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20230726155614-23370e0ffb3e // indirect google.golang.org/genproto/googleapis/api v0.0.0-20230726155614-23370e0ffb3e // indirect diff --git a/go.sum b/go.sum index 859aa034536..02368c94cf2 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,10 @@ atomicgo.dev/assert v0.0.2 h1:FiKeMiZSgRrZsPo9qn/7vmr7mCsh5SZyXY4YGYiYwrg= -atomicgo.dev/cursor v0.1.3 h1:w8GcylMdZRyFzvDiGm3wy3fhZYYT7BwaqNjUFHxo0NU= -atomicgo.dev/cursor v0.1.3/go.mod h1:Lr4ZJB3U7DfPPOkbH7/6TOtJ4vFGHlgj1nc+n900IpU= +atomicgo.dev/cursor v0.2.0 h1:H6XN5alUJ52FZZUkI7AlJbUc1aW38GWZalpYRPpoPOw= +atomicgo.dev/cursor v0.2.0/go.mod h1:Lr4ZJB3U7DfPPOkbH7/6TOtJ4vFGHlgj1nc+n900IpU= atomicgo.dev/keyboard v0.2.9 h1:tOsIid3nlPLZ3lwgG8KZMp/SFmr7P0ssEN5JUsm78K8= atomicgo.dev/keyboard v0.2.9/go.mod h1:BC4w9g00XkxH/f1HXhW2sXmJFOCWbKn9xrOunSFtExQ= -atomicgo.dev/schedule v0.0.2 h1:2e/4KY6t3wokja01Cyty6qgkQM8MotJzjtqCH70oX2Q= -atomicgo.dev/schedule v0.0.2/go.mod h1:xeUa3oAkiuHYh8bKiQBRojqAMq3PXXbJujjb0hw8pEU= +atomicgo.dev/schedule v0.1.0 h1:nTthAbhZS5YZmgYbb2+DH8uQIZcTlIrd4eYr3UQxEjs= +atomicgo.dev/schedule v0.1.0/go.mod h1:xeUa3oAkiuHYh8bKiQBRojqAMq3PXXbJujjb0hw8pEU= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= @@ -48,8 +48,8 @@ github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557 h1:l6surSnJ3RP4qA github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557/go.mod h1:sTrmvD/TxuypdOERsDOS7SndZg0rzzcCi1b6wQMXUYM= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/DATA-DOG/go-txdb v0.1.5 h1:kKzz+LYk9qw1+fMyo8/9yDQiNXrJ2HbfX/TY61HkkB4= -github.com/DATA-DOG/go-txdb v0.1.5/go.mod h1:DhAhxMXZpUJVGnT+p9IbzJoRKvlArO2pkHjnGX7o0n0= +github.com/DATA-DOG/go-txdb v0.1.7 h1:ibr3YvD3SKI4oBPbXbmzsn7eCPlg9oFdDdFtsWCvy7Q= +github.com/DATA-DOG/go-txdb v0.1.7/go.mod h1:l06JaBQdV+y4aWAmDmWj4NwfnJknEXBxg8d4B8sJzXA= github.com/MarvinJWendt/testza v0.1.0/go.mod h1:7AxNvlfeHP7Z/hDQ5JtE3OKYT3XFUeLCDE2DQninSqs= github.com/MarvinJWendt/testza v0.2.1/go.mod h1:God7bhG8n6uQxwdScay+gjm9/LnO4D3kkcZX4hv9Rp8= github.com/MarvinJWendt/testza v0.2.8/go.mod h1:nwIcjmr0Zz+Rcwfh3/4UhBp7ePKVhuBExvZqnKYWlII= @@ -80,56 +80,61 @@ github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:W github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk= -github.com/aws/aws-sdk-go-v2 v1.18.1 h1:+tefE750oAb7ZQGzla6bLkOwfcQCEtC5y2RqoqCeqKo= github.com/aws/aws-sdk-go-v2 v1.18.1/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 h1:dK82zF6kkPeCo8J1e+tGx4JdvDIQzj7ygIoLg8WMuGs= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10/go.mod h1:VeTZetY5KRJLuD/7fkQXMU6Mw7H5m/KP2J5Iy9osMno= -github.com/aws/aws-sdk-go-v2/config v1.18.27 h1:Az9uLwmssTE6OGTpsFqOnaGpLnKDqNYOJzWuC6UAYzA= -github.com/aws/aws-sdk-go-v2/config v1.18.27/go.mod h1:0My+YgmkGxeqjXZb5BYme5pc4drjTnM+x1GJ3zv42Nw= -github.com/aws/aws-sdk-go-v2/credentials v1.13.26 h1:qmU+yhKmOCyujmuPY7tf5MxR/RKyZrOPO3V4DobiTUk= -github.com/aws/aws-sdk-go-v2/credentials v1.13.26/go.mod h1:GoXt2YC8jHUBbA4jr+W3JiemnIbkXOfxSXcisUsZ3os= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.4 h1:LxK/bitrAr4lnh9LnIS6i7zWbCOdMsfzKFBI6LUCS0I= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.4/go.mod h1:E1hLXN/BL2e6YizK1zFlYd8vsfi2GTjbjBazinMmeaM= -github.com/aws/aws-sdk-go-v2/feature/rds/auth v1.2.12 h1:2v4gCEM/SfIMZiMT8luKNmgrngfZo61YPENzedO22n0= -github.com/aws/aws-sdk-go-v2/feature/rds/auth v1.2.12/go.mod h1:G5IqZWpdJ0wIV6URDOeFvpa0qEDz9GTn3uoPKU1EQCA= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.34 h1:A5UqQEmPaCFpedKouS4v+dHCTUo2sKqhoKO9U5kxyWo= +github.com/aws/aws-sdk-go-v2 v1.21.0 h1:gMT0IW+03wtYJhRqTVYn0wLzwdnK9sRMcxmtfGzRdJc= +github.com/aws/aws-sdk-go-v2 v1.21.0/go.mod h1:/RfNgGmRxI+iFOB1OeJUyxiU+9s88k3pfHvDagGEp0M= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.13 h1:OPLEkmhXf6xFPiz0bLeDArZIDx1NNS4oJyG4nv3Gct0= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.13/go.mod h1:gpAbvyDGQFozTEmlTFO8XcQKHzubdq0LzRyJpG6MiXM= +github.com/aws/aws-sdk-go-v2/config v1.18.37 h1:RNAfbPqw1CstCooHaTPhScz7z1PyocQj0UL+l95CgzI= +github.com/aws/aws-sdk-go-v2/config v1.18.37/go.mod h1:8AnEFxW9/XGKCbjYDCJy7iltVNyEI9Iu9qC21UzhhgQ= +github.com/aws/aws-sdk-go-v2/credentials v1.13.35 h1:QpsNitYJu0GgvMBLUIYu9H4yryA5kMksjeIVQfgXrt8= +github.com/aws/aws-sdk-go-v2/credentials v1.13.35/go.mod h1:o7rCaLtvK0hUggAGclf76mNGGkaG5a9KWlp+d9IpcV8= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.11 h1:uDZJF1hu0EVT/4bogChk8DyjSF6fof6uL/0Y26Ma7Fg= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.11/go.mod h1:TEPP4tENqBGO99KwVpV9MlOX4NSrSLP8u3KRy2CDwA8= +github.com/aws/aws-sdk-go-v2/feature/rds/auth v1.2.19 h1:G2Lci4ZUQPyeAnuPSs1QQRx153Tcg4l28Iasnmd8F30= +github.com/aws/aws-sdk-go-v2/feature/rds/auth v1.2.19/go.mod h1:PwSqxzMM8n6tP98Dw/m8bc353aELMyYczvrCDDU6sbY= github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.34/go.mod h1:wZpTEecJe0Btj3IYnDx/VlUzor9wm3fJHyvLpQF0VwY= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.28 h1:srIVS45eQuewqz6fKKu6ZGXaq6FuFg5NzgQBAM6g8Y4= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41 h1:22dGT7PneFMx4+b3pz7lMTRyN8ZKH7M2cW4GP9yUS2g= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41/go.mod h1:CrObHAuPneJBlfEJ5T3szXOUkLEThaGfvnhTf33buas= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.28/go.mod h1:7VRpKQQedkfIEXb4k52I7swUnZP0wohVajJMRn3vsUw= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.35 h1:LWA+3kDM8ly001vJ1X1waCuLJdtTl48gwkPKWy9sosI= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.35/go.mod h1:0Eg1YjxE0Bhn56lx+SHJwCzhW+2JGtizsrx+lCqrfm0= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.26 h1:wscW+pnn3J1OYnanMnza5ZVYXLX4cKk5rAvUAl4Qu+c= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.26/go.mod h1:MtYiox5gvyB+OyP0Mr0Sm/yzbEAIPL9eijj/ouHAPw0= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35 h1:SijA0mgjV8E+8G45ltVHs0fvKpTj8xmZJ3VwhGKtUSI= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35/go.mod h1:SJC1nEVVva1g3pHAIdCp7QsRIkMmLAgoDquQ9Rr8kYw= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.42 h1:GPUcE/Yq7Ur8YSUk6lVkoIMWnJNO0HT18GUzCWCgCI0= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.42/go.mod h1:rzfdUlfA+jdgLDmPKjd3Chq9V7LVLYo1Nz++Wb91aRo= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.4 h1:6lJvvkQ9HmbHZ4h/IEwclwv2mrTW8Uq1SOB/kXy0mfw= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.4/go.mod h1:1PrKYwxTM+zjpw9Y41KFtoJCQrJ34Z47Y4VgVbfndjo= github.com/aws/aws-sdk-go-v2/service/cloudwatchevents v1.15.13 h1:pLQWDntUlaDO4jde6MqxOrCBKrYvlU11PJrIttEQPGA= github.com/aws/aws-sdk-go-v2/service/cloudwatchevents v1.15.13/go.mod h1:N0aDlGD0iO8/Hs1OAQ6cgYmIMz7EoY8BGXGLdv9n8qI= -github.com/aws/aws-sdk-go-v2/service/ecr v1.18.13 h1:hF7MUVNjubetjggZDtn3AmqCJzD7EUi//tSdxMYPm7U= -github.com/aws/aws-sdk-go-v2/service/ecr v1.18.13/go.mod h1:XwEFO35g0uN/SftK0asWxh8Rk6DOx37R83TmWe2tzEE= +github.com/aws/aws-sdk-go-v2/service/ecr v1.19.5 h1:hg2/a7rE9dwYr+/DPNzHQ+IsHXLNt1NsQVUecBtA8os= +github.com/aws/aws-sdk-go-v2/service/ecr v1.19.5/go.mod h1:pGwmNL8hN0jpBfKfTbmu+Rl0bJkDhaGl+9PQLrZ4KLo= github.com/aws/aws-sdk-go-v2/service/ecs v1.27.4 h1:F1N0Eh5EGRRY9QpF+tMTkx8Wb59DkQWE91Xza/9dk1c= github.com/aws/aws-sdk-go-v2/service/ecs v1.27.4/go.mod h1:0irnFofeEZwT7uTjSkNVcSQJbWRqZ9BRoxhKjt1BObM= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11 h1:y2+VQzC6Zh2ojtV2LoC0MNwHWc6qXv/j2vrQtlftkdA= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11/go.mod h1:iV4q2hsqtNECrfmlXyord9u4zyuFEJX9eLgLpSPzWA8= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.29 h1:zZSLP3v3riMOP14H7b4XP0uyfREDQOYv2cqIrvTXDNQ= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.29/go.mod h1:z7EjRjVwZ6pWcWdI2H64dKttvzaP99jRIj5hphW0M5U= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.28 h1:bkRyG4a929RCnpVSTvLM2j/T4ls015ZhhYApbmYs15s= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.14 h1:m0QTSI6pZYJTk5WSKx3fm5cNW/DCicVzULBgU/6IyD0= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.14/go.mod h1:dDilntgHy9WnHXsh7dDtUPgHKEfTJIBUTHM8OWm0f/0= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.36 h1:eev2yZX7esGRjqRbnVk1UxMLw4CyVZDpZXRCcy75oQk= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.36/go.mod h1:lGnOkH9NJATw0XEPcAknFBj3zzNTEGRHtSw+CwC1YTg= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.28/go.mod h1:jj7znCIg05jXlaGBlFMGP8+7UN3VtCkRBG2spnmRQkU= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.3 h1:dBL3StFxHtpBzJJ/mNEsjXVgfO+7jR0dAIEwLqMapEA= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.3/go.mod h1:f1QyiAsvIv4B49DmCqrhlXqyaR+0IxMmyX+1P+AnzOM= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.35 h1:CdzPW9kKitgIiLV1+MHobfR5Xg25iYnyzWZhyQuSlDI= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.35/go.mod h1:QGF2Rs33W5MaN9gYdEQOBBFPLwTZkEhRwI33f7KIG0o= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.4 h1:v0jkRigbSD6uOdwcaUQmgEwG1BkPfAPDqaeNt/29ghg= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.4/go.mod h1:LhTyt8J04LL+9cIt7pYJ5lbS/U98ZmXovLOR/4LUsk8= github.com/aws/aws-sdk-go-v2/service/rds v1.45.2 h1:tKZ+n9/BTnNYXZo80nIoFes7QEbOd/lnbp0EkPbHfUU= github.com/aws/aws-sdk-go-v2/service/rds v1.45.2/go.mod h1:goBDR4OPrsnKpYyU0GHGcEnlTmL8O+eKGsWeyOAFJ5M= -github.com/aws/aws-sdk-go-v2/service/s3 v1.35.0 h1:ya7fmrN2fE7s1P2gaPbNg5MTkERVWfsH8ToP1YC4Z9o= -github.com/aws/aws-sdk-go-v2/service/s3 v1.35.0/go.mod h1:aVbf0sko/TsLWHx30c/uVu7c62+0EAJ3vbxaJga0xCw= -github.com/aws/aws-sdk-go-v2/service/ses v1.15.11 h1:7/MtUTSdIdZuW3jRWwDq5kKVihe+VwBGEjWkuZbhlFg= -github.com/aws/aws-sdk-go-v2/service/ses v1.15.11/go.mod h1:iI3tHe16kyXcYA6fcoct3hW1uB/XSoe46w29wupWwho= -github.com/aws/aws-sdk-go-v2/service/ssm v1.36.6 h1:/DEPQUCqR6UoJjW4a21gW9AqjFlRSTwyOmciNef19qI= -github.com/aws/aws-sdk-go-v2/service/ssm v1.36.6/go.mod h1:NdyMyZH/FzmCaybTrVMBD0nTCGrs1G4cOPKHFywx9Ns= -github.com/aws/aws-sdk-go-v2/service/sso v1.12.12 h1:nneMBM2p79PGWBQovYO/6Xnc2ryRMw3InnDJq1FHkSY= -github.com/aws/aws-sdk-go-v2/service/sso v1.12.12/go.mod h1:HuCOxYsF21eKrerARYO6HapNeh9GBNq7fius2AcwodY= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.12 h1:2qTR7IFk7/0IN/adSFhYu9Xthr0zVFTgBrmPldILn80= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.12/go.mod h1:E4VrHCPzmVB/KFXtqBGKb3c8zpbNBgKe3fisDNLAW5w= -github.com/aws/aws-sdk-go-v2/service/sts v1.19.2 h1:XFJ2Z6sNUUcAz9poj+245DMkrHE4h2j5I9/xD50RHfE= -github.com/aws/aws-sdk-go-v2/service/sts v1.19.2/go.mod h1:dp0yLPsLBOi++WTxzCjA/oZqi6NPIhoR+uF7GeMU9eg= -github.com/aws/smithy-go v1.13.5 h1:hgz0X/DX0dGqTYpGALqXJoRKRj5oQ7150i5FdTePzO8= +github.com/aws/aws-sdk-go-v2/service/s3 v1.38.5 h1:A42xdtStObqy7NGvzZKpnyNXvoOmm+FENobZ0/ssHWk= +github.com/aws/aws-sdk-go-v2/service/s3 v1.38.5/go.mod h1:rDGMZA7f4pbmTtPOk5v5UM2lmX6UAbRnMDJeDvnH7AM= +github.com/aws/aws-sdk-go-v2/service/ses v1.16.7 h1:yK0opxuSyFWmS3pL+niGStVheVccYwCFTelVrQ2nSAo= +github.com/aws/aws-sdk-go-v2/service/ses v1.16.7/go.mod h1:2DezZ88AkYPNf3Qxf79s8bneDLzATTGVGD4Htjcyw8Y= +github.com/aws/aws-sdk-go-v2/service/ssm v1.37.5 h1:s9QR0F1W5+11lq04OJ/mihpRpA2VDFIHmu+ktgAbNfg= +github.com/aws/aws-sdk-go-v2/service/ssm v1.37.5/go.mod h1:JjBzoceyKkpQY3v1GPIdg6kHqUFHRJ7SDlwtwoH0Qh8= +github.com/aws/aws-sdk-go-v2/service/sso v1.13.5 h1:oCvTFSDi67AX0pOX3PuPdGFewvLRU2zzFSrTsgURNo0= +github.com/aws/aws-sdk-go-v2/service/sso v1.13.5/go.mod h1:fIAwKQKBFu90pBxx07BFOMJLpRUGu8VOzLJakeY+0K4= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.15.5 h1:dnInJb4S0oy8aQuri1mV6ipLlnZPfnsDNB9BGO9PDNY= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.15.5/go.mod h1:yygr8ACQRY2PrEcy3xsUI357stq2AxnFM6DIsR9lij4= +github.com/aws/aws-sdk-go-v2/service/sts v1.21.5 h1:CQBFElb0LS8RojMJlxRSo/HXipvTZW2S44Lt9Mk2aYQ= +github.com/aws/aws-sdk-go-v2/service/sts v1.21.5/go.mod h1:VC7JDqsqiwXukYEDjoHh9U0fOJtNWh04FPQz4ct4GGU= github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= +github.com/aws/smithy-go v1.14.2 h1:MJU9hqBGbvWZdApzpvoF2WAIJDbtjK2NDJSiJP7HblQ= +github.com/aws/smithy-go v1.14.2/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= @@ -271,11 +276,12 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.14.1 h1:9c50NUPC30zyuKprjL3vNZ0m5oG+jU0zvx4AqHGnv4k= -github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-playground/validator/v10 v10.15.1 h1:BSe8uhN+xQ4r5guV/ywQI4gO59C2raYcGffYWZEjZzM= +github.com/go-playground/validator/v10 v10.15.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= +github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-swagger/go-swagger v0.30.5 h1:SQ2+xSonWjjoEMOV5tcOnZJVlfyUfCBhGQGArS1b9+U= github.com/go-swagger/go-swagger v0.30.5/go.mod h1:cWUhSyCNqV7J1wkkxfr5QmbcnCewetCdvEXqgPvbc/Q= @@ -430,8 +436,8 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ= github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo= -github.com/gookit/color v1.5.3 h1:twfIhZs4QLCtimkP7MOxlF3A0U/5cDPseRT9M/+2SCE= -github.com/gookit/color v1.5.3/go.mod h1:NUzwzeehUfl7GIb36pqId+UGmRfQcU/WiiyTTeNjHtE= +github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0= +github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/csrf v1.7.1 h1:Ir3o2c1/Uzj6FBxMlAUB6SivgVMy1ONXwYgXn+/aHPE= github.com/gorilla/csrf v1.7.1/go.mod h1:+a/4tCmqhG6/w4oafeAZ9pEa3/NZOWYVbD9fV0FwIQA= @@ -614,8 +620,8 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= -github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= @@ -676,8 +682,8 @@ github.com/pterm/pterm v0.12.31/go.mod h1:32ZAWZVXD7ZfG0s8qqHXePte42kdz8ECtRyEej github.com/pterm/pterm v0.12.33/go.mod h1:x+h2uL+n7CP/rel9+bImHD5lF3nM9vJj80k9ybiiTTE= github.com/pterm/pterm v0.12.36/go.mod h1:NjiL09hFhT/vWjQHSj1athJpx6H8cjpHXNAK5bUw8T8= github.com/pterm/pterm v0.12.40/go.mod h1:ffwPLwlbXxP+rxT0GsgDTzS3y3rmpAO1NMjUkGTYf8s= -github.com/pterm/pterm v0.12.63 h1:fHlrpFiI9qLtEU0TWDWMU+tAt4qKJ/s157BEAPtGm8w= -github.com/pterm/pterm v0.12.63/go.mod h1:Bq1eoUJ6BhUzzXG8WxA4l7T3s7d3Ogwg7v9VXlsVat0= +github.com/pterm/pterm v0.12.66 h1:bjsoMyUstaarzJ1NG7+1HGT7afR0JVMYsR3ooPeh4bo= +github.com/pterm/pterm v0.12.66/go.mod h1:nFuT9ZVkkCi8o4L1dtWuYPwDQxggLh4C263qG5nTLpQ= github.com/rickar/cal/v2 v2.1.13 h1:FENBPXxDPyL1OWGf9ZdpWGcEiGoSjt0UZED8VOxvK0c= github.com/rickar/cal/v2 v2.1.13/go.mod h1:/fdlMcx7GjPlIBibMzOM9gMvDBsrK+mOtRXdTzUqV/A= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= @@ -770,8 +776,8 @@ github.com/toqueteos/webbrowser v1.2.0 h1:tVP/gpK69Fx+qMJKsLE7TD8LuGWPnEV71wBN9r github.com/toqueteos/webbrowser v1.2.0/go.mod h1:XWoZq4cyp9WeUeak7w7LXRUQf1F1ATJMir8RTqb4ayM= 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.32.0 h1:IXUoQ3s5VxJPpi95DECUmkRUXZ44I1spQ3YatEypIF4= -github.com/vektra/mockery/v2 v2.32.0/go.mod h1:9lREs4VEeQiUS3rizYQx1saxHu2JiIhThP0q9+fDegM= +github.com/vektra/mockery/v2 v2.33.0 h1:C3W/EoEiBCdb8olVat+hcnqI8rebH4xmZzTP7FPoLSs= +github.com/vektra/mockery/v2 v2.33.0/go.mod h1:9lREs4VEeQiUS3rizYQx1saxHu2JiIhThP0q9+fDegM= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= @@ -791,10 +797,10 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= go.flipt.io/flipt/errors v1.19.3 h1:mgQrT3XdambAdu4UykYZ3gm1NG7Ilri5Gt+nLafbJHY= go.flipt.io/flipt/errors v1.19.3/go.mod h1:I2loVwHUoXy+yT7suRx7+pDiSyO1G7CHu6bby9DywyA= -go.flipt.io/flipt/rpc/flipt v1.22.0 h1:SHXMNLKPEmghOV36zM6OHbwHsSdSOafvxPXnDYBZY9E= -go.flipt.io/flipt/rpc/flipt v1.22.0/go.mod h1:H/P7nl/5lMo8uWiXyYGFX5LcOGL0ycWnlcLx3eaxJG4= -go.flipt.io/flipt/sdk/go v0.3.0 h1:6n7HVsLw1ApOi5Kht1Q6Cr5GkOlIJH4uVrh30Qm/n6w= -go.flipt.io/flipt/sdk/go v0.3.0/go.mod h1:4EVLw2+2SzqjqeyTx+NOz2E7a2Fs0+A+ZZKLHs6YvIY= +go.flipt.io/flipt/rpc/flipt v1.25.0 h1:BkIYs3kro8rK1yXK3+jStRXvAzq8eO/7+JeioHNxoq0= +go.flipt.io/flipt/rpc/flipt v1.25.0/go.mod h1:H/P7nl/5lMo8uWiXyYGFX5LcOGL0ycWnlcLx3eaxJG4= +go.flipt.io/flipt/sdk/go v0.5.0 h1:HiFUJ403rMWchlvFjCUyZHrQTgr7x+2ArPiTczAvGk4= +go.flipt.io/flipt/sdk/go v0.5.0/go.mod h1:XF9JWsiK41mNg5aDT3b7bzFxWUsc3Te3Gy5Ok3aijHc= go.mongodb.org/mongo-driver v1.7.3/go.mod h1:NqaYOwnXWr5Pm7AOpO5QFxKJ503nbMse/R79oO62zWg= go.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4xhp5Zvxng= go.mongodb.org/mongo-driver v1.10.0/go.mod h1:wsihk0Kdgv8Kqu1Anit4sfK+22vSFbUrAVEYRhCXrA8= @@ -816,8 +822,8 @@ go.opentelemetry.io/contrib/instrumentation/runtime v0.42.0 h1:EbmAUG9hEAMXyfWEa go.opentelemetry.io/contrib/instrumentation/runtime v0.42.0/go.mod h1:rD9feqRYP24P14t5kmhNMqsqm1jvKmpx2H2rKVw52V8= go.opentelemetry.io/contrib/propagators/aws v1.17.0 h1:IX8d7l2uRw61BlmZBOTQFaK+y22j6vytMVTs9wFrO+c= go.opentelemetry.io/contrib/propagators/aws v1.17.0/go.mod h1:pAlCYRWff4uGqRXOVn3WP8pDZ5E0K56bEoG7a1VSL4k= -go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s= -go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4= +go.opentelemetry.io/otel v1.17.0 h1:MW+phZ6WZ5/uk2nd93ANk/6yJ+dVrvNWUjGhnnFU5jM= +go.opentelemetry.io/otel v1.17.0/go.mod h1:I2vmBGtFaODIVMBSTPVDlJSzBDNf93k60E6Ft0nyjo0= go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.16.0 h1:t4ZwRPU+emrcvM2e9DHd0Fsf0JTPVcbfa/BhTDF03d0= go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.16.0/go.mod h1:vLarbg68dH2Wa77g71zmKQqlQ8+8Rq3GRG31uc0WcWI= go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.39.0 h1:f6BwB2OACc3FCbYVznctQ9V6KK7Vq6CjmYXJ7DeSs4E= @@ -832,14 +838,14 @@ go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v0.39.0 h1:fl2WmyenEf6LYY go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v0.39.0/go.mod h1:csyQxQ0UHHKVA8KApS7eUO/klMO5sd/av5CNZNU4O6w= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.16.0 h1:+XWJd3jf75RXJq29mxbuXhCXFDG3S3R4vBUeSI2P7tE= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.16.0/go.mod h1:hqgzBPTf4yONMFgdZvL/bK42R/iinTyVQtiWihs3SZc= -go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo= -go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4= +go.opentelemetry.io/otel/metric v1.17.0 h1:iG6LGVz5Gh+IuO0jmgvpTB6YVrCGngi8QGm+pMd8Pdc= +go.opentelemetry.io/otel/metric v1.17.0/go.mod h1:h4skoxdZI17AxwITdmdZjjYJQH5nzijUUjm+wtPph5o= go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE= go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4= go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh46FAScOTuDI= go.opentelemetry.io/otel/sdk/metric v0.39.0/go.mod h1:piDIRgjcK7u0HCL5pCA4e74qpK/jk3NiUoAHATVAmiI= -go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs= -go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0= +go.opentelemetry.io/otel/trace v1.17.0 h1:/SWhSRHmDPOImIAetP1QAeMnZYiQXrTy4fMMYOdSKWQ= +go.opentelemetry.io/otel/trace v1.17.0/go.mod h1:I/4vKTgFclIsXRVucpH25X0mpFSczM7aHeaz0ZBLWjY= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw= go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= @@ -848,8 +854,6 @@ go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= -go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= @@ -862,8 +866,9 @@ go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9E go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= -go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= +go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c= +go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= @@ -885,8 +890,8 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= -golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -974,8 +979,8 @@ golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfS golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= -golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= +golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1070,8 +1075,8 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= -golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -1081,8 +1086,8 @@ golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuX golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= -golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= +golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0= +golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1095,8 +1100,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= -golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1161,8 +1166,8 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.11.0 h1:EMCa6U9S2LtZXLAMoWiR/R8dAQFRqbAitmbJ2UKhoi8= -golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8= +golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss= +golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/migrations/app/migrations_manifest.txt b/migrations/app/migrations_manifest.txt index 3d6584f7dad..a71590d5736 100644 --- a/migrations/app/migrations_manifest.txt +++ b/migrations/app/migrations_manifest.txt @@ -842,3 +842,9 @@ 20230804161700_import_trdm_part_1.up.sql 20230807175944_import_trdm_part_2.up.sql 20230807180000_import_trdm_part_3.up.sql +20230808155723_add_ns_mayport.up.sql +20230822201206_add_fields_for_sit_to_mto_service_items.up.sql +20230824200422_update_loa_trnsn_ids.up.sql +20230824224954_ghc_domestic_transit_times_update_data_migration.up.sql +20230828180000_update_mayport_zip.up.sql +20230828180001_update_mayport_services_counseling.up.sql diff --git a/migrations/app/schema/20230822201206_add_fields_for_sit_to_mto_service_items.up.sql b/migrations/app/schema/20230822201206_add_fields_for_sit_to_mto_service_items.up.sql new file mode 100644 index 00000000000..b983faf195e --- /dev/null +++ b/migrations/app/schema/20230822201206_add_fields_for_sit_to_mto_service_items.up.sql @@ -0,0 +1,7 @@ +ALTER TABLE mto_service_items + ADD COLUMN sit_customer_contacted date, + ADD COLUMN sit_requested_delivery date; + +-- Comment On Column +COMMENT ON COLUMN mto_service_items.sit_customer_contacted IS 'The date when the customer contacted the prime for a delivery out of SIT'; +COMMENT ON COLUMN mto_service_items.sit_requested_delivery IS 'The date when the customer has requested delivery out of SIT'; diff --git a/migrations/app/schema/20230824224954_ghc_domestic_transit_times_update_data_migration.up.sql b/migrations/app/schema/20230824224954_ghc_domestic_transit_times_update_data_migration.up.sql new file mode 100644 index 00000000000..2cedaaacae4 --- /dev/null +++ b/migrations/app/schema/20230824224954_ghc_domestic_transit_times_update_data_migration.up.sql @@ -0,0 +1,143 @@ +TRUNCATE TABLE "public"."ghc_domestic_transit_times"; + +INSERT INTO "public"."ghc_domestic_transit_times" ("id", "max_days_transit_time", "weight_lbs_lower", "weight_lbs_upper", "distance_miles_lower", "distance_miles_upper") VALUES +('0112b560-1c59-495c-a1e8-17c262f2edd7', '9', '1', '999', '1', '250'), +('03399857-8316-4e89-ae87-78dd6b031013', '8', '1000', '1999', '1', '250'), +('03830e8c-cbfa-4f7d-b112-7dbe776696bb', '7', '2000', '3999', '1', '250'), +('0462b8f2-030d-45f4-9508-ea3ef0fffe1f', '6', '4000', '7999', '1', '250'), +('05fa7acd-c8ec-4d95-a193-c56ac5f87c06', '5', '8000', '0', '1', '250'), +('0cb6de06-fe30-49a6-b46c-8bdc42a04474', '12', '1', '999', '251', '500'), +('0d163eec-933a-4c60-b464-78fe96205d1e', '11', '1000', '1999', '251', '500'), +('0d7239cd-0a73-434d-80dd-05f76218075d', '8', '2000', '3999', '251', '500'), +('15b1488c-2c3f-429c-ad44-501e47c12f64', '7', '4000', '7999', '251', '500'), +('170f040e-0d2a-4a73-bff3-2d3f9eea7a79', '6', '8000', '0', '251', '500'), +('177e797e-d9b6-4cf4-85a6-6c659d83aa59', '15', '1', '999', '501', '750'), +('1a0493a6-d6ed-4244-bc55-93bc0ecb7b84', '13', '1000', '1999', '501', '750'), +('1a7e1caa-976b-4ac3-9d67-4a62c5cec61e', '11', '2000', '3999', '501', '750'), +('1cadc4c1-9bca-46a6-a9d7-7adc22da1327', '10', '4000', '7999', '501', '750'), +('1dad5764-9397-44e2-bf76-71dee1cd0f8f', '8', '8000', '0', '501', '750'), +('1dcea51b-e418-4e11-9a19-cc111ca7fe37', '17', '1', '999', '751', '1000'), +('1e6d0aeb-f7bf-4f91-a19a-caf5e38a0e44', '15', '1000', '1999', '751', '1000'), +('21bbb19e-4ac1-4a52-be19-520aac27db55', '12', '2000', '3999', '751', '1000'), +('22275a7c-e814-40d0-b5b7-f7ee921d4e00', '11', '4000', '7999', '751', '1000'), +('25072113-7e99-450c-9ca9-844f0c6a9177', '9', '8000', '0', '751', '1000'), +('274856a2-d936-4668-8c06-b688d198ca28', '17', '1', '999', '1001', '1250'), +('279a9c04-965d-43a0-926b-eec47a54aab3', '14', '1000', '1999', '1001', '1250'), +('295e21eb-a056-461e-a085-f1fa473ab217', '12', '2000', '3999', '1001', '1250'), +('2980abf8-0e96-4ed7-9503-235c5dc44c3f', '11', '4000', '7999', '1001', '1250'), +('29fcfa96-94f4-41d0-82f3-11268a3d9a3f', '10', '8000', '0', '1001', '1250'), +('2a42d7ef-4e9e-4aaf-9e99-44468b73d669', '18', '1', '999', '1251', '1500'), +('2b45d667-a4a0-4578-a171-010193cb74bf', '15', '1000', '1999', '1251', '1500'), +('2b989e5f-8aad-44d0-93ed-3e42d7951359', '13', '2000', '3999', '1251', '1500'), +('2c22ce21-f2e2-4679-8592-22b739e32245', '12', '4000', '7999', '1251', '1500'), +('315dc7d9-b5a7-4863-aaf9-cc746935d1a4', '11', '8000', '0', '1251', '1500'), +('3b4037be-b415-43af-9ba1-664de5fe266d', '19', '1', '999', '1501', '1750'), +('3b6aeb8f-fa7b-483f-a5c8-d0a014f01e1a', '16', '1000', '1999', '1501', '1750'), +('3c29ba46-6208-4656-993c-61a98ee35dcd', '14', '2000', '3999', '1501', '1750'), +('3ea9039b-8123-4a06-b734-691ae6b2dad8', '13', '4000', '7999', '1501', '1750'), +('3eb1e375-8520-4b0a-9b08-9e24ea7f94e7', '12', '8000', '0', '1501', '1750'), +('3ee7c175-04dc-49df-a7c1-5698094546a6', '19', '1', '999', '1751', '2000'), +('3f4e812f-580f-418e-aab4-1256ac15bbf2', '18', '1000', '1999', '1751', '2000'), +('43264c59-0b3c-47a3-a4a6-27525efe0e00', '15', '2000', '3999', '1751', '2000'), +('45ae8ae0-d230-4d76-9b0c-d9aafaaa605f', '14', '4000', '7999', '1751', '2000'), +('472de745-b92d-45ef-8254-254be7db7ce4', '13', '8000', '0', '1751', '2000'), +('473c3e83-be57-4b24-adfc-26c6fb679252', '20', '1', '999', '2001', '2250'), +('47bd49d4-cf55-45ec-ad8f-bbe6f5e7199e', '19', '1000', '1999', '2001', '2250'), +('4b3c3965-a2b9-4156-8916-62fdbe090f16', '17', '2000', '3999', '2001', '2250'), +('4d8be502-6775-4a2b-94be-e9fcd37b6277', '15', '4000', '7999', '2001', '2250'), +('4da84092-4557-49e3-a58f-b24ab7760902', '14', '8000', '0', '2001', '2250'), +('5436fde9-3847-498c-b9d4-6b1af28a6861', '21', '1', '999', '2251', '2500'), +('552c0837-30a3-44f9-8a1b-34d5720d896b', '19', '1000', '1999', '2251', '2500'), +('5560dae5-80ba-4817-841c-d8d486a561d6', '18', '2000', '3999', '2251', '2500'), +('55c1bdf0-ffdc-44ea-910c-e9b7f9dee609', '16', '4000', '7999', '2251', '2500'), +('583a8674-2338-4089-abb2-5faf3964a346', '15', '8000', '0', '2251', '2500'), +('5d8fb76d-8c1c-48fb-9a35-9eff4d7c9427', '22', '1', '999', '2501', '2750'), +('613cc341-f200-4975-9f2e-9f35c317c40f', '20', '1000', '1999', '2501', '2750'), +('61580524-e306-4b13-ae7c-4e78d8069bc4', '19', '2000', '3999', '2501', '2750'), +('6196d2e0-dcbd-43ec-85fb-eb410ab819a9', '17', '4000', '7999', '2501', '2750'), +('62259ddf-ff99-46fe-abb7-63c1c0e3bf8c', '16', '8000', '0', '2501', '2750'), +('629f1037-466c-40c9-8fbb-b26a3a6a1fb8', '23', '1', '999', '2751', '3000'), +('69c830b5-6936-4631-9e32-ae7317ba668d', '21', '1000', '1999', '2751', '3000'), +('6e1cd911-485a-4e7a-8702-c350738ad3a6', '19', '2000', '3999', '2751', '3000'), +('711ea795-1ab7-414e-908c-fd546d00bc03', '18', '4000', '7999', '2751', '3000'), +('71d3cd38-b62b-4b32-9cbe-5e658447e027', '17', '8000', '0', '2751', '3000'), +('732aeca2-bfce-4052-98ec-0352ec395ca6', '24', '1', '999', '3001', '3250'), +('73f88626-a3a2-4133-882f-cfbc85d14396', '22', '1000', '1999', '3001', '3250'), +('74551d9e-5c3b-482a-9aaf-100a6cb37ce7', '20', '2000', '3999', '3001', '3250'), +('74debd3e-aca4-4faa-a2b7-29439b96338a', '19', '4000', '7999', '3001', '3250'), +('75fad8f1-ce57-4c87-91de-02ac8759f843', '18', '8000', '0', '3001', '3250'), +('7679fe22-0565-4810-9aba-499e816b3535', '25', '1', '999', '3251', '3500'), +('78a12269-2b21-4b7f-8aa2-d5d2a31a6001', '23', '1000', '1999', '3251', '3500'), +('78c4f864-8989-4d1f-b1cf-9907657c5321', '21', '2000', '3999', '3251', '3500'), +('7aa83b7d-4c1a-4e38-b50e-fab494af8128', '19', '4000', '7999', '3251', '3500'), +('7d58bb2d-a044-4dec-9e22-38817aca776d', '19', '8000', '0', '3251', '3500'), +('7e88791f-aa78-4f4f-aee5-a01956ba4480', '26', '1', '999', '3501', '3750'), +('801f90f6-068d-4358-88ae-8ba04315b43c', '24', '1000', '1999', '3501', '3750'), +('82b26561-0ca1-47cc-9bd3-dcd87892d665', '22', '2000', '3999', '3501', '3750'), +('82ef20b3-6b05-4428-85a6-b98b464be4c5', '21', '4000', '7999', '3501', '3750'), +('8562437a-f840-44db-b76f-9f2e8d875e89', '19', '8000', '0', '3501', '3750'), +('85842e02-ab6e-464b-b029-2359d639790d', '27', '1', '999', '3751', '4000'), +('88508cf5-89b4-43b5-9780-6eb8342ca624', '25', '1000', '1999', '3751', '4000'), +('8a9178d9-7e16-4451-946f-dc24bd1d6944', '23', '2000', '3999', '3751', '4000'), +('8c12eb33-5bae-4314-8156-4eaf4357a479', '22', '4000', '7999', '3751', '4000'), +('8c2425fc-98ef-4793-a5b6-a527eb9cbaad', '20', '8000', '0', '3751', '4000'), +('8dc724e6-aa65-454b-ae96-9059090aeea6', '28', '1', '999', '4001', '4250'), +('9d0b2074-658f-4dcb-8663-27705220d12c', '26', '1000', '1999', '4001', '4250'), +('9e5ef6a4-3f0f-4d1f-941f-2839ff910cba', '24', '2000', '3999', '4001', '4250'), +('9e8800e5-c340-4df4-9245-696cd17a7d45', '23', '4000', '7999', '4001', '4250'), +('a15ed9d9-31c1-4f58-844f-39acc70d9f54', '21', '8000', '0', '4001', '4250'), +('a1a73146-bffe-403b-b523-672bfca42bd1', '29', '1', '999', '4251', '4500'), +('a24c1d0c-7190-42c7-9028-ca5e179c8641', '27', '1000', '1999', '4251', '4500'), +('a2d93b11-814a-4c26-ad8d-8e27b2ad7829', '25', '2000', '3999', '4251', '4500'), +('a542e808-93b8-470e-9ba5-3c72f76ad45e', '24', '4000', '7999', '4251', '4500'), +('a5b8a03a-32e0-4a23-bbbd-8191d1b726d0', '22', '8000', '0', '4251', '4500'), +('a805db78-6e74-40c7-be9f-748511cf2b54', '30', '1', '999', '4501', '4750'), +('aabce664-25c1-4144-bd39-6106e68b041e', '28', '1000', '1999', '4501', '4750'), +('acafa56e-306a-417c-bf5e-d5fe58cc47c0', '26', '2000', '3999', '4501', '4750'), +('acbcac9e-a812-4185-83ff-25c433a13df1', '25', '4000', '7999', '4501', '4750'), +('ad632b1d-de47-4472-bbf2-2e5ee5fbea10', '23', '8000', '0', '4501', '4750'), +('ad68cd02-b62b-43f5-b43e-45210d39d271', '31', '1', '999', '4751', '5000'), +('ae6e9e8e-d58e-4b28-beb4-36a410f29b30', '29', '1000', '1999', '4751', '5000'), +('af5aabee-872f-48ba-81cc-5b2366c885e8', '27', '2000', '3999', '4751', '5000'), +('b0fa4588-9050-4001-b22d-17902adbecf7', '26', '4000', '7999', '4751', '5000'), +('b1576b50-dabd-43b1-a582-70dc681fe7ec', '24', '8000', '0', '4751', '5000'), +('b2a48928-d7b8-4587-ab2e-bab50f49305d', '29', '1', '999', '5001', '5250'), +('b56fc986-a729-424b-abfa-6ba79b12e2ad', '27', '1000', '1999', '5001', '5250'), +('b70fcec3-f65b-4143-b799-bc2df28635b8', '25', '2000', '3999', '5001', '5250'), +('b8085372-1da5-47a1-8a53-4c4466f39891', '24', '4000', '7999', '5001', '5250'), +('b93a9274-ad23-4214-b551-53e115ae5c2b', '23', '8000', '0', '5001', '5250'), +('ba2f0e83-4578-4732-afc4-e7f0e9a8f38f', '30', '1', '999', '5251', '5500'), +('ba7b434d-28bd-4bbe-aa43-16bf26b49c82', '28', '1000', '1999', '5251', '5500'), +('bbe14b4d-f7d4-40f3-878c-40030e9ad59e', '26', '2000', '3999', '5251', '5500'), +('bde5e8b6-9694-4f0f-8656-f138eea3adee', '25', '4000', '7999', '5251', '5500'), +('be395e93-b428-49e4-89a4-2d791f4599dc', '24', '8000', '0', '5251', '5500'), +('c18479fc-11bb-4eb6-9dcc-476c180c246e', '21', '1', '999', '5501', '5750'), +('c21a8bf3-e607-4917-9700-b8571b9d007e', '29', '1000', '1999', '5501', '5750'), +('c3c6d2ce-c50d-488c-9eb1-af34bfe0f130', '27', '2000', '3999', '5501', '5750'), +('c407654b-a8dd-4843-9f08-96bb427eac90', '26', '4000', '7999', '5501', '5750'), +('c5e3eb27-27e3-4218-96dd-f2eb0d8cc057', '25', '8000', '0', '5501', '5750'), +('c98f26e4-ffc4-4755-b38b-a8982edcfc14', '31', '1', '999', '5751', '6000'), +('cc08fb75-650e-4aab-891d-3ca86609ee13', '30', '1000', '1999', '5751', '6000'), +('d07344ca-6e3c-41c9-9687-02a767873b63', '28', '2000', '3999', '5751', '6000'), +('d12c64d0-81e2-4aee-892a-028ff2db0fae', '27', '4000', '7999', '5751', '6000'), +('d1748cde-9f71-48bd-81b3-a236904dd60b', '26', '8000', '0', '5751', '6000'), +('d2b87719-5139-4a07-b911-1f77613d00a4', '32', '1', '999', '6001', '6250'), +('d483197a-c684-4ca4-81e7-23b6a7ce7acf', '31', '1000', '1999', '6001', '6250'), +('d90fce74-b1a7-439a-8c16-c8b8e7c50681', '29', '2000', '3999', '6001', '6250'), +('db654308-7503-4e7c-8567-ed1a7a0b8016', '28', '4000', '7999', '6001', '6250'), +('dca80d32-d365-46d7-bd8c-b5f60f996c1f', '27', '8000', '0', '6001', '6250'), +('dd51f4a4-3140-468b-8030-d1d110df9c82', '33', '1', '999', '6251', '6500'), +('e28f9901-23c4-4efd-97ec-eab548431c50', '31', '1000', '1999', '6251', '6500'), +('e36e9d5c-00fe-46a4-82c3-9a2a1a8279c9', '30', '2000', '3999', '6251', '6500'), +('e5cd938a-fa80-4e0a-b77b-6fb3a6632ad4', '29', '4000', '7999', '6251', '6500'), +('e66103f6-ed53-429f-b656-bc8fa9d7a9da', '28', '8000', '0', '6251', '6500'), +('e99cabd5-848a-4284-8270-51f00f75e854', '34', '1', '999', '6501', '6750'), +('ec17e44e-8a60-4595-a780-bd369ea94ec4', '32', '1000', '1999', '6501', '6750'), +('ecd3b0bd-8888-49ef-97c9-3ca815e29b8e', '31', '2000', '3999', '6501', '6750'), +('ef5e2333-d8ae-4729-aa31-38aa1892cffa', '30', '4000', '7999', '6501', '6750'), +('f357c4dc-7353-4aa6-90de-15c39ae6d6af', '29', '8000', '0', '6501', '6750'), +('f3c4e5f2-5f1b-4a05-9eca-96a3071a6c89', '35', '1', '999', '6751', '7000'), +('f62baf2b-a259-4b86-a432-7020abc63d95', '33', '1000', '1999', '6751', '7000'), +('f8c0f43a-2dc6-4ec7-8da7-5f304ecf5bde', '31', '2000', '3999', '6751', '7000'), +('f936c7a1-b85d-4cc7-8e4e-609655336cca', '31', '4000', '7999', '6751', '7000'), +('f9ed9ec2-d3be-499e-baee-f179981ca919', '30', '8000', '0', '6751', '7000'); diff --git a/migrations/app/secure/20230808155723_add_ns_mayport.up.sql b/migrations/app/secure/20230808155723_add_ns_mayport.up.sql new file mode 100644 index 00000000000..2e21d8ac1fe --- /dev/null +++ b/migrations/app/secure/20230808155723_add_ns_mayport.up.sql @@ -0,0 +1,2 @@ +INSERT INTO addresses (id, street_address_1, city, state, postal_code, updated_at, created_at) VALUES ('430c50b4-cc87-42a6-9921-2010aa6d4ed3', 'n/a', 'Mayport', 'FL', '32233', now(), now()); +INSERT INTO duty_locations (id, address_id, name, affiliation, transportation_office_id, updated_at, created_at, provides_services_counseling) VALUES ('3bfe8068-f9d1-4c21-ac19-1c0a60dcd326', '430c50b4-cc87-42a6-9921-2010aa6d4ed3', 'NS Mayport, FL 32233', 'NAVY', 'f5ab88fe-47f8-4b58-99af-41067d6cb60d', now(), now(), 'TRUE'); diff --git a/migrations/app/secure/20230824200422_update_loa_trnsn_ids.up.sql b/migrations/app/secure/20230824200422_update_loa_trnsn_ids.up.sql new file mode 100644 index 00000000000..15e1b920105 --- /dev/null +++ b/migrations/app/secure/20230824200422_update_loa_trnsn_ids.up.sql @@ -0,0 +1,21 @@ +-- Local test migration. +-- This will be run on development environments. +-- It should mirror what you intend to apply on loadtest/demo/exp/stg/prd +-- DO NOT include any sensitive data. + +-- Update loa_trnsn_id column constraint +ALTER TABLE lines_of_accounting ALTER COLUMN loa_trnsn_id TYPE varchar (3); +-- Update lines_of_accounting with updated loa_trnsn_id values, mapped by loa_sys_id +UPDATE lines_of_accounting AS loas SET + loa_trnsn_id = updated.loa_trnsn_id +FROM (VALUES + (10002, NULL), + (10001, 'A1'), + (10003, 'B1'), + (10004, NULL), + (10005, 'C1'), + (10006, 'AB'), + (10007, NULL), + (10008, 'C12') +) AS updated(loa_sys_id, loa_trnsn_id) +WHERE updated.loa_sys_id = loas.loa_sys_id; diff --git a/migrations/app/secure/20230828180000_update_mayport_zip.up.sql b/migrations/app/secure/20230828180000_update_mayport_zip.up.sql new file mode 100644 index 00000000000..582feb032bb --- /dev/null +++ b/migrations/app/secure/20230828180000_update_mayport_zip.up.sql @@ -0,0 +1,3 @@ +-- This should only be updated on staging +UPDATE addresses SET postal_code = '32228' WHERE id = '430c50b4-cc87-42a6-9921-2010aa6d4ed3'; +UPDATE duty_locations SET name = 'NS Mayport, FL 32228' WHERE id = '3bfe8068-f9d1-4c21-ac19-1c0a60dcd326'; diff --git a/migrations/app/secure/20230828180001_update_mayport_services_counseling.up.sql b/migrations/app/secure/20230828180001_update_mayport_services_counseling.up.sql new file mode 100644 index 00000000000..870704cede6 --- /dev/null +++ b/migrations/app/secure/20230828180001_update_mayport_services_counseling.up.sql @@ -0,0 +1,2 @@ +-- This should only be updated on staging +UPDATE duty_locations SET provides_services_counseling = 'FALSE' WHERE id = '3bfe8068-f9d1-4c21-ac19-1c0a60dcd326'; diff --git a/pkg/edi/invoice/generator_test.go b/pkg/edi/invoice/generator_test.go index aaf42796215..9241412ceec 100644 --- a/pkg/edi/invoice/generator_test.go +++ b/pkg/edi/invoice/generator_test.go @@ -211,7 +211,7 @@ func MakeValidEdi() Invoice858C { }, FA2s: []edisegment.FA2{ { - BreakdownStructureDetailCode: "TA", + BreakdownStructureDetailCode: edisegment.FA2DetailCodeTA, FinancialInformationCode: "1234", }, }, diff --git a/pkg/edi/segment/ak9.go b/pkg/edi/segment/ak9.go index f973360dde3..dd14e8d027b 100644 --- a/pkg/edi/segment/ak9.go +++ b/pkg/edi/segment/ak9.go @@ -10,7 +10,7 @@ type AK9 struct { FunctionalGroupAcknowledgeCode string `validate:"oneof=A E P R"` NumberOfTransactionSetsIncluded int `validate:"min=1,max=999999"` NumberOfReceivedTransactionSets int `validate:"min=1,max=999999"` - NumberOfAcceptedTransactionSets int `validate:"min=1,max=999999"` + NumberOfAcceptedTransactionSets int `validate:"min=0,max=999999"` FunctionalGroupSyntaxErrorCodeAK905 string `validate:"omitempty,max=3"` FunctionalGroupSyntaxErrorCodeAK906 string `validate:"omitempty,max=3"` FunctionalGroupSyntaxErrorCodeAK907 string `validate:"omitempty,max=3"` diff --git a/pkg/edi/segment/ak9_test.go b/pkg/edi/segment/ak9_test.go index 5846c4599ee..a0766718b3d 100644 --- a/pkg/edi/segment/ak9_test.go +++ b/pkg/edi/segment/ak9_test.go @@ -71,8 +71,7 @@ func (suite *SegmentSuite) TestValidateAK9() { suite.ValidateError(err, "FunctionalGroupAcknowledgeCode", "oneof") suite.ValidateError(err, "NumberOfTransactionSetsIncluded", "min") suite.ValidateError(err, "NumberOfReceivedTransactionSets", "min") - suite.ValidateError(err, "NumberOfAcceptedTransactionSets", "min") - suite.ValidateErrorLen(err, 4) + suite.ValidateErrorLen(err, 3) }) suite.Run("validate failure max", func() { @@ -103,7 +102,7 @@ func (suite *SegmentSuite) TestValidateAK9() { }) suite.Run("validate failure min", func() { - // length of characters are less than min + // length of characters are less than min, note that 0 is acceptable for Accepted Transaction Sets ak9 := AK9{ FunctionalGroupAcknowledgeCode: "", NumberOfTransactionSetsIncluded: 0, @@ -115,8 +114,7 @@ func (suite *SegmentSuite) TestValidateAK9() { suite.ValidateError(err, "FunctionalGroupAcknowledgeCode", "oneof") suite.ValidateError(err, "NumberOfTransactionSetsIncluded", "min") suite.ValidateError(err, "NumberOfReceivedTransactionSets", "min") - suite.ValidateError(err, "NumberOfAcceptedTransactionSets", "min") - suite.ValidateErrorLen(err, 4) + suite.ValidateErrorLen(err, 3) }) } diff --git a/pkg/edi/segment/fa2.go b/pkg/edi/segment/fa2.go index 9469242092a..95939cc66e2 100644 --- a/pkg/edi/segment/fa2.go +++ b/pkg/edi/segment/fa2.go @@ -4,15 +4,84 @@ import ( "fmt" ) +type FA2DetailCode string + +func (d FA2DetailCode) String() string { + return string(d) +} + +const ( + // FA2DetailCodeTA is Transportation Account Code (TAC) + FA2DetailCodeTA FA2DetailCode = "TA" + // FA2DetailCodeZZ is Mutually Defined + FA2DetailCodeZZ FA2DetailCode = "ZZ" + // FA2DetailCodeA1 is Department Indicator + FA2DetailCodeA1 FA2DetailCode = "A1" + // FA2DetailCodeA2 is Transfer from Department + FA2DetailCodeA2 FA2DetailCode = "A2" + // FA2DetailCodeA3 is Fiscal Year Indicator + FA2DetailCodeA3 FA2DetailCode = "A3" + // FA2DetailCodeA4 is Basic Symbol Number + FA2DetailCodeA4 FA2DetailCode = "A4" + // FA2DetailCodeA5 is Sub-class + FA2DetailCodeA5 FA2DetailCode = "A5" + // FA2DetailCodeA6 is Sub-Account Symbol + FA2DetailCodeA6 FA2DetailCode = "A6" + // FA2DetailCodeB1 is Budget Activity Number + FA2DetailCodeB1 FA2DetailCode = "B1" + // FA2DetailCodeB2 is Budget Sub-activity Number + FA2DetailCodeB2 FA2DetailCode = "B2" + // FA2DetailCodeB3 is Budget Program Activity + FA2DetailCodeB3 FA2DetailCode = "B3" + // FA2DetailCodeC1 is Program Element + FA2DetailCodeC1 FA2DetailCode = "C1" + // FA2DetailCodeC2 is Project Task or Budget Subline + FA2DetailCodeC2 FA2DetailCode = "C2" + // FA2DetailCodeD1 is Defense Agency Allocation Recipient + FA2DetailCodeD1 FA2DetailCode = "D1" + // FA2DetailCodeD4 is Component Sub-allocation Recipient + FA2DetailCodeD4 FA2DetailCode = "D4" + // FA2DetailCodeD6 is Sub-allotment Recipient + FA2DetailCodeD6 FA2DetailCode = "D6" + // FA2DetailCodeD7 is Work Center Recipient + FA2DetailCodeD7 FA2DetailCode = "D7" + // FA2DetailCodeE1 is Major Reimbursement Source Code + FA2DetailCodeE1 FA2DetailCode = "E1" + // FA2DetailCodeE2 is Detail Reimbursement Source Code + FA2DetailCodeE2 FA2DetailCode = "E2" + // FA2DetailCodeE3 is Customer Indicator + FA2DetailCodeE3 FA2DetailCode = "E3" + // FA2DetailCodeF1 is Object Class + FA2DetailCodeF1 FA2DetailCode = "F1" + // FA2DetailCodeF3 is Government or Public Sector Identifier + FA2DetailCodeF3 FA2DetailCode = "F3" + // FA2DetailCodeG2 is Special Interest Code or Special Program Cost Code + FA2DetailCodeG2 FA2DetailCode = "G2" + // FA2DetailCodeI1 is Abbreviated Department of Defense (DoD) Budget and Accounting Classification Code (BACC) + FA2DetailCodeI1 FA2DetailCode = "I1" + // FA2DetailCodeJ1 is Document or Record Reference Number + FA2DetailCodeJ1 FA2DetailCode = "J1" + // FA2DetailCodeK6 is Accounting Classification Reference Code + FA2DetailCodeK6 FA2DetailCode = "K6" + // FA2DetailCodeL1 is Accounting Installation Number + FA2DetailCodeL1 FA2DetailCode = "L1" + // FA2DetailCodeM1 is Local Installation Data + FA2DetailCodeM1 FA2DetailCode = "M1" + // FA2DetailCodeN1 is Transaction Type + FA2DetailCodeN1 FA2DetailCode = "N1" + // FA2DetailCodeP5 is Security Cooperation Case Line Item Identifier + FA2DetailCodeP5 FA2DetailCode = "P5" +) + // FA2 represents the FA2 EDI segment type FA2 struct { - BreakdownStructureDetailCode string `validate:"oneof=ZZ TA"` - FinancialInformationCode string `validate:"min=1,max=80"` + BreakdownStructureDetailCode FA2DetailCode `validate:"oneof=TA ZZ A1 A2 A3 A4 A5 A6 B1 B2 B3 C1 C2 D1 D4 D6 D7 E1 E2 E3 F1 F3 G2 I1 J1 K6 L1 M1 N1 P5"` + FinancialInformationCode string `validate:"min=1,max=80"` } // StringArray converts FA2 to an array of strings func (s *FA2) StringArray() []string { - return []string{"FA2", s.BreakdownStructureDetailCode, s.FinancialInformationCode} + return []string{"FA2", s.BreakdownStructureDetailCode.String(), s.FinancialInformationCode} } // Parse parses an X12 string that's split into an array into the FA2 struct @@ -22,7 +91,7 @@ func (s *FA2) Parse(elements []string) error { return fmt.Errorf("FA2: Wrong number of elements, expected %d, got %d", expectedNumElements, len(elements)) } - s.BreakdownStructureDetailCode = elements[0] + s.BreakdownStructureDetailCode = FA2DetailCode(elements[0]) s.FinancialInformationCode = elements[1] return nil } diff --git a/pkg/edi/segment/fa2_test.go b/pkg/edi/segment/fa2_test.go index b9deba6ef1a..d47d46b5b1c 100644 --- a/pkg/edi/segment/fa2_test.go +++ b/pkg/edi/segment/fa2_test.go @@ -2,7 +2,7 @@ package edisegment func (suite *SegmentSuite) TestValidateFA2() { validFA2 := FA2{ - BreakdownStructureDetailCode: "TA", + BreakdownStructureDetailCode: FA2DetailCodeTA, FinancialInformationCode: "307", } diff --git a/pkg/factory/line_of_accounting_factory.go b/pkg/factory/line_of_accounting_factory.go index da7c7b1236a..420903af461 100644 --- a/pkg/factory/line_of_accounting_factory.go +++ b/pkg/factory/line_of_accounting_factory.go @@ -77,7 +77,7 @@ func BuildFullLineOfAccounting(db *pop.Connection) models.LineOfAccounting { LoaHsGdsCd: models.StringPointer("12"), OrgGrpDfasCd: models.StringPointer("12"), LoaUic: models.StringPointer("123456"), - LoaTrnsnID: models.StringPointer("LoaTrnsnID"), + LoaTrnsnID: models.StringPointer("123"), LoaSubAcntID: models.StringPointer("123"), LoaBetCd: models.StringPointer("1234"), LoaFndTyFgCd: models.StringPointer("1"), diff --git a/pkg/factory/ppm_shipment_factory.go b/pkg/factory/ppm_shipment_factory.go index ec6964d6d48..d7d9fd2487d 100644 --- a/pkg/factory/ppm_shipment_factory.go +++ b/pkg/factory/ppm_shipment_factory.go @@ -424,11 +424,10 @@ func buildPPMShipmentReadyForFinalCustomerCloseOutWithCustoms(db *pop.Connection // PPMShipment that has customer documents and is ready for the // customer to sign and submit. // -// This function does not accept customizations to reduce the +// This function does not accept traits directly to reduce the // complexity of supporting different variations for tests -func BuildPPMShipmentReadyForFinalCustomerCloseOut(db *pop.Connection, userUploader *uploader.UserUploader) models.PPMShipment { - return buildPPMShipmentReadyForFinalCustomerCloseOutWithCustoms(db, userUploader, - nil) +func BuildPPMShipmentReadyForFinalCustomerCloseOut(db *pop.Connection, userUploader *uploader.UserUploader, customs []Customization) models.PPMShipment { + return buildPPMShipmentReadyForFinalCustomerCloseOutWithCustoms(db, userUploader, customs) } // BuildPPMShipmentReadyForFinalCustomerCloseOutWithAllDocTypes @@ -442,7 +441,7 @@ func BuildPPMShipmentReadyForFinalCustomerCloseOutWithAllDocTypes(db *pop.Connec // It's easier to use some of the data from other downstream // functions if we have them go first and then make our changes on // top of those changes. - ppmShipment := BuildPPMShipmentReadyForFinalCustomerCloseOut(db, userUploader) + ppmShipment := BuildPPMShipmentReadyForFinalCustomerCloseOut(db, userUploader, nil) AddProgearWeightTicketToPPMShipment(db, &ppmShipment, userUploader, nil) AddMovingExpenseToPPMShipment(db, &ppmShipment, userUploader, nil) diff --git a/pkg/factory/ppm_shipment_factory_test.go b/pkg/factory/ppm_shipment_factory_test.go index 6d8979cd85b..0dd41e25ed7 100644 --- a/pkg/factory/ppm_shipment_factory_test.go +++ b/pkg/factory/ppm_shipment_factory_test.go @@ -232,7 +232,7 @@ func (suite *FactorySuite) TestBuildPPMShipment() { // Expected outcome: New PPMShipment should be created with // Weight Ticket - ppmShipment := BuildPPMShipmentReadyForFinalCustomerCloseOut(suite.DB(), nil) + ppmShipment := BuildPPMShipmentReadyForFinalCustomerCloseOut(suite.DB(), nil, nil) suite.NotNil(ppmShipment.ActualPickupPostalCode) suite.Equal(ppmShipment.PickupPostalCode, *ppmShipment.ActualPickupPostalCode) @@ -255,7 +255,7 @@ func (suite *FactorySuite) TestBuildPPMShipment() { // Expected outcome: New PPMShipment should be created with // Weight Ticket - ppmShipment := BuildPPMShipmentReadyForFinalCustomerCloseOut(nil, nil) + ppmShipment := BuildPPMShipmentReadyForFinalCustomerCloseOut(nil, nil, nil) suite.False(ppmShipment.ID.IsNil()) suite.NotNil(ppmShipment.ActualPickupPostalCode) diff --git a/pkg/gen/primeapi/embedded_spec.go b/pkg/gen/primeapi/embedded_spec.go index bbc7e3e86e9..855e6b4e702 100644 --- a/pkg/gen/primeapi/embedded_spec.go +++ b/pkg/gen/primeapi/embedded_spec.go @@ -2314,6 +2314,12 @@ func init() { "sitAddressUpdates": { "$ref": "#/definitions/SitAddressUpdates" }, + "sitCustomerContacted": { + "description": "Date when the customer contacted the prime for a delivery out of SIT.", + "type": "string", + "format": "date", + "x-nullable": true + }, "sitDepartureDate": { "description": "Departure date for SIT. This is the end date of the SIT at either origin or destination. This is optional as it can be updated using the UpdateMTOServiceItemSIT modelType at a later date.", "type": "string", @@ -2328,6 +2334,12 @@ func init() { "type": "string", "format": "date" }, + "sitRequestedDelivery": { + "description": "Date when the customer has requested delivery out of SIT.", + "type": "string", + "format": "date", + "x-nullable": true + }, "timeMilitary1": { "description": "Time of attempted contact corresponding to ` + "`" + `dateOfContact1` + "`" + `, in military format.", "type": "string", @@ -4051,6 +4063,12 @@ func init() { "DOPSIT" ] }, + "sitCustomerContacted": { + "description": "Date when the customer contacted the prime for a delivery out of SIT.", + "type": "string", + "format": "date", + "x-nullable": true + }, "sitDepartureDate": { "description": "Departure date for SIT. This is the end date of the SIT at either origin or destination.", "type": "string", @@ -4059,6 +4077,12 @@ func init() { "sitDestinationFinalAddress": { "$ref": "#/definitions/Address" }, + "sitRequestedDelivery": { + "description": "Date when the customer has requested delivery out of SIT.", + "type": "string", + "format": "date", + "x-nullable": true + }, "timeMilitary1": { "description": "Time of attempted contact by the prime corresponding to 'dateOfContact1', in military format.", "type": "string", @@ -7252,6 +7276,12 @@ func init() { "sitAddressUpdates": { "$ref": "#/definitions/SitAddressUpdates" }, + "sitCustomerContacted": { + "description": "Date when the customer contacted the prime for a delivery out of SIT.", + "type": "string", + "format": "date", + "x-nullable": true + }, "sitDepartureDate": { "description": "Departure date for SIT. This is the end date of the SIT at either origin or destination. This is optional as it can be updated using the UpdateMTOServiceItemSIT modelType at a later date.", "type": "string", @@ -7266,6 +7296,12 @@ func init() { "type": "string", "format": "date" }, + "sitRequestedDelivery": { + "description": "Date when the customer has requested delivery out of SIT.", + "type": "string", + "format": "date", + "x-nullable": true + }, "timeMilitary1": { "description": "Time of attempted contact corresponding to ` + "`" + `dateOfContact1` + "`" + `, in military format.", "type": "string", @@ -8992,6 +9028,12 @@ func init() { "DOPSIT" ] }, + "sitCustomerContacted": { + "description": "Date when the customer contacted the prime for a delivery out of SIT.", + "type": "string", + "format": "date", + "x-nullable": true + }, "sitDepartureDate": { "description": "Departure date for SIT. This is the end date of the SIT at either origin or destination.", "type": "string", @@ -9000,6 +9042,12 @@ func init() { "sitDestinationFinalAddress": { "$ref": "#/definitions/Address" }, + "sitRequestedDelivery": { + "description": "Date when the customer has requested delivery out of SIT.", + "type": "string", + "format": "date", + "x-nullable": true + }, "timeMilitary1": { "description": "Time of attempted contact by the prime corresponding to 'dateOfContact1', in military format.", "type": "string", diff --git a/pkg/gen/primemessages/m_t_o_service_item_dest_s_i_t.go b/pkg/gen/primemessages/m_t_o_service_item_dest_s_i_t.go index 234e4038440..b62c90c425c 100644 --- a/pkg/gen/primemessages/m_t_o_service_item_dest_s_i_t.go +++ b/pkg/gen/primemessages/m_t_o_service_item_dest_s_i_t.go @@ -65,6 +65,10 @@ type MTOServiceItemDestSIT struct { // sit address updates SitAddressUpdates SitAddressUpdates `json:"sitAddressUpdates,omitempty"` + // Date when the customer contacted the prime for a delivery out of SIT. + // Format: date + SitCustomerContacted *strfmt.Date `json:"sitCustomerContacted,omitempty"` + // Departure date for SIT. This is the end date of the SIT at either origin or destination. This is optional as it can be updated using the UpdateMTOServiceItemSIT modelType at a later date. // Format: date SitDepartureDate *strfmt.Date `json:"sitDepartureDate,omitempty"` @@ -77,6 +81,10 @@ type MTOServiceItemDestSIT struct { // Format: date SitEntryDate *strfmt.Date `json:"sitEntryDate"` + // Date when the customer has requested delivery out of SIT. + // Format: date + SitRequestedDelivery *strfmt.Date `json:"sitRequestedDelivery,omitempty"` + // Time of attempted contact corresponding to `dateOfContact1`, in military format. // Example: 1400Z // Pattern: \d{4}Z @@ -210,6 +218,10 @@ func (m *MTOServiceItemDestSIT) UnmarshalJSON(raw []byte) error { // sit address updates SitAddressUpdates SitAddressUpdates `json:"sitAddressUpdates,omitempty"` + // Date when the customer contacted the prime for a delivery out of SIT. + // Format: date + SitCustomerContacted *strfmt.Date `json:"sitCustomerContacted,omitempty"` + // Departure date for SIT. This is the end date of the SIT at either origin or destination. This is optional as it can be updated using the UpdateMTOServiceItemSIT modelType at a later date. // Format: date SitDepartureDate *strfmt.Date `json:"sitDepartureDate,omitempty"` @@ -222,6 +234,10 @@ func (m *MTOServiceItemDestSIT) UnmarshalJSON(raw []byte) error { // Format: date SitEntryDate *strfmt.Date `json:"sitEntryDate"` + // Date when the customer has requested delivery out of SIT. + // Format: date + SitRequestedDelivery *strfmt.Date `json:"sitRequestedDelivery,omitempty"` + // Time of attempted contact corresponding to `dateOfContact1`, in military format. // Example: 1400Z // Pattern: \d{4}Z @@ -298,9 +314,11 @@ func (m *MTOServiceItemDestSIT) UnmarshalJSON(raw []byte) error { result.ReServiceCode = data.ReServiceCode result.Reason = data.Reason result.SitAddressUpdates = data.SitAddressUpdates + result.SitCustomerContacted = data.SitCustomerContacted result.SitDepartureDate = data.SitDepartureDate result.SitDestinationFinalAddress = data.SitDestinationFinalAddress result.SitEntryDate = data.SitEntryDate + result.SitRequestedDelivery = data.SitRequestedDelivery result.TimeMilitary1 = data.TimeMilitary1 result.TimeMilitary2 = data.TimeMilitary2 @@ -344,6 +362,10 @@ func (m MTOServiceItemDestSIT) MarshalJSON() ([]byte, error) { // sit address updates SitAddressUpdates SitAddressUpdates `json:"sitAddressUpdates,omitempty"` + // Date when the customer contacted the prime for a delivery out of SIT. + // Format: date + SitCustomerContacted *strfmt.Date `json:"sitCustomerContacted,omitempty"` + // Departure date for SIT. This is the end date of the SIT at either origin or destination. This is optional as it can be updated using the UpdateMTOServiceItemSIT modelType at a later date. // Format: date SitDepartureDate *strfmt.Date `json:"sitDepartureDate,omitempty"` @@ -356,6 +378,10 @@ func (m MTOServiceItemDestSIT) MarshalJSON() ([]byte, error) { // Format: date SitEntryDate *strfmt.Date `json:"sitEntryDate"` + // Date when the customer has requested delivery out of SIT. + // Format: date + SitRequestedDelivery *strfmt.Date `json:"sitRequestedDelivery,omitempty"` + // Time of attempted contact corresponding to `dateOfContact1`, in military format. // Example: 1400Z // Pattern: \d{4}Z @@ -381,12 +407,16 @@ func (m MTOServiceItemDestSIT) MarshalJSON() ([]byte, error) { SitAddressUpdates: m.SitAddressUpdates, + SitCustomerContacted: m.SitCustomerContacted, + SitDepartureDate: m.SitDepartureDate, SitDestinationFinalAddress: m.SitDestinationFinalAddress, SitEntryDate: m.SitEntryDate, + SitRequestedDelivery: m.SitRequestedDelivery, + TimeMilitary1: m.TimeMilitary1, TimeMilitary2: m.TimeMilitary2, @@ -491,6 +521,10 @@ func (m *MTOServiceItemDestSIT) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validateSitCustomerContacted(formats); err != nil { + res = append(res, err) + } + if err := m.validateSitDepartureDate(formats); err != nil { res = append(res, err) } @@ -503,6 +537,10 @@ func (m *MTOServiceItemDestSIT) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validateSitRequestedDelivery(formats); err != nil { + res = append(res, err) + } + if err := m.validateTimeMilitary1(formats); err != nil { res = append(res, err) } @@ -705,6 +743,19 @@ func (m *MTOServiceItemDestSIT) validateSitAddressUpdates(formats strfmt.Registr return nil } +func (m *MTOServiceItemDestSIT) validateSitCustomerContacted(formats strfmt.Registry) error { + + if swag.IsZero(m.SitCustomerContacted) { // not required + return nil + } + + if err := validate.FormatOf("sitCustomerContacted", "body", "date", m.SitCustomerContacted.String(), formats); err != nil { + return err + } + + return nil +} + func (m *MTOServiceItemDestSIT) validateSitDepartureDate(formats strfmt.Registry) error { if swag.IsZero(m.SitDepartureDate) { // not required @@ -751,6 +802,19 @@ func (m *MTOServiceItemDestSIT) validateSitEntryDate(formats strfmt.Registry) er return nil } +func (m *MTOServiceItemDestSIT) validateSitRequestedDelivery(formats strfmt.Registry) error { + + if swag.IsZero(m.SitRequestedDelivery) { // not required + return nil + } + + if err := validate.FormatOf("sitRequestedDelivery", "body", "date", m.SitRequestedDelivery.String(), formats); err != nil { + return err + } + + return nil +} + func (m *MTOServiceItemDestSIT) validateTimeMilitary1(formats strfmt.Registry) error { if swag.IsZero(m.TimeMilitary1) { // not required diff --git a/pkg/gen/primemessages/update_m_t_o_service_item_s_i_t.go b/pkg/gen/primemessages/update_m_t_o_service_item_s_i_t.go index 202bee5fc9c..5b2593c6a57 100644 --- a/pkg/gen/primemessages/update_m_t_o_service_item_s_i_t.go +++ b/pkg/gen/primemessages/update_m_t_o_service_item_s_i_t.go @@ -42,6 +42,10 @@ type UpdateMTOServiceItemSIT struct { // Enum: [DDDSIT DOPSIT] ReServiceCode string `json:"reServiceCode,omitempty"` + // Date when the customer contacted the prime for a delivery out of SIT. + // Format: date + SitCustomerContacted *strfmt.Date `json:"sitCustomerContacted,omitempty"` + // Departure date for SIT. This is the end date of the SIT at either origin or destination. // Format: date SitDepartureDate strfmt.Date `json:"sitDepartureDate,omitempty"` @@ -49,6 +53,10 @@ type UpdateMTOServiceItemSIT struct { // sit destination final address SitDestinationFinalAddress *Address `json:"sitDestinationFinalAddress,omitempty"` + // Date when the customer has requested delivery out of SIT. + // Format: date + SitRequestedDelivery *strfmt.Date `json:"sitRequestedDelivery,omitempty"` + // Time of attempted contact by the prime corresponding to 'dateOfContact1', in military format. // Example: 1400Z // Pattern: \d{4}Z @@ -103,6 +111,10 @@ func (m *UpdateMTOServiceItemSIT) UnmarshalJSON(raw []byte) error { // Enum: [DDDSIT DOPSIT] ReServiceCode string `json:"reServiceCode,omitempty"` + // Date when the customer contacted the prime for a delivery out of SIT. + // Format: date + SitCustomerContacted *strfmt.Date `json:"sitCustomerContacted,omitempty"` + // Departure date for SIT. This is the end date of the SIT at either origin or destination. // Format: date SitDepartureDate strfmt.Date `json:"sitDepartureDate,omitempty"` @@ -110,6 +122,10 @@ func (m *UpdateMTOServiceItemSIT) UnmarshalJSON(raw []byte) error { // sit destination final address SitDestinationFinalAddress *Address `json:"sitDestinationFinalAddress,omitempty"` + // Date when the customer has requested delivery out of SIT. + // Format: date + SitRequestedDelivery *strfmt.Date `json:"sitRequestedDelivery,omitempty"` + // Time of attempted contact by the prime corresponding to 'dateOfContact1', in military format. // Example: 1400Z // Pattern: \d{4}Z @@ -157,8 +173,10 @@ func (m *UpdateMTOServiceItemSIT) UnmarshalJSON(raw []byte) error { result.FirstAvailableDeliveryDate1 = data.FirstAvailableDeliveryDate1 result.FirstAvailableDeliveryDate2 = data.FirstAvailableDeliveryDate2 result.ReServiceCode = data.ReServiceCode + result.SitCustomerContacted = data.SitCustomerContacted result.SitDepartureDate = data.SitDepartureDate result.SitDestinationFinalAddress = data.SitDestinationFinalAddress + result.SitRequestedDelivery = data.SitRequestedDelivery result.TimeMilitary1 = data.TimeMilitary1 result.TimeMilitary2 = data.TimeMilitary2 @@ -193,6 +211,10 @@ func (m UpdateMTOServiceItemSIT) MarshalJSON() ([]byte, error) { // Enum: [DDDSIT DOPSIT] ReServiceCode string `json:"reServiceCode,omitempty"` + // Date when the customer contacted the prime for a delivery out of SIT. + // Format: date + SitCustomerContacted *strfmt.Date `json:"sitCustomerContacted,omitempty"` + // Departure date for SIT. This is the end date of the SIT at either origin or destination. // Format: date SitDepartureDate strfmt.Date `json:"sitDepartureDate,omitempty"` @@ -200,6 +222,10 @@ func (m UpdateMTOServiceItemSIT) MarshalJSON() ([]byte, error) { // sit destination final address SitDestinationFinalAddress *Address `json:"sitDestinationFinalAddress,omitempty"` + // Date when the customer has requested delivery out of SIT. + // Format: date + SitRequestedDelivery *strfmt.Date `json:"sitRequestedDelivery,omitempty"` + // Time of attempted contact by the prime corresponding to 'dateOfContact1', in military format. // Example: 1400Z // Pattern: \d{4}Z @@ -221,10 +247,14 @@ func (m UpdateMTOServiceItemSIT) MarshalJSON() ([]byte, error) { ReServiceCode: m.ReServiceCode, + SitCustomerContacted: m.SitCustomerContacted, + SitDepartureDate: m.SitDepartureDate, SitDestinationFinalAddress: m.SitDestinationFinalAddress, + SitRequestedDelivery: m.SitRequestedDelivery, + TimeMilitary1: m.TimeMilitary1, TimeMilitary2: m.TimeMilitary2, @@ -277,6 +307,10 @@ func (m *UpdateMTOServiceItemSIT) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validateSitCustomerContacted(formats); err != nil { + res = append(res, err) + } + if err := m.validateSitDepartureDate(formats); err != nil { res = append(res, err) } @@ -285,6 +319,10 @@ func (m *UpdateMTOServiceItemSIT) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validateSitRequestedDelivery(formats); err != nil { + res = append(res, err) + } + if err := m.validateTimeMilitary1(formats); err != nil { res = append(res, err) } @@ -398,6 +436,19 @@ func (m *UpdateMTOServiceItemSIT) validateReServiceCode(formats strfmt.Registry) return nil } +func (m *UpdateMTOServiceItemSIT) validateSitCustomerContacted(formats strfmt.Registry) error { + + if swag.IsZero(m.SitCustomerContacted) { // not required + return nil + } + + if err := validate.FormatOf("sitCustomerContacted", "body", "date", m.SitCustomerContacted.String(), formats); err != nil { + return err + } + + return nil +} + func (m *UpdateMTOServiceItemSIT) validateSitDepartureDate(formats strfmt.Registry) error { if swag.IsZero(m.SitDepartureDate) { // not required @@ -431,6 +482,19 @@ func (m *UpdateMTOServiceItemSIT) validateSitDestinationFinalAddress(formats str return nil } +func (m *UpdateMTOServiceItemSIT) validateSitRequestedDelivery(formats strfmt.Registry) error { + + if swag.IsZero(m.SitRequestedDelivery) { // not required + return nil + } + + if err := validate.FormatOf("sitRequestedDelivery", "body", "date", m.SitRequestedDelivery.String(), formats); err != nil { + return err + } + + return nil +} + func (m *UpdateMTOServiceItemSIT) validateTimeMilitary1(formats strfmt.Registry) error { if swag.IsZero(m.TimeMilitary1) { // not required diff --git a/pkg/gen/primev2api/embedded_spec.go b/pkg/gen/primev2api/embedded_spec.go index 49a56b44ef3..031ee49b4b8 100644 --- a/pkg/gen/primev2api/embedded_spec.go +++ b/pkg/gen/primev2api/embedded_spec.go @@ -642,6 +642,12 @@ func init() { "sitAddressUpdates": { "$ref": "#/definitions/SitAddressUpdates" }, + "sitCustomerContacted": { + "description": "Date when the customer contacted the prime for a delivery out of SIT.", + "type": "string", + "format": "date", + "x-nullable": true + }, "sitDepartureDate": { "description": "Departure date for SIT. This is the end date of the SIT at either origin or destination. This is optional as it can be updated using the UpdateMTOServiceItemSIT modelType at a later date.", "type": "string", @@ -656,6 +662,12 @@ func init() { "type": "string", "format": "date" }, + "sitRequestedDelivery": { + "description": "Date when the customer has requested delivery out of SIT.", + "type": "string", + "format": "date", + "x-nullable": true + }, "timeMilitary1": { "description": "Time of attempted contact corresponding to ` + "`" + `dateOfContact1` + "`" + `, in military format.", "type": "string", @@ -3006,6 +3018,12 @@ func init() { "sitAddressUpdates": { "$ref": "#/definitions/SitAddressUpdates" }, + "sitCustomerContacted": { + "description": "Date when the customer contacted the prime for a delivery out of SIT.", + "type": "string", + "format": "date", + "x-nullable": true + }, "sitDepartureDate": { "description": "Departure date for SIT. This is the end date of the SIT at either origin or destination. This is optional as it can be updated using the UpdateMTOServiceItemSIT modelType at a later date.", "type": "string", @@ -3020,6 +3038,12 @@ func init() { "type": "string", "format": "date" }, + "sitRequestedDelivery": { + "description": "Date when the customer has requested delivery out of SIT.", + "type": "string", + "format": "date", + "x-nullable": true + }, "timeMilitary1": { "description": "Time of attempted contact corresponding to ` + "`" + `dateOfContact1` + "`" + `, in military format.", "type": "string", diff --git a/pkg/gen/primev2messages/m_t_o_service_item_dest_s_i_t.go b/pkg/gen/primev2messages/m_t_o_service_item_dest_s_i_t.go index e703eba55c2..8c0302f30e3 100644 --- a/pkg/gen/primev2messages/m_t_o_service_item_dest_s_i_t.go +++ b/pkg/gen/primev2messages/m_t_o_service_item_dest_s_i_t.go @@ -65,6 +65,10 @@ type MTOServiceItemDestSIT struct { // sit address updates SitAddressUpdates SitAddressUpdates `json:"sitAddressUpdates,omitempty"` + // Date when the customer contacted the prime for a delivery out of SIT. + // Format: date + SitCustomerContacted *strfmt.Date `json:"sitCustomerContacted,omitempty"` + // Departure date for SIT. This is the end date of the SIT at either origin or destination. This is optional as it can be updated using the UpdateMTOServiceItemSIT modelType at a later date. // Format: date SitDepartureDate *strfmt.Date `json:"sitDepartureDate,omitempty"` @@ -77,6 +81,10 @@ type MTOServiceItemDestSIT struct { // Format: date SitEntryDate *strfmt.Date `json:"sitEntryDate"` + // Date when the customer has requested delivery out of SIT. + // Format: date + SitRequestedDelivery *strfmt.Date `json:"sitRequestedDelivery,omitempty"` + // Time of attempted contact corresponding to `dateOfContact1`, in military format. // Example: 1400Z // Pattern: \d{4}Z @@ -210,6 +218,10 @@ func (m *MTOServiceItemDestSIT) UnmarshalJSON(raw []byte) error { // sit address updates SitAddressUpdates SitAddressUpdates `json:"sitAddressUpdates,omitempty"` + // Date when the customer contacted the prime for a delivery out of SIT. + // Format: date + SitCustomerContacted *strfmt.Date `json:"sitCustomerContacted,omitempty"` + // Departure date for SIT. This is the end date of the SIT at either origin or destination. This is optional as it can be updated using the UpdateMTOServiceItemSIT modelType at a later date. // Format: date SitDepartureDate *strfmt.Date `json:"sitDepartureDate,omitempty"` @@ -222,6 +234,10 @@ func (m *MTOServiceItemDestSIT) UnmarshalJSON(raw []byte) error { // Format: date SitEntryDate *strfmt.Date `json:"sitEntryDate"` + // Date when the customer has requested delivery out of SIT. + // Format: date + SitRequestedDelivery *strfmt.Date `json:"sitRequestedDelivery,omitempty"` + // Time of attempted contact corresponding to `dateOfContact1`, in military format. // Example: 1400Z // Pattern: \d{4}Z @@ -298,9 +314,11 @@ func (m *MTOServiceItemDestSIT) UnmarshalJSON(raw []byte) error { result.ReServiceCode = data.ReServiceCode result.Reason = data.Reason result.SitAddressUpdates = data.SitAddressUpdates + result.SitCustomerContacted = data.SitCustomerContacted result.SitDepartureDate = data.SitDepartureDate result.SitDestinationFinalAddress = data.SitDestinationFinalAddress result.SitEntryDate = data.SitEntryDate + result.SitRequestedDelivery = data.SitRequestedDelivery result.TimeMilitary1 = data.TimeMilitary1 result.TimeMilitary2 = data.TimeMilitary2 @@ -344,6 +362,10 @@ func (m MTOServiceItemDestSIT) MarshalJSON() ([]byte, error) { // sit address updates SitAddressUpdates SitAddressUpdates `json:"sitAddressUpdates,omitempty"` + // Date when the customer contacted the prime for a delivery out of SIT. + // Format: date + SitCustomerContacted *strfmt.Date `json:"sitCustomerContacted,omitempty"` + // Departure date for SIT. This is the end date of the SIT at either origin or destination. This is optional as it can be updated using the UpdateMTOServiceItemSIT modelType at a later date. // Format: date SitDepartureDate *strfmt.Date `json:"sitDepartureDate,omitempty"` @@ -356,6 +378,10 @@ func (m MTOServiceItemDestSIT) MarshalJSON() ([]byte, error) { // Format: date SitEntryDate *strfmt.Date `json:"sitEntryDate"` + // Date when the customer has requested delivery out of SIT. + // Format: date + SitRequestedDelivery *strfmt.Date `json:"sitRequestedDelivery,omitempty"` + // Time of attempted contact corresponding to `dateOfContact1`, in military format. // Example: 1400Z // Pattern: \d{4}Z @@ -381,12 +407,16 @@ func (m MTOServiceItemDestSIT) MarshalJSON() ([]byte, error) { SitAddressUpdates: m.SitAddressUpdates, + SitCustomerContacted: m.SitCustomerContacted, + SitDepartureDate: m.SitDepartureDate, SitDestinationFinalAddress: m.SitDestinationFinalAddress, SitEntryDate: m.SitEntryDate, + SitRequestedDelivery: m.SitRequestedDelivery, + TimeMilitary1: m.TimeMilitary1, TimeMilitary2: m.TimeMilitary2, @@ -491,6 +521,10 @@ func (m *MTOServiceItemDestSIT) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validateSitCustomerContacted(formats); err != nil { + res = append(res, err) + } + if err := m.validateSitDepartureDate(formats); err != nil { res = append(res, err) } @@ -503,6 +537,10 @@ func (m *MTOServiceItemDestSIT) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validateSitRequestedDelivery(formats); err != nil { + res = append(res, err) + } + if err := m.validateTimeMilitary1(formats); err != nil { res = append(res, err) } @@ -705,6 +743,19 @@ func (m *MTOServiceItemDestSIT) validateSitAddressUpdates(formats strfmt.Registr return nil } +func (m *MTOServiceItemDestSIT) validateSitCustomerContacted(formats strfmt.Registry) error { + + if swag.IsZero(m.SitCustomerContacted) { // not required + return nil + } + + if err := validate.FormatOf("sitCustomerContacted", "body", "date", m.SitCustomerContacted.String(), formats); err != nil { + return err + } + + return nil +} + func (m *MTOServiceItemDestSIT) validateSitDepartureDate(formats strfmt.Registry) error { if swag.IsZero(m.SitDepartureDate) { // not required @@ -751,6 +802,19 @@ func (m *MTOServiceItemDestSIT) validateSitEntryDate(formats strfmt.Registry) er return nil } +func (m *MTOServiceItemDestSIT) validateSitRequestedDelivery(formats strfmt.Registry) error { + + if swag.IsZero(m.SitRequestedDelivery) { // not required + return nil + } + + if err := validate.FormatOf("sitRequestedDelivery", "body", "date", m.SitRequestedDelivery.String(), formats); err != nil { + return err + } + + return nil +} + func (m *MTOServiceItemDestSIT) validateTimeMilitary1(formats strfmt.Registry) error { if swag.IsZero(m.TimeMilitary1) { // not required diff --git a/pkg/handlers/internalapi/api.go b/pkg/handlers/internalapi/api.go index f94f09725e7..ecd5ba46a34 100644 --- a/pkg/handlers/internalapi/api.go +++ b/pkg/handlers/internalapi/api.go @@ -55,7 +55,10 @@ func NewInternalAPI(handlerConfig handlers.HandlerConfig) *internalops.MymoveAPI transportationOfficeFetcher := transportationoffice.NewTransportationOfficesFetcher() closeoutOfficeUpdater := move.NewCloseoutOfficeUpdater(move.NewMoveFetcher(), transportationOfficeFetcher) + ppmShipmentFetcher := ppmshipment.NewPPMShipmentFetcher() + internalAPI.FeatureFlagsFeatureFlagForUserHandler = FeatureFlagsForUserHandler{handlerConfig} + internalAPI.UsersShowLoggedInUserHandler = ShowLoggedInUserHandler{handlerConfig, officeuser.NewOfficeUserFetcherPop()} internalAPI.CertificationCreateSignedCertificationHandler = CreateSignedCertificationHandler{handlerConfig} internalAPI.CertificationIndexSignedCertificationHandler = IndexSignedCertificationsHandler{handlerConfig} @@ -193,7 +196,7 @@ func NewInternalAPI(handlerConfig handlers.HandlerConfig) *internalops.MymoveAPI internalAPI.PpmCreatePPMUploadHandler = CreatePPMUploadHandler{handlerConfig} - ppmShipmentNewSubmitter := ppmshipment.NewPPMShipmentNewSubmitter(signedCertificationCreator, ppmShipmentRouter) + ppmShipmentNewSubmitter := ppmshipment.NewPPMShipmentNewSubmitter(ppmShipmentFetcher, signedCertificationCreator, ppmShipmentRouter) internalAPI.PpmSubmitPPMShipmentDocumentationHandler = SubmitPPMShipmentDocumentationHandler{handlerConfig, ppmShipmentNewSubmitter} diff --git a/pkg/handlers/internalapi/ppm_shipment.go b/pkg/handlers/internalapi/ppm_shipment.go index d14d890225f..182f58b7af2 100644 --- a/pkg/handlers/internalapi/ppm_shipment.go +++ b/pkg/handlers/internalapi/ppm_shipment.go @@ -24,14 +24,6 @@ type SubmitPPMShipmentDocumentationHandler struct { func (h SubmitPPMShipmentDocumentationHandler) Handle(params ppmops.SubmitPPMShipmentDocumentationParams) middleware.Responder { return h.AuditableAppContextFromRequestWithErrors(params.HTTPRequest, func(appCtx appcontext.AppContext) (middleware.Responder, error) { - if appCtx.Session() == nil { - return ppmops.NewSubmitPPMShipmentDocumentationUnauthorized(), apperror.NewSessionError("No user session") - } else if !appCtx.Session().IsMilApp() { - return ppmops.NewSubmitPPMShipmentDocumentationForbidden(), apperror.NewSessionError("Request is not from the customer app") - } else if appCtx.Session().UserID.IsNil() { - return ppmops.NewSubmitPPMShipmentDocumentationForbidden(), apperror.NewSessionError("No user ID in session") - } - ppmShipmentID, err := uuid.FromString(params.PpmShipmentID.String()) if err != nil || ppmShipmentID.IsNil() { appCtx.Logger().Error("error with PPM Shipment ID", zap.Error(err)) diff --git a/pkg/handlers/internalapi/ppm_shipment_test.go b/pkg/handlers/internalapi/ppm_shipment_test.go index 10008cc3b4e..3bb84229ab9 100644 --- a/pkg/handlers/internalapi/ppm_shipment_test.go +++ b/pkg/handlers/internalapi/ppm_shipment_test.go @@ -29,7 +29,7 @@ import ( func (suite *HandlerSuite) TestSubmitPPMShipmentDocumentationHandlerUnit() { setUpPPMShipment := func() models.PPMShipment { - ppmShipment := factory.BuildPPMShipmentReadyForFinalCustomerCloseOut(nil, nil) + ppmShipment := factory.BuildPPMShipmentReadyForFinalCustomerCloseOut(nil, nil, nil) ppmShipment.ID = uuid.Must(uuid.NewV4()) ppmShipment.CreatedAt = time.Now() @@ -39,18 +39,20 @@ func (suite *HandlerSuite) TestSubmitPPMShipmentDocumentationHandlerUnit() { return ppmShipment } - setUpRequestAndParams := func(ppmShipment models.PPMShipment, authUser bool, setUpPayload bool) (*http.Request, ppmops.SubmitPPMShipmentDocumentationParams) { - endpoint := fmt.Sprintf("/ppm-shipments/%s/submit-ppm-shipment-documentation", ppmShipment.ID.String()) + setUpRequestAndParams := func( + ppmShipmentID uuid.UUID, + serviceMemberToAuth models.ServiceMember, + setUpPayload bool, + ) (*http.Request, ppmops.SubmitPPMShipmentDocumentationParams) { + endpoint := fmt.Sprintf("/ppm-shipments/%s/submit-ppm-shipment-documentation", ppmShipmentID.String()) request := httptest.NewRequest("POST", endpoint, nil) - if authUser { - request = suite.AuthenticateRequest(request, ppmShipment.Shipment.MoveTaskOrder.Orders.ServiceMember) - } + request = suite.AuthenticateRequest(request, serviceMemberToAuth) params := ppmops.SubmitPPMShipmentDocumentationParams{ HTTPRequest: request, - PpmShipmentID: handlers.FmtUUIDValue(ppmShipment.ID), + PpmShipmentID: handlers.FmtUUIDValue(ppmShipmentID), SavePPMShipmentSignedCertificationPayload: nil, } @@ -88,52 +90,10 @@ func (suite *HandlerSuite) TestSubmitPPMShipmentDocumentationHandlerUnit() { } } - suite.Run("Returns an error if there is no session information", func() { - ppmShipment := setUpPPMShipment() - - _, params := setUpRequestAndParams(ppmShipment, false, false) - - handler := setUpHandler(setUpPPMShipmentNewSubmitter(nil, nil)) - - response := handler.Handle(params) - - suite.IsType(&ppmops.SubmitPPMShipmentDocumentationUnauthorized{}, response) - }) - - suite.Run("Returns an error if the request isn't coming from the correct app", func() { - ppmShipment := setUpPPMShipment() - - request, params := setUpRequestAndParams(ppmShipment, false, false) - - officeUser := factory.BuildOfficeUser(nil, nil, nil) - request = suite.AuthenticateOfficeRequest(request, officeUser) - params.HTTPRequest = request - - handler := setUpHandler(setUpPPMShipmentNewSubmitter(nil, nil)) - - response := handler.Handle(params) - - suite.IsType(&ppmops.SubmitPPMShipmentDocumentationForbidden{}, response) - }) - - suite.Run("Returns an error if the user ID is missing from the session", func() { - ppmShipment := setUpPPMShipment() - ppmShipment.Shipment.MoveTaskOrder.Orders.ServiceMember.UserID = uuid.Nil - - _, params := setUpRequestAndParams(ppmShipment, true, false) - - handler := setUpHandler(setUpPPMShipmentNewSubmitter(nil, nil)) - - response := handler.Handle(params) - - suite.IsType(&ppmops.SubmitPPMShipmentDocumentationForbidden{}, response) - }) - suite.Run("Returns an error if the PPMShipment ID in the url is invalid", func() { ppmShipment := setUpPPMShipment() - ppmShipment.ID = uuid.Nil - _, params := setUpRequestAndParams(ppmShipment, true, false) + _, params := setUpRequestAndParams(uuid.Nil, ppmShipment.Shipment.MoveTaskOrder.Orders.ServiceMember, false) handler := setUpHandler(setUpPPMShipmentNewSubmitter(nil, nil)) @@ -149,7 +109,7 @@ func (suite *HandlerSuite) TestSubmitPPMShipmentDocumentationHandlerUnit() { suite.Run("Returns an error if there is no request body", func() { ppmShipment := setUpPPMShipment() - _, params := setUpRequestAndParams(ppmShipment, true, false) + _, params := setUpRequestAndParams(ppmShipment.ID, ppmShipment.Shipment.MoveTaskOrder.Orders.ServiceMember, false) handler := setUpHandler(setUpPPMShipmentNewSubmitter(nil, nil)) @@ -165,12 +125,14 @@ func (suite *HandlerSuite) TestSubmitPPMShipmentDocumentationHandlerUnit() { suite.Run("Returns an error if the submitter service returns a BadDataError", func() { ppmShipment := setUpPPMShipment() - _, params := setUpRequestAndParams(ppmShipment, true, true) + _, params := setUpRequestAndParams(ppmShipment.ID, ppmShipment.Shipment.MoveTaskOrder.Orders.ServiceMember, true) err := apperror.NewBadDataError("Bad data") handler := setUpHandler(setUpPPMShipmentNewSubmitter(nil, err)) + suite.NoError(params.SavePPMShipmentSignedCertificationPayload.Validate(strfmt.Default)) + response := handler.Handle(params) if suite.IsType(&ppmops.SubmitPPMShipmentDocumentationBadRequest{}, response) { @@ -183,12 +145,14 @@ func (suite *HandlerSuite) TestSubmitPPMShipmentDocumentationHandlerUnit() { suite.Run("Returns an error if the submitter service returns a NotFoundError", func() { ppmShipment := setUpPPMShipment() - _, params := setUpRequestAndParams(ppmShipment, true, true) + _, params := setUpRequestAndParams(ppmShipment.ID, ppmShipment.Shipment.MoveTaskOrder.Orders.ServiceMember, true) err := apperror.NewNotFoundError(ppmShipment.ID, "Can't find PPM shipment") handler := setUpHandler(setUpPPMShipmentNewSubmitter(nil, err)) + suite.NoError(params.SavePPMShipmentSignedCertificationPayload.Validate(strfmt.Default)) + response := handler.Handle(params) if suite.IsType(&ppmops.SubmitPPMShipmentDocumentationNotFound{}, response) { @@ -201,12 +165,14 @@ func (suite *HandlerSuite) TestSubmitPPMShipmentDocumentationHandlerUnit() { suite.Run("Returns an error if the submitter service returns a QueryError", func() { ppmShipment := setUpPPMShipment() - _, params := setUpRequestAndParams(ppmShipment, true, true) + _, params := setUpRequestAndParams(ppmShipment.ID, ppmShipment.Shipment.MoveTaskOrder.Orders.ServiceMember, true) err := apperror.NewQueryError("PPMShipment", nil, "Error getting PPM shipment") handler := setUpHandler(setUpPPMShipmentNewSubmitter(nil, err)) + suite.NoError(params.SavePPMShipmentSignedCertificationPayload.Validate(strfmt.Default)) + response := handler.Handle(params) suite.IsType(&ppmops.SubmitPPMShipmentDocumentationInternalServerError{}, response) @@ -215,16 +181,18 @@ func (suite *HandlerSuite) TestSubmitPPMShipmentDocumentationHandlerUnit() { suite.Run("Returns an error if the submitter service returns a InvalidInputError", func() { ppmShipment := setUpPPMShipment() - _, params := setUpRequestAndParams(ppmShipment, true, true) + _, params := setUpRequestAndParams(ppmShipment.ID, ppmShipment.Shipment.MoveTaskOrder.Orders.ServiceMember, true) verrs := validate.NewErrors() fieldWithErr := "field" fieldErrorMsg := "Field error" verrs.Add(fieldWithErr, fieldErrorMsg) - err := apperror.NewInvalidInputError(ppmShipment.ID, nil, verrs, "Invalid input") + fakeErr := apperror.NewInvalidInputError(ppmShipment.ID, nil, verrs, "Invalid input") - handler := setUpHandler(setUpPPMShipmentNewSubmitter(nil, err)) + handler := setUpHandler(setUpPPMShipmentNewSubmitter(nil, fakeErr)) + + suite.NoError(params.SavePPMShipmentSignedCertificationPayload.Validate(strfmt.Default)) response := handler.Handle(params) @@ -242,12 +210,14 @@ func (suite *HandlerSuite) TestSubmitPPMShipmentDocumentationHandlerUnit() { suite.Run("Returns an error if the submitter service returns a ConflictError", func() { ppmShipment := setUpPPMShipment() - _, params := setUpRequestAndParams(ppmShipment, true, true) + _, params := setUpRequestAndParams(ppmShipment.ID, ppmShipment.Shipment.MoveTaskOrder.Orders.ServiceMember, true) err := apperror.NewConflictError(ppmShipment.ID, "Can't route PPM shipment") handler := setUpHandler(setUpPPMShipmentNewSubmitter(nil, err)) + suite.NoError(params.SavePPMShipmentSignedCertificationPayload.Validate(strfmt.Default)) + response := handler.Handle(params) if suite.IsType(&ppmops.SubmitPPMShipmentDocumentationConflict{}, response) { @@ -260,12 +230,14 @@ func (suite *HandlerSuite) TestSubmitPPMShipmentDocumentationHandlerUnit() { suite.Run("Returns an error if the submitter service returns an unexpected error", func() { ppmShipment := setUpPPMShipment() - _, params := setUpRequestAndParams(ppmShipment, true, true) + _, params := setUpRequestAndParams(ppmShipment.ID, ppmShipment.Shipment.MoveTaskOrder.Orders.ServiceMember, true) err := apperror.NewNotImplementedError("Not implemented") handler := setUpHandler(setUpPPMShipmentNewSubmitter(nil, err)) + suite.NoError(params.SavePPMShipmentSignedCertificationPayload.Validate(strfmt.Default)) + response := handler.Handle(params) suite.IsType(&ppmops.SubmitPPMShipmentDocumentationInternalServerError{}, response) @@ -274,7 +246,7 @@ func (suite *HandlerSuite) TestSubmitPPMShipmentDocumentationHandlerUnit() { suite.Run("Returns the PPM shipment if all goes well", func() { ppmShipment := setUpPPMShipment() - _, params := setUpRequestAndParams(ppmShipment, true, true) + _, params := setUpRequestAndParams(ppmShipment.ID, ppmShipment.Shipment.MoveTaskOrder.Orders.ServiceMember, true) expectedPPMShipment := ppmShipment expectedPPMShipment.Status = models.PPMShipmentStatusNeedsPaymentApproval @@ -300,6 +272,8 @@ func (suite *HandlerSuite) TestSubmitPPMShipmentDocumentationHandlerUnit() { handler := setUpHandler(setUpPPMShipmentNewSubmitter(&expectedPPMShipment, nil)) + suite.NoError(params.SavePPMShipmentSignedCertificationPayload.Validate(strfmt.Default)) + response := handler.Handle(params) if suite.IsType(&ppmops.SubmitPPMShipmentDocumentationOK{}, response) { @@ -315,22 +289,27 @@ func (suite *HandlerSuite) TestSubmitPPMShipmentDocumentationHandlerUnit() { } func (suite *HandlerSuite) TestSubmitPPMShipmentDocumentationHandlerIntegration() { + ppmShipmentFetcher := ppmshipment.NewPPMShipmentFetcher() signedCertificationCreator := signedcertification.NewSignedCertificationCreator() mtoShipmentRouter := mtoshipment.NewShipmentRouter() ppmShipmentRouter := ppmshipment.NewPPMShipmentRouter(mtoShipmentRouter) - submitter := ppmshipment.NewPPMShipmentNewSubmitter(signedCertificationCreator, ppmShipmentRouter) + submitter := ppmshipment.NewPPMShipmentNewSubmitter(ppmShipmentFetcher, signedCertificationCreator, ppmShipmentRouter) - setUpParamsAndHandler := func(ppmShipment models.PPMShipment, payload *internalmessages.SavePPMShipmentSignedCertification) (ppmops.SubmitPPMShipmentDocumentationParams, SubmitPPMShipmentDocumentationHandler) { - endpoint := fmt.Sprintf("/ppm-shipments/%s/submit-ppm-shipment-documentation", ppmShipment.ID.String()) + setUpParamsAndHandler := func( + ppmShipmentID uuid.UUID, + serviceMemberToAuth models.ServiceMember, + payload *internalmessages.SavePPMShipmentSignedCertification, + ) (ppmops.SubmitPPMShipmentDocumentationParams, SubmitPPMShipmentDocumentationHandler) { + endpoint := fmt.Sprintf("/ppm-shipments/%s/submit-ppm-shipment-documentation", ppmShipmentID.String()) request := httptest.NewRequest("POST", endpoint, nil) - request = suite.AuthenticateRequest(request, ppmShipment.Shipment.MoveTaskOrder.Orders.ServiceMember) + request = suite.AuthenticateRequest(request, serviceMemberToAuth) params := ppmops.SubmitPPMShipmentDocumentationParams{ HTTPRequest: request, - PpmShipmentID: handlers.FmtUUIDValue(ppmShipment.ID), + PpmShipmentID: handlers.FmtUUIDValue(ppmShipmentID), SavePPMShipmentSignedCertificationPayload: payload, } @@ -343,18 +322,43 @@ func (suite *HandlerSuite) TestSubmitPPMShipmentDocumentationHandlerIntegration( } suite.Run("Returns an error if the PPM shipment is not found", func() { - ppmShipment := factory.BuildPPMShipmentReadyForFinalCustomerCloseOut(suite.DB(), nil) + ppmShipment := factory.BuildPPMShipmentReadyForFinalCustomerCloseOut(suite.DB(), nil, nil) - ppmShipment.ID = uuid.Must(uuid.NewV4()) + params, handler := setUpParamsAndHandler( + uuid.Must(uuid.NewV4()), + ppmShipment.Shipment.MoveTaskOrder.Orders.ServiceMember, + &internalmessages.SavePPMShipmentSignedCertification{ + CertificationText: handlers.FmtString("certification text"), + Date: handlers.FmtDate(time.Now()), + Signature: handlers.FmtString("signature"), + }) - params, handler := setUpParamsAndHandler(ppmShipment, &internalmessages.SavePPMShipmentSignedCertification{ - CertificationText: handlers.FmtString("certification text"), - Date: handlers.FmtDate(time.Now()), - Signature: handlers.FmtString("signature"), - }) + suite.NoError(params.SavePPMShipmentSignedCertificationPayload.Validate(strfmt.Default)) - err := params.SavePPMShipmentSignedCertificationPayload.Validate(strfmt.Default) - suite.NoError(err) + response := handler.Handle(params) + + if suite.IsType(&ppmops.SubmitPPMShipmentDocumentationNotFound{}, response) { + errResponse := response.(*ppmops.SubmitPPMShipmentDocumentationNotFound) + + suite.Contains(*errResponse.Payload.Detail, "not found while looking for PPMShipment") + } + }) + + suite.Run("Returns an error if the PPM shipment belongs to a different service member", func() { + ppmShipment := factory.BuildPPMShipmentReadyForFinalCustomerCloseOut(suite.DB(), nil, nil) + + otherServiceMember := factory.BuildExtendedServiceMember(suite.DB(), factory.GetTraitActiveServiceMemberUser(), nil) + + params, handler := setUpParamsAndHandler( + ppmShipment.ID, + otherServiceMember, + &internalmessages.SavePPMShipmentSignedCertification{ + CertificationText: handlers.FmtString("certification text"), + Date: handlers.FmtDate(time.Now()), + Signature: handlers.FmtString("signature"), + }) + + suite.NoError(params.SavePPMShipmentSignedCertificationPayload.Validate(strfmt.Default)) response := handler.Handle(params) @@ -366,12 +370,15 @@ func (suite *HandlerSuite) TestSubmitPPMShipmentDocumentationHandlerIntegration( }) suite.Run("Returns an error if the SignedCertification has any errors", func() { - ppmShipment := factory.BuildPPMShipmentReadyForFinalCustomerCloseOut(suite.DB(), nil) + ppmShipment := factory.BuildPPMShipmentReadyForFinalCustomerCloseOut(suite.DB(), nil, nil) - params, handler := setUpParamsAndHandler(ppmShipment, &internalmessages.SavePPMShipmentSignedCertification{ - CertificationText: handlers.FmtString("certification text"), - Signature: handlers.FmtString("signature"), - }) + params, handler := setUpParamsAndHandler( + ppmShipment.ID, + ppmShipment.Shipment.MoveTaskOrder.Orders.ServiceMember, + &internalmessages.SavePPMShipmentSignedCertification{ + CertificationText: handlers.FmtString("certification text"), + Signature: handlers.FmtString("signature"), + }) err := params.SavePPMShipmentSignedCertificationPayload.Validate(strfmt.Default) suite.Error(err) @@ -393,11 +400,16 @@ func (suite *HandlerSuite) TestSubmitPPMShipmentDocumentationHandlerIntegration( suite.Run("Returns an error if the PPM shipment is not in the right status", func() { ppmShipment := factory.BuildPPMShipmentThatNeedsPaymentApproval(suite.DB(), nil, nil) - params, handler := setUpParamsAndHandler(ppmShipment, &internalmessages.SavePPMShipmentSignedCertification{ - CertificationText: handlers.FmtString("certification text"), - Signature: handlers.FmtString("signature"), - Date: handlers.FmtDate(time.Now()), - }) + params, handler := setUpParamsAndHandler( + ppmShipment.ID, + ppmShipment.Shipment.MoveTaskOrder.Orders.ServiceMember, + &internalmessages.SavePPMShipmentSignedCertification{ + CertificationText: handlers.FmtString("certification text"), + Signature: handlers.FmtString("signature"), + Date: handlers.FmtDate(time.Now()), + }) + + suite.NoError(params.SavePPMShipmentSignedCertificationPayload.Validate(strfmt.Default)) response := handler.Handle(params) @@ -416,17 +428,22 @@ func (suite *HandlerSuite) TestSubmitPPMShipmentDocumentationHandlerIntegration( }) suite.Run("Can successfully submit a PPM shipment for close out", func() { - ppmShipment := factory.BuildPPMShipmentReadyForFinalCustomerCloseOut(suite.DB(), nil) + ppmShipment := factory.BuildPPMShipmentReadyForFinalCustomerCloseOut(suite.DB(), nil, nil) certText := "certification text" signature := "signature" signDate := time.Now() - params, handler := setUpParamsAndHandler(ppmShipment, &internalmessages.SavePPMShipmentSignedCertification{ - CertificationText: handlers.FmtString(certText), - Signature: handlers.FmtString(signature), - Date: handlers.FmtDate(signDate), - }) + params, handler := setUpParamsAndHandler( + ppmShipment.ID, + ppmShipment.Shipment.MoveTaskOrder.Orders.ServiceMember, + &internalmessages.SavePPMShipmentSignedCertification{ + CertificationText: handlers.FmtString(certText), + Signature: handlers.FmtString(signature), + Date: handlers.FmtDate(signDate), + }) + + suite.NoError(params.SavePPMShipmentSignedCertificationPayload.Validate(strfmt.Default)) response := handler.Handle(params) diff --git a/pkg/handlers/internalapi/weight_ticket_test.go b/pkg/handlers/internalapi/weight_ticket_test.go index 3b9e54ab29d..63bc11d6fa3 100644 --- a/pkg/handlers/internalapi/weight_ticket_test.go +++ b/pkg/handlers/internalapi/weight_ticket_test.go @@ -13,7 +13,6 @@ import ( "github.com/transcom/mymove/pkg/gen/internalmessages" "github.com/transcom/mymove/pkg/handlers" "github.com/transcom/mymove/pkg/models" - "github.com/transcom/mymove/pkg/models/roles" "github.com/transcom/mymove/pkg/services/mocks" weightticket "github.com/transcom/mymove/pkg/services/weight_ticket" "github.com/transcom/mymove/pkg/testdatagen" @@ -86,19 +85,19 @@ func (suite *HandlerSuite) TestCreateWeightTicketHandler() { suite.IsType(&weightticketops.CreateWeightTicketUnauthorized{}, response) }) - suite.Run("POST failure - 403- permission denied - wrong application", func() { + suite.Run("POST failure - 404- not found - wrong service member", func() { subtestData := makeCreateSubtestData(false) - officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) + unauthorizedUser := factory.BuildServiceMember(suite.DB(), nil, nil) req := subtestData.params.HTTPRequest - unauthorizedReq := suite.AuthenticateOfficeRequest(req, officeUser) + unauthorizedReq := suite.AuthenticateRequest(req, unauthorizedUser) unauthorizedParams := subtestData.params unauthorizedParams.HTTPRequest = unauthorizedReq response := subtestData.handler.Handle(unauthorizedParams) - suite.IsType(&weightticketops.CreateWeightTicketForbidden{}, response) + suite.IsType(&weightticketops.CreateWeightTicketNotFound{}, response) }) suite.Run("Post failure - 500 - Server Error", func() { diff --git a/pkg/handlers/primeapi/move_task_order_test.go b/pkg/handlers/primeapi/move_task_order_test.go index e0c82edf9c9..a7571170800 100644 --- a/pkg/handlers/primeapi/move_task_order_test.go +++ b/pkg/handlers/primeapi/move_task_order_test.go @@ -1131,12 +1131,14 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { serviceItem := factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ { Model: models.MTOServiceItem{ - RejectionReason: models.StringPointer("not applicable"), - MTOShipmentID: &successShipment.ID, - Reason: models.StringPointer("there was a delay in getting the apartment"), - SITEntryDate: &nowDate, - SITDepartureDate: &later, - CustomerContacts: models.MTOServiceItemCustomerContacts{contact1, contact2}, + RejectionReason: models.StringPointer("not applicable"), + MTOShipmentID: &successShipment.ID, + Reason: models.StringPointer("there was a delay in getting the apartment"), + SITEntryDate: &nowDate, + SITDepartureDate: &later, + CustomerContacts: models.MTOServiceItemCustomerContacts{contact1, contact2}, + SITCustomerContacted: &nowDate, + SITRequestedDelivery: &nowDate, }, }, { @@ -1197,6 +1199,8 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { suite.Equal(*serviceItem.Reason, *payload.Reason) suite.Equal(serviceItem.SITEntryDate.Format(time.RFC3339), handlers.FmtDatePtrToPop(payload.SitEntryDate).Format(time.RFC3339)) suite.Equal(serviceItem.SITDepartureDate.Format(time.RFC3339), handlers.FmtDatePtrToPop(payload.SitDepartureDate).Format(time.RFC3339)) + suite.Equal(serviceItem.SITCustomerContacted.Format(time.RFC3339), handlers.FmtDatePtrToPop(payload.SitCustomerContacted).Format(time.RFC3339)) + suite.Equal(serviceItem.SITRequestedDelivery.Format(time.RFC3339), handlers.FmtDatePtrToPop(payload.SitRequestedDelivery).Format(time.RFC3339)) suite.Equal(serviceItem.CustomerContacts[0].DateOfContact.Format(time.RFC3339), handlers.FmtDatePtrToPop(payload.DateOfContact1).Format(time.RFC3339)) suite.Equal(serviceItem.CustomerContacts[0].TimeMilitary, *payload.TimeMilitary1) suite.Equal(serviceItem.CustomerContacts[0].FirstAvailableDeliveryDate.Format(time.RFC3339), handlers.FmtDatePtrToPop(payload.FirstAvailableDeliveryDate1).Format(time.RFC3339)) diff --git a/pkg/handlers/primeapi/mto_service_item_test.go b/pkg/handlers/primeapi/mto_service_item_test.go index bf2fe105dd4..9283b27c9e5 100644 --- a/pkg/handlers/primeapi/mto_service_item_test.go +++ b/pkg/handlers/primeapi/mto_service_item_test.go @@ -1344,6 +1344,8 @@ func (suite *HandlerSuite) TestUpdateMTOServiceItemDDDSIT() { DateOfContact1: handlers.FmtDate(time.Date(2020, time.December, 04, 0, 0, 0, 0, time.UTC)), TimeMilitary1: handlers.FmtStringPtrNonEmpty(&milTime), FirstAvailableDeliveryDate1: handlers.FmtDate(time.Date(2020, time.December, 02, 0, 0, 0, 0, time.UTC)), + SitCustomerContacted: handlers.FmtDate(time.Now()), + SitRequestedDelivery: handlers.FmtDate(time.Now().AddDate(0, 0, 3)), } subtestData.reqPayload.SetID(strfmt.UUID(subtestData.dddsit.ID.String())) @@ -1369,7 +1371,7 @@ func (suite *HandlerSuite) TestUpdateMTOServiceItemDDDSIT() { return subtestData } - suite.Run("Successful PATCH - Updated SITDepartureDate and FADD Fields on DDDSIT", func() { + suite.Run("Successful PATCH - Updated SITDepartureDate and FADD Fields and other date fields on DDDSIT", func() { subtestData := makeSubtestData() // Under test: updateMTOServiceItemHandler.Handle function // MTOServiceItemUpdater.Update service object function @@ -1408,6 +1410,8 @@ func (suite *HandlerSuite) TestUpdateMTOServiceItemDDDSIT() { suite.Equal(subtestData.reqPayload.DateOfContact1, respPayload.DateOfContact1) suite.Equal(subtestData.reqPayload.TimeMilitary1, respPayload.TimeMilitary1) suite.Equal(subtestData.reqPayload.FirstAvailableDeliveryDate1, respPayload.FirstAvailableDeliveryDate1) + suite.Equal(*subtestData.reqPayload.SitCustomerContacted, *respPayload.SitCustomerContacted) + suite.Equal(*subtestData.reqPayload.SitRequestedDelivery, *respPayload.SitRequestedDelivery) }) suite.Run("Failed PATCH - No DDDSIT found", func() { diff --git a/pkg/handlers/primeapi/payloads/model_to_payload.go b/pkg/handlers/primeapi/payloads/model_to_payload.go index a43d69887d9..3e8fc0adec9 100644 --- a/pkg/handlers/primeapi/payloads/model_to_payload.go +++ b/pkg/handlers/primeapi/payloads/model_to_payload.go @@ -624,6 +624,8 @@ func MTOServiceItem(mtoServiceItem *models.MTOServiceItem) primemessages.MTOServ SitEntryDate: handlers.FmtDatePtr(mtoServiceItem.SITEntryDate), SitDestinationFinalAddress: Address(mtoServiceItem.SITDestinationFinalAddress), SitAddressUpdates: SITAddressUpdates(mtoServiceItem.SITAddressUpdates), + SitCustomerContacted: handlers.FmtDatePtr(mtoServiceItem.SITCustomerContacted), + SitRequestedDelivery: handlers.FmtDatePtr(mtoServiceItem.SITRequestedDelivery), } case models.ReServiceCodeDCRT, models.ReServiceCodeDUCRT: diff --git a/pkg/handlers/primeapi/payloads/payload_to_model.go b/pkg/handlers/primeapi/payloads/payload_to_model.go index 0b8b6f1a1d3..9a3e53fccf8 100644 --- a/pkg/handlers/primeapi/payloads/payload_to_model.go +++ b/pkg/handlers/primeapi/payloads/payload_to_model.go @@ -597,6 +597,8 @@ func MTOServiceItemModelFromUpdate(mtoServiceItemID string, mtoServiceItem prime model.CustomerContacts = customerContacts } + model.SITCustomerContacted = handlers.FmtDatePtrToPopPtr(sit.SitCustomerContacted) + model.SITRequestedDelivery = handlers.FmtDatePtrToPopPtr(sit.SitRequestedDelivery) } if verrs != nil && verrs.HasAny() { diff --git a/pkg/handlers/primeapi/payloads/payload_to_model_test.go b/pkg/handlers/primeapi/payloads/payload_to_model_test.go index 870c904ece7..a6e679614a1 100644 --- a/pkg/handlers/primeapi/payloads/payload_to_model_test.go +++ b/pkg/handlers/primeapi/payloads/payload_to_model_test.go @@ -164,7 +164,6 @@ func (suite *PayloadsSuite) TestMTOServiceItemModel() { suite.Equal(models.ReServiceCodeDDFSIT, returnedModel.ReService.Code) suite.Equal(destPostalCode, returnedModel.SITDestinationFinalAddress.PostalCode) suite.Equal(destStreet, returnedModel.SITDestinationFinalAddress.StreetAddress1) - }) suite.Run("Success - Returns SIT destination service item model without customer contact fields", func() { diff --git a/pkg/handlers/primeapi/payment_request_test.go b/pkg/handlers/primeapi/payment_request_test.go index 51a258ee0a2..8f8a281fada 100644 --- a/pkg/handlers/primeapi/payment_request_test.go +++ b/pkg/handlers/primeapi/payment_request_test.go @@ -730,10 +730,7 @@ func (suite *HandlerSuite) TestCreatePaymentRequestHandlerNewPaymentRequestCreat move, mtoServiceItems := suite.setupDomesticLinehaulData() moveTaskOrderID := move.ID - requestUser := factory.BuildUser(nil, nil, nil) - req := httptest.NewRequest("POST", "/payment_requests", nil) - req = suite.AuthenticateUserRequest(req, requestUser) planner := &routemocks.Planner{} planner.On("Zip5TransitDistanceLineHaul", @@ -802,10 +799,7 @@ func (suite *HandlerSuite) TestCreatePaymentRequestHandlerNewPaymentRequestCreat mtoServiceItems[0].Status = models.MTOServiceItemStatusSubmitted suite.MustSave(&mtoServiceItems[0]) - requestUser := factory.BuildUser(nil, nil, nil) - req := httptest.NewRequest("POST", "/payment_requests", nil) - req = suite.AuthenticateUserRequest(req, requestUser) planner := &routemocks.Planner{} @@ -849,10 +843,7 @@ func (suite *HandlerSuite) TestCreatePaymentRequestHandlerNewPaymentRequestCreat mtoServiceItems[0].Status = models.MTOServiceItemStatusRejected suite.MustSave(&mtoServiceItems[0]) - requestUser := factory.BuildUser(nil, nil, nil) - req := httptest.NewRequest("POST", "/payment_requests", nil) - req = suite.AuthenticateUserRequest(req, requestUser) planner := &routemocks.Planner{} @@ -899,10 +890,7 @@ func (suite *HandlerSuite) TestCreatePaymentRequestHandlerInvalidMTOReferenceID( move, mtoServiceItems := suite.setupDomesticLinehaulData() moveTaskOrderID := move.ID - requestUser := factory.BuildUser(nil, nil, nil) - req := httptest.NewRequest("POST", "/payment_requests", nil) - req = suite.AuthenticateUserRequest(req, requestUser) planner := &routemocks.Planner{} planner.On("Zip5TransitDistanceLineHaul", @@ -967,10 +955,7 @@ func (suite *HandlerSuite) TestCreatePaymentRequestHandlerInvalidMTOReferenceID( move, mtoServiceItems := suite.setupDomesticLinehaulData() moveTaskOrderID := move.ID - requestUser := factory.BuildUser(nil, nil, nil) - req := httptest.NewRequest("POST", "/payment_requests", nil) - req = suite.AuthenticateUserRequest(req, requestUser) planner := &routemocks.Planner{} planner.On("Zip5TransitDistanceLineHaul", diff --git a/pkg/handlers/primeapiv2/move_task_order_test.go b/pkg/handlers/primeapiv2/move_task_order_test.go index 94b20dd16fb..69df49410a9 100644 --- a/pkg/handlers/primeapiv2/move_task_order_test.go +++ b/pkg/handlers/primeapiv2/move_task_order_test.go @@ -1086,12 +1086,14 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { serviceItem := factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ { Model: models.MTOServiceItem{ - RejectionReason: models.StringPointer("not applicable"), - MTOShipmentID: &successShipment.ID, - Reason: models.StringPointer("there was a delay in getting the apartment"), - SITEntryDate: &nowDate, - SITDepartureDate: &later, - CustomerContacts: models.MTOServiceItemCustomerContacts{contact1, contact2}, + RejectionReason: models.StringPointer("not applicable"), + MTOShipmentID: &successShipment.ID, + Reason: models.StringPointer("there was a delay in getting the apartment"), + SITEntryDate: &nowDate, + SITDepartureDate: &later, + CustomerContacts: models.MTOServiceItemCustomerContacts{contact1, contact2}, + SITCustomerContacted: &nowDate, + SITRequestedDelivery: &later, }, }, { @@ -1152,6 +1154,8 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { suite.Equal(*serviceItem.Reason, *payload.Reason) suite.Equal(serviceItem.SITEntryDate.Format(time.RFC3339), handlers.FmtDatePtrToPop(payload.SitEntryDate).Format(time.RFC3339)) suite.Equal(serviceItem.SITDepartureDate.Format(time.RFC3339), handlers.FmtDatePtrToPop(payload.SitDepartureDate).Format(time.RFC3339)) + suite.Equal(serviceItem.SITCustomerContacted.Format(time.RFC3339), handlers.FmtDatePtrToPop(payload.SitCustomerContacted).Format(time.RFC3339)) + suite.Equal(serviceItem.SITRequestedDelivery.Format(time.RFC3339), handlers.FmtDatePtrToPop(payload.SitRequestedDelivery).Format(time.RFC3339)) suite.Equal(serviceItem.CustomerContacts[0].DateOfContact.Format(time.RFC3339), handlers.FmtDatePtrToPop(payload.DateOfContact1).Format(time.RFC3339)) suite.Equal(serviceItem.CustomerContacts[0].TimeMilitary, *payload.TimeMilitary1) suite.Equal(serviceItem.CustomerContacts[0].FirstAvailableDeliveryDate.Format(time.RFC3339), handlers.FmtDatePtrToPop(payload.FirstAvailableDeliveryDate1).Format(time.RFC3339)) diff --git a/pkg/handlers/primeapiv2/payloads/model_to_payload.go b/pkg/handlers/primeapiv2/payloads/model_to_payload.go index c3fbfa23db3..9428f84e01b 100644 --- a/pkg/handlers/primeapiv2/payloads/model_to_payload.go +++ b/pkg/handlers/primeapiv2/payloads/model_to_payload.go @@ -572,6 +572,8 @@ func MTOServiceItem(mtoServiceItem *models.MTOServiceItem) primev2messages.MTOSe SitEntryDate: handlers.FmtDatePtr(mtoServiceItem.SITEntryDate), SitDestinationFinalAddress: Address(mtoServiceItem.SITDestinationFinalAddress), SitAddressUpdates: SITAddressUpdates(mtoServiceItem.SITAddressUpdates), + SitCustomerContacted: handlers.FmtDatePtr(mtoServiceItem.SITCustomerContacted), + SitRequestedDelivery: handlers.FmtDatePtr(mtoServiceItem.SITRequestedDelivery), } case models.ReServiceCodeDCRT, models.ReServiceCodeDUCRT: diff --git a/pkg/handlers/routing/internalapi_test/ppm_shipment_test.go b/pkg/handlers/routing/internalapi_test/ppm_shipment_test.go new file mode 100644 index 00000000000..d4fedcbf8df --- /dev/null +++ b/pkg/handlers/routing/internalapi_test/ppm_shipment_test.go @@ -0,0 +1,93 @@ +package internalapi_test + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "time" + + "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" +) + +func (suite *InternalAPISuite) TestSubmitPPMShipmentDocumentation() { + setUpDataAndEndpointPath := func() (*models.PPMShipment, string) { + ppmShipment := factory.BuildPPMShipmentReadyForFinalCustomerCloseOut(suite.DB(), nil, factory.GetTraitActiveServiceMemberUser()) + + endpointPath := fmt.Sprintf("/internal/ppm-shipments/%s/submit-ppm-shipment-documentation", ppmShipment.ID.String()) + + return &ppmShipment, endpointPath + } + + // setUpRequestBody sets up the request body for the ppm document submission request. + setUpRequestBody := func() *bytes.Buffer { + body := &internalmessages.SavePPMShipmentSignedCertification{ + CertificationText: handlers.FmtString("I accept all the liability!"), + Signature: handlers.FmtString("Best Customer"), + Date: handlers.FmtDate(time.Now()), + } + + jsonBody, err := json.Marshal(body) + + suite.FatalNoError(err) + + bodyBuffer := bytes.NewBuffer(jsonBody) + + return bodyBuffer + } + + suite.Run("Unauthorized call to /ppm-shipments/{ppmShipmentId}/submit-ppm-shipment-documentation by another service member", func() { + _, endpointPath := setUpDataAndEndpointPath() + + maliciousUser := factory.BuildServiceMember(suite.DB(), factory.GetTraitActiveServiceMemberUser(), nil) + + body := setUpRequestBody() + + req := suite.NewAuthenticatedMilRequest(http.MethodPost, endpointPath, body, maliciousUser) + + req.Header.Set("Content-Type", "application/json") + + rr := httptest.NewRecorder() + + suite.SetupSiteHandler().ServeHTTP(rr, req) + + suite.Equal(http.StatusNotFound, rr.Code) + }) + + suite.Run("Unauthorized call to /ppm-shipments/{ppmShipmentId}/submit-ppm-shipment-documentation by user that isn't logged in", func() { + _, endpointPath := setUpDataAndEndpointPath() + + body := setUpRequestBody() + + req := suite.NewMilRequest(http.MethodPost, endpointPath, body) + + req.Header.Set("Content-Type", "application/json") + + rr := httptest.NewRecorder() + + suite.SetupSiteHandler().ServeHTTP(rr, req) + + // Happens because we don't have a CSRF token, since they aren't logged in. + suite.Equal(http.StatusForbidden, rr.Code) + }) + + suite.Run("Authorized call to /ppm-shipments/{ppmShipmentId}/submit-ppm-shipment-documentation", func() { + ppmShipment, endpointPath := setUpDataAndEndpointPath() + + body := setUpRequestBody() + + req := suite.NewAuthenticatedMilRequest(http.MethodPost, endpointPath, body, ppmShipment.Shipment.MoveTaskOrder.Orders.ServiceMember) + + req.Header.Set("Content-Type", "application/json") + + rr := httptest.NewRecorder() + + suite.SetupSiteHandler().ServeHTTP(rr, req) + + suite.Equal(http.StatusOK, rr.Code) + }) +} diff --git a/pkg/handlers/routing/internalapi_test/weight_ticket_test.go b/pkg/handlers/routing/internalapi_test/weight_ticket_test.go new file mode 100644 index 00000000000..80b7e49edb7 --- /dev/null +++ b/pkg/handlers/routing/internalapi_test/weight_ticket_test.go @@ -0,0 +1,35 @@ +package internalapi_test + +import ( + "fmt" + "net/http" + "net/http/httptest" + + "github.com/transcom/mymove/pkg/factory" +) + +func (suite *InternalAPISuite) TestUploadWeightTicket() { + suite.Run("Authorized post to /ppm-shipments/{ppmShipmentId}/weight-ticket", func() { + ppmShipment := factory.BuildPPMShipment(suite.DB(), factory.GetTraitActiveServiceMemberUser(), nil) + serviceMember := ppmShipment.Shipment.MoveTaskOrder.Orders.ServiceMember + endpointPath := fmt.Sprintf("/internal/ppm-shipments/%s/weight-ticket", ppmShipment.ID.String()) + + req := suite.NewAuthenticatedMilRequest("POST", endpointPath, nil, serviceMember) + rr := httptest.NewRecorder() + + suite.SetupSiteHandler().ServeHTTP(rr, req) + suite.Equal(http.StatusOK, rr.Code) + }) + + suite.Run("Unauthorized post to /ppm-shipments/{ppmShipmentId}/weight-ticket", func() { + ppmShipment := factory.BuildPPMShipment(suite.DB(), nil, nil) + serviceMember := factory.BuildServiceMember(suite.DB(), factory.GetTraitActiveServiceMemberUser(), nil) + endpointPath := fmt.Sprintf("/internal/ppm-shipments/%s/weight-ticket", ppmShipment.ID.String()) + + req := suite.NewAuthenticatedMilRequest("POST", endpointPath, nil, serviceMember) + rr := httptest.NewRecorder() + + suite.SetupSiteHandler().ServeHTTP(rr, req) + suite.Equal(http.StatusNotFound, rr.Code) + }) +} diff --git a/pkg/handlers/testharnessapi/api.go b/pkg/handlers/testharnessapi/api.go index 0cd3e79a7eb..1a6f43558fb 100644 --- a/pkg/handlers/testharnessapi/api.go +++ b/pkg/handlers/testharnessapi/api.go @@ -47,10 +47,15 @@ func NewDefaultBuilder(handlerConfig handlers.HandlerConfig) http.Handler { t := template.Must(template.New("users").Parse(` - +
+ Back to Testharness data scenarios list +

Testharness data scenario created Success

+
+

The Testharness scenario was created successfully. The raw JSON output below contains information on the created scenario.

+
{{.}}
@@ -78,10 +83,14 @@ func NewBuilderList(handlerConfig handlers.HandlerConfig) http.Handler { t := template.Must(template.New("actions").Parse(` - +
+

Welcome to the Testharness data scenarios New

+
+

These scenarios were introduced in ADR 0076 and addressed further in ADR 0083. These scenarios can be used to create Moves in the MilMove system when working locally or with Ephemeral deployments. Click any of the buttons below to create a Move data scenario.

+
{{range .}} diff --git a/pkg/models/line_of_accounting.go b/pkg/models/line_of_accounting.go index 7f38d0b1906..634de797f62 100644 --- a/pkg/models/line_of_accounting.go +++ b/pkg/models/line_of_accounting.go @@ -6,6 +6,15 @@ import ( "github.com/gofrs/uuid" ) +const ( + LineOfAccountingHouseholdGoodsCodeCivilian string = "HC" + LineOfAccountingHouseholdGoodsCodeEnlisted string = "HE" + LineOfAccountingHouseholdGoodsCodeOfficer string = "HO" + LineOfAccountingHouseholdGoodsCodeOther string = "HT" + LineOfAccountingHouseholdGoodsCodeDual string = "HD" + LineOfAccountingHouseholdGoodsCodeNTS string = "HS" +) + type LineOfAccounting struct { ID uuid.UUID `json:"id" db:"id"` LoaSysID *int `json:"loa_sys_id" db:"loa_sys_id"` // Using an int here deviates from the TRDM matrix; however, no direct conflicts found as of yet diff --git a/pkg/models/mto_service_items.go b/pkg/models/mto_service_items.go index 47471dd8485..0e30e428cdb 100644 --- a/pkg/models/mto_service_items.go +++ b/pkg/models/mto_service_items.go @@ -40,6 +40,8 @@ type MTOServiceItem struct { SITPostalCode *string `db:"sit_postal_code"` SITEntryDate *time.Time `db:"sit_entry_date"` SITDepartureDate *time.Time `db:"sit_departure_date"` + SITCustomerContacted *time.Time `db:"sit_customer_contacted"` + SITRequestedDelivery *time.Time `db:"sit_requested_delivery"` SITOriginHHGOriginalAddress *Address `belongs_to:"addresses" fk_id:"sit_origin_hhg_original_address_id"` SITOriginHHGOriginalAddressID *uuid.UUID `db:"sit_origin_hhg_original_address_id"` SITOriginHHGActualAddress *Address `belongs_to:"addresses" fk_id:"sit_origin_hhg_actual_address_id"` diff --git a/pkg/models/re_zip3s.go b/pkg/models/re_zip3s.go index 282b46b7a24..a389ea58123 100644 --- a/pkg/models/re_zip3s.go +++ b/pkg/models/re_zip3s.go @@ -45,3 +45,17 @@ func (r *ReZip3) Validate(_ *pop.Connection) (*validate.Errors, error) { &validators.UUIDIsPresent{Field: r.DomesticServiceAreaID, Name: "DomesticServiceAreaID"}, ), nil } + +// FetchReZip3Item returns an reZip3 for a given zip3 +func FetchReZip3Item(tx *pop.Connection, zip3 string) (*ReZip3, error) { + var reZip3 ReZip3 + err := tx. + Where("zip3 = $1", zip3). + First(&reZip3) + + if err != nil { + return nil, err + } + + return &reZip3, err +} diff --git a/pkg/models/re_zip3s_test.go b/pkg/models/re_zip3s_test.go index 7b823d7243b..3395842216d 100644 --- a/pkg/models/re_zip3s_test.go +++ b/pkg/models/re_zip3s_test.go @@ -4,6 +4,7 @@ import ( "github.com/gofrs/uuid" "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/testdatagen" ) func (suite *ModelSuite) TestReZip3Validations() { @@ -44,4 +45,17 @@ func (suite *ModelSuite) TestReZip3Validations() { } suite.verifyValidationErrors(&invalidReZip3, expErrors) }) + + suite.Run("test FetchReZip3Item", func() { + zip3 := "606" + testdatagen.MakeReZip3(suite.DB(), testdatagen.Assertions{ + ReZip3: models.ReZip3{ + Zip3: zip3, + }, + }) + + reZip3, err := models.FetchReZip3Item(suite.DB(), zip3) + suite.Nil(err) + suite.Equal(zip3, reZip3.Zip3) + }) } diff --git a/pkg/route/hhg_planner.go b/pkg/route/hhg_planner.go index de4ac14b44b..544d8f5eed7 100644 --- a/pkg/route/hhg_planner.go +++ b/pkg/route/hhg_planner.go @@ -3,6 +3,8 @@ package route import ( "fmt" + "go.uber.org/zap" + "github.com/transcom/mymove/pkg/appcontext" "github.com/transcom/mymove/pkg/models" ) @@ -63,6 +65,22 @@ func (p *hhgPlanner) ZipTransitDistance(appCtx appcontext.AppContext, source str 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 + sourceReZip3, sErr := models.FetchReZip3Item(appCtx.DB(), sourceZip3) + if sErr != nil { + appCtx.Logger().Error("Failed to fetch the reZip3 item for sourceZip3", zap.Error(sErr)) + } + destinationReZip3, dErr := models.FetchReZip3Item(appCtx.DB(), destZip3) + if dErr != nil { + appCtx.Logger().Error("Failed to fetch the reZip3 item for destinationZip3", zap.Error(dErr)) + } + + // Different zip3, same base point city, use DTOD + if sourceReZip3 != nil && destinationReZip3 != nil && sourceReZip3.BasePointCity == destinationReZip3.BasePointCity { + return p.dtodPlannerMileage.DTODZip5Distance(appCtx, source, destination) + } + return randMcNallyZip3Distance(appCtx, sourceZip3, destZip3) } diff --git a/pkg/route/hhg_planner_test.go b/pkg/route/hhg_planner_test.go index 78988f3f62c..e9639c0e8ff 100644 --- a/pkg/route/hhg_planner_test.go +++ b/pkg/route/hhg_planner_test.go @@ -104,6 +104,44 @@ func (suite *GHCTestSuite) TestHHGZipTransitDistance() { suite.Equal(1, distance) }) + suite.Run("Uses DTOD for distance when origin/dest zips differ but are in the same base point city", func() { + // Mock DTOD distance response + testSoapClient := &ghcmocks.SoapCaller{} + testSoapClient.On("Call", + mock.Anything, + mock.Anything, + ).Return(soapResponseForDistance("166"), nil) + + // Create two zip3s in the same base point city (Miami) + testdatagen.MakeReZip3(suite.DB(), testdatagen.Assertions{ + ReZip3: models.ReZip3{ + Zip3: "330", + BasePointCity: "Miami", + State: "FL", + }, + }) + testdatagen.MakeReZip3(suite.DB(), testdatagen.Assertions{ + ReZip3: models.ReZip3{ + Zip3: "331", + BasePointCity: "Miami", + State: "FL", + }, + ReDomesticServiceArea: models.ReDomesticServiceArea{ + ServiceArea: "005", + }, + }) + + plannerMileage := NewDTODZip5Distance(fakeUsername, fakePassword, testSoapClient) + planner := NewHHGPlanner(plannerMileage) + + // Get distance between two zips in the same base point city + distance, err := planner.ZipTransitDistance(suite.AppContextForTest(), "33169", "33040") + suite.NoError(err) + + // Ensure DTOD was used for distance + suite.Equal(166, distance) + }) + suite.Run("fake DTOD returns an error", func() { testSoapClient := &ghcmocks.SoapCaller{} testSoapClient.On("Call", diff --git a/pkg/services/ghcrateengine/domestic_destination_additional_days_sit_pricer_test.go b/pkg/services/ghcrateengine/domestic_destination_additional_days_sit_pricer_test.go index 5eeb47f9a8c..a452cf2da84 100644 --- a/pkg/services/ghcrateengine/domestic_destination_additional_days_sit_pricer_test.go +++ b/pkg/services/ghcrateengine/domestic_destination_additional_days_sit_pricer_test.go @@ -20,7 +20,7 @@ const ( ddasitTestEscalationCompounded = 1.042 ddasitTestWeight = unit.Pound(4200) ddasitTestNumberOfDaysInSIT = 29 - ddasitTestPriceCents = unit.Cents(948060) // ddasitTestBasePriceCents * (ddasitTestWeight / 100) * ddasitTestEscalationCompounded * ddasitTestNumberOfDaysInSIT + ddasitTestPriceCents = unit.Cents(948068) // ddasitTestBasePriceCents * (ddasitTestWeight / 100) * ddasitTestEscalationCompounded * ddasitTestNumberOfDaysInSIT ) var ddasitTestRequestedPickupDate = time.Date(testdatagen.TestYear, time.January, 5, 7, 33, 11, 456, time.UTC) @@ -87,7 +87,7 @@ func (suite *GHCRateEngineServiceSuite) TestDomesticDestinationAdditionalDaysSIT twoYearsLaterPickupDate := ddasitTestRequestedPickupDate.AddDate(2, 0, 0) _, _, err := pricer.Price(suite.AppContextForTest(), testdatagen.DefaultContractCode, twoYearsLaterPickupDate, ddasitTestWeight, ddasitTestServiceArea, ddasitTestNumberOfDaysInSIT, false) suite.Error(err) - suite.Contains(err.Error(), "could not fetch contract year") + suite.Contains(err.Error(), "could not lookup contract year") }) } diff --git a/pkg/services/ghcrateengine/domestic_destination_first_day_sit_pricer_test.go b/pkg/services/ghcrateengine/domestic_destination_first_day_sit_pricer_test.go index 0d641d4e758..59735de002b 100644 --- a/pkg/services/ghcrateengine/domestic_destination_first_day_sit_pricer_test.go +++ b/pkg/services/ghcrateengine/domestic_destination_first_day_sit_pricer_test.go @@ -82,7 +82,7 @@ func (suite *GHCRateEngineServiceSuite) TestDomesticDestinationFirstDaySITPricer twoYearsLaterPickupDate := ddfsitTestRequestedPickupDate.AddDate(2, 0, 0) _, _, err := pricer.Price(suite.AppContextForTest(), testdatagen.DefaultContractCode, twoYearsLaterPickupDate, ddfsitTestWeight, ddfsitTestServiceArea, false) suite.Error(err) - suite.Contains(err.Error(), "could not fetch contract year") + suite.Contains(err.Error(), "could not lookup contract year") }) } diff --git a/pkg/services/ghcrateengine/domestic_destination_pricer.go b/pkg/services/ghcrateengine/domestic_destination_pricer.go index 85412b0d858..0bbfb8fbcef 100644 --- a/pkg/services/ghcrateengine/domestic_destination_pricer.go +++ b/pkg/services/ghcrateengine/domestic_destination_pricer.go @@ -45,18 +45,17 @@ func (p domesticDestinationPricer) Price(appCtx appcontext.AppContext, contractC return 0, nil, fmt.Errorf("Could not lookup Domestic Service Area Price: %w", err) } - contractYear, err := fetchContractYear(appCtx, domServiceAreaPrice.ContractID, referenceDate) - if err != nil { - return 0, nil, fmt.Errorf("Could not lookup contract year: %w", err) - } - finalWeight := weight if isPPM && weight < minDomesticWeight { finalWeight = minDomesticWeight } basePrice := domServiceAreaPrice.PriceCents.Float64() * finalWeight.ToCWTFloat64() - escalatedPrice := basePrice * contractYear.EscalationCompounded + escalatedPrice, contractYear, err := escalatePriceForContractYear(appCtx, domServiceAreaPrice.ContractID, referenceDate, false, basePrice) + if err != nil { + return 0, nil, fmt.Errorf("could not calculate escalated price: %w", err) + } + totalCost := unit.Cents(math.Round(escalatedPrice)) pricingParams := services.PricingDisplayParams{ diff --git a/pkg/services/ghcrateengine/domestic_destination_pricer_test.go b/pkg/services/ghcrateengine/domestic_destination_pricer_test.go index 7b7b60e5f1b..175892481fb 100644 --- a/pkg/services/ghcrateengine/domestic_destination_pricer_test.go +++ b/pkg/services/ghcrateengine/domestic_destination_pricer_test.go @@ -226,7 +226,8 @@ func (suite *GHCRateEngineServiceSuite) TestPriceDomesticDestination() { ) suite.Error(err) - suite.Equal("Could not lookup contract year: "+models.RecordNotFoundErrorString, err.Error()) + suite.Contains(err.Error(), "could not calculate escalated price") + }) suite.Run("fail when is weight below minimum and shipment isn't a PPM", func() { diff --git a/pkg/services/ghcrateengine/domestic_destination_shuttling_pricer_test.go b/pkg/services/ghcrateengine/domestic_destination_shuttling_pricer_test.go index 0a33c4dd300..1f2c7e2de39 100644 --- a/pkg/services/ghcrateengine/domestic_destination_shuttling_pricer_test.go +++ b/pkg/services/ghcrateengine/domestic_destination_shuttling_pricer_test.go @@ -75,7 +75,7 @@ func (suite *GHCRateEngineServiceSuite) TestDomesticDestinationShuttlingPricer() twoYearsLaterPickupDate := ddshutTestRequestedPickupDate.AddDate(2, 0, 0) _, _, err := pricer.Price(suite.AppContextForTest(), testdatagen.DefaultContractCode, twoYearsLaterPickupDate, ddshutTestWeight, ddshutTestServiceSchedule) suite.Error(err) - suite.Contains(err.Error(), "Could not lookup contract year") + suite.Contains(err.Error(), "could not calculate escalated price") }) } diff --git a/pkg/services/ghcrateengine/domestic_destination_sit_delivery_pricer_test.go b/pkg/services/ghcrateengine/domestic_destination_sit_delivery_pricer_test.go index e29541a865b..e858bb6726d 100644 --- a/pkg/services/ghcrateengine/domestic_destination_sit_delivery_pricer_test.go +++ b/pkg/services/ghcrateengine/domestic_destination_sit_delivery_pricer_test.go @@ -201,7 +201,7 @@ func (suite *GHCRateEngineServiceSuite) TestDomesticDestinationSITDeliveryPricer twoYearsLaterPickupDate := dddsitTestRequestedPickupDate.AddDate(2, 0, 0) _, _, err := pricer.Price(suite.AppContextForTest(), testdatagen.DefaultContractCode, twoYearsLaterPickupDate, dddsitTestWeight, dddsitTestServiceArea, dddsitTestSchedule, zipDest, zipSITDest, distance) suite.Error(err) - suite.Contains(err.Error(), "could not fetch contract year") + suite.Contains(err.Error(), "could not lookup contract year") }) } diff --git a/pkg/services/ghcrateengine/domestic_linehaul_pricer.go b/pkg/services/ghcrateengine/domestic_linehaul_pricer.go index 013edda14ef..dfea88ca6ac 100644 --- a/pkg/services/ghcrateengine/domestic_linehaul_pricer.go +++ b/pkg/services/ghcrateengine/domestic_linehaul_pricer.go @@ -50,15 +50,20 @@ func (p domesticLinehaulPricer) Price(appCtx appcontext.AppContext, contractCode return unit.Cents(0), nil, fmt.Errorf("could not fetch domestic linehaul rate: %w", err) } - contractYear, err := fetchContractYear(appCtx, domesticLinehaulPrice.ContractID, referenceDate) + baseTotalPrice := finalWeight.ToCWTFloat64() * distance.Float64() * domesticLinehaulPrice.PriceMillicents.Float64() + + escalatedPrice, contractYear, err := escalatePriceForContractYear( + appCtx, + domesticLinehaulPrice.ContractID, + referenceDate, + true, + baseTotalPrice) + if err != nil { - return 0, nil, fmt.Errorf("Could not lookup contract year: %w", err) + return 0, nil, fmt.Errorf("could not calculate escalated price: %w", err) } - baseTotalPrice := finalWeight.ToCWTFloat64() * distance.Float64() * domesticLinehaulPrice.PriceMillicents.Float64() - escalatedTotalPrice := contractYear.EscalationCompounded * baseTotalPrice - - totalPriceMillicents := unit.Millicents(escalatedTotalPrice) + totalPriceMillicents := unit.Millicents(escalatedPrice) totalPriceCents := totalPriceMillicents.ToCents() params := services.PricingDisplayParams{ diff --git a/pkg/services/ghcrateengine/domestic_linehaul_pricer_test.go b/pkg/services/ghcrateengine/domestic_linehaul_pricer_test.go index 0422210d2e5..a7ade6b2f04 100644 --- a/pkg/services/ghcrateengine/domestic_linehaul_pricer_test.go +++ b/pkg/services/ghcrateengine/domestic_linehaul_pricer_test.go @@ -18,12 +18,12 @@ const ( dlhTestWeightUpper = unit.Pound(4999) dlhTestMilesLower = 1001 dlhTestMilesUpper = 1500 - dlhTestBasePriceMillicents = unit.Millicents(5100) + dlhTestBasePriceMillicents = unit.Millicents(5111) dlhTestContractYearName = "DLH Test Year" dlhTestEscalationCompounded = 1.04071 - dlhTestDistance = unit.Miles(1200) - dlhTestWeight = unit.Pound(4000) - dlhPriceCents = unit.Cents(254766) + dlhTestDistance = unit.Miles(1201) + dlhTestWeight = unit.Pound(4001) + dlhPriceCents = unit.Cents(255592) ) var dlhRequestedPickupDate = time.Date(testdatagen.TestYear, time.June, 5, 7, 33, 11, 456, time.UTC) @@ -32,6 +32,7 @@ func (suite *GHCRateEngineServiceSuite) TestPriceDomesticLinehaul() { linehaulServicePricer := NewDomesticLinehaulPricer() suite.Run("success using PaymentServiceItemParams", func() { + // serviceArea := "sa0" suite.setupDomesticLinehaulPrice(dlhTestServiceArea, dlhTestIsPeakPeriod, dlhTestWeightLower, dlhTestWeightUpper, dlhTestMilesLower, dlhTestMilesUpper, dlhTestBasePriceMillicents, dlhTestContractYearName, dlhTestEscalationCompounded) paymentServiceItem := suite.setupDomesticLinehaulServiceItem() priceCents, displayParams, err := linehaulServicePricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) @@ -86,7 +87,7 @@ func (suite *GHCRateEngineServiceSuite) TestPriceDomesticLinehaul() { // < 50 mile distance with PPM priceCents, _, err := linehaulServicePricer.Price(suite.AppContextForTest(), testdatagen.DefaultContractCode, dlhRequestedPickupDate, unit.Miles(49), dlhTestWeight, dlhTestServiceArea, isPPM) suite.NoError(err) - suite.Equal(unit.Cents(10403), priceCents) + suite.Equal(unit.Cents(10428), priceCents) }) suite.Run("successfully finds linehaul price for ppm with distance < 50 miles with PriceUsingParams method", func() { @@ -198,6 +199,10 @@ func (suite *GHCRateEngineServiceSuite) TestPriceDomesticLinehaul() { _, _, err = linehaulServicePricer.Price(suite.AppContextForTest(), testdatagen.DefaultContractCode, dlhRequestedPickupDate, dlhTestDistance, dlhTestWeight, "", isPPM) suite.Error(err) suite.Equal("ServiceArea is required", err.Error()) + + _, _, err = linehaulServicePricer.Price(suite.AppContextForTest(), testdatagen.DefaultContractCode, time.Date(testdatagen.TestYear+1, 1, 1, 1, 1, 1, 1, time.UTC), dlhTestDistance, dlhTestWeight, dlhTestServiceArea, isPPM) + suite.Error(err) + suite.Contains(err.Error(), "could not fetch domestic linehaul rate") }) } diff --git a/pkg/services/ghcrateengine/domestic_origin_additional_days_sit_pricer_test.go b/pkg/services/ghcrateengine/domestic_origin_additional_days_sit_pricer_test.go index ffc796b19a1..b6660471834 100644 --- a/pkg/services/ghcrateengine/domestic_origin_additional_days_sit_pricer_test.go +++ b/pkg/services/ghcrateengine/domestic_origin_additional_days_sit_pricer_test.go @@ -20,7 +20,7 @@ const ( doasitTestEscalationCompounded = 1.042 doasitTestWeight = unit.Pound(4200) doasitTestNumberOfDaysInSIT = 29 - doasitTestPriceCents = unit.Cents(948060) // doasitTestBasePriceCents * (doasitTestWeight / 100) * doasitTestEscalationCompounded * doasitTestNumberOfDaysInSIT + doasitTestPriceCents = unit.Cents(948068) // doasitTestBasePriceCents * (doasitTestWeight / 100) * doasitTestEscalationCompounded948068 * doasitTestNumberOfDaysInSIT ) var doasitTestRequestedPickupDate = time.Date(testdatagen.TestYear, time.January, 5, 7, 33, 11, 456, time.UTC) @@ -87,7 +87,7 @@ func (suite *GHCRateEngineServiceSuite) TestDomesticOriginAdditionalDaysSITPrice twoYearsLaterPickupDate := doasitTestRequestedPickupDate.AddDate(2, 0, 0) _, _, err := pricer.Price(suite.AppContextForTest(), testdatagen.DefaultContractCode, twoYearsLaterPickupDate, doasitTestWeight, doasitTestServiceArea, doasitTestNumberOfDaysInSIT, false) suite.Error(err) - suite.Contains(err.Error(), "could not fetch contract year") + suite.Contains(err.Error(), "could not lookup contract year") }) } diff --git a/pkg/services/ghcrateengine/domestic_origin_first_day_sit_pricer_test.go b/pkg/services/ghcrateengine/domestic_origin_first_day_sit_pricer_test.go index 0a067b91c48..60d0b2bd57c 100644 --- a/pkg/services/ghcrateengine/domestic_origin_first_day_sit_pricer_test.go +++ b/pkg/services/ghcrateengine/domestic_origin_first_day_sit_pricer_test.go @@ -82,7 +82,7 @@ func (suite *GHCRateEngineServiceSuite) TestDomesticOriginFirstDaySITPricer() { twoYearsLaterPickupDate := dofsitTestRequestedPickupDate.AddDate(2, 0, 0) _, _, err := pricer.Price(suite.AppContextForTest(), testdatagen.DefaultContractCode, twoYearsLaterPickupDate, dofsitTestWeight, dofsitTestServiceArea, false) suite.Error(err) - suite.Contains(err.Error(), "could not fetch contract year") + suite.Contains(err.Error(), "could not lookup contract year") }) } diff --git a/pkg/services/ghcrateengine/domestic_origin_pricer.go b/pkg/services/ghcrateengine/domestic_origin_pricer.go index cf5e8b285f1..5c2e6914fc3 100644 --- a/pkg/services/ghcrateengine/domestic_origin_pricer.go +++ b/pkg/services/ghcrateengine/domestic_origin_pricer.go @@ -45,18 +45,25 @@ func (p domesticOriginPricer) Price(appCtx appcontext.AppContext, contractCode s return 0, nil, fmt.Errorf("Could not lookup Domestic Service Area Price: %w", err) } - contractYear, err := fetchContractYear(appCtx, domServiceAreaPrice.ContractID, referenceDate) - if err != nil { - return 0, nil, fmt.Errorf("Could not lookup contract year: %w", err) - } - finalWeight := weight if isPPM && weight < minDomesticWeight { finalWeight = minDomesticWeight } basePrice := domServiceAreaPrice.PriceCents.Float64() * finalWeight.ToCWTFloat64() - escalatedPrice := basePrice * contractYear.EscalationCompounded + + escalatedPrice, contractYear, err := escalatePriceForContractYear( + appCtx, + domServiceAreaPrice.ContractID, + referenceDate, + false, + basePrice, + ) + + if err != nil { + return 0, nil, fmt.Errorf("could not calculate escalated price: %w", err) + } + totalCost := unit.Cents(math.Round(escalatedPrice)) params := services.PricingDisplayParams{ diff --git a/pkg/services/ghcrateengine/domestic_origin_pricer_test.go b/pkg/services/ghcrateengine/domestic_origin_pricer_test.go index 225dba31b8b..d1ec0cbad03 100644 --- a/pkg/services/ghcrateengine/domestic_origin_pricer_test.go +++ b/pkg/services/ghcrateengine/domestic_origin_pricer_test.go @@ -1,6 +1,7 @@ package ghcrateengine import ( + "fmt" "strconv" "time" @@ -190,8 +191,14 @@ func (suite *GHCRateEngineServiceSuite) TestPriceDomesticOrigin() { isPPM, ) - suite.Error(err) - suite.Equal("Could not lookup contract year: "+models.RecordNotFoundErrorString, err.Error()) + if suite.Error(err) { + expectedErr := fmt.Sprintf( + "could not calculate escalated price: could not lookup contract year: %s", + models.RecordNotFoundErrorString, + ) + + suite.Equal(expectedErr, err.Error()) + } }) suite.Run("weight below minimum", func() { diff --git a/pkg/services/ghcrateengine/domestic_origin_shuttling_pricer_test.go b/pkg/services/ghcrateengine/domestic_origin_shuttling_pricer_test.go index 61c843cdc87..7ed1540e0fa 100644 --- a/pkg/services/ghcrateengine/domestic_origin_shuttling_pricer_test.go +++ b/pkg/services/ghcrateengine/domestic_origin_shuttling_pricer_test.go @@ -75,7 +75,7 @@ func (suite *GHCRateEngineServiceSuite) TestDomesticOriginShuttlingPricer() { twoYearsLaterPickupDate := doshutTestRequestedPickupDate.AddDate(2, 0, 0) _, _, err := pricer.Price(suite.AppContextForTest(), testdatagen.DefaultContractCode, twoYearsLaterPickupDate, doshutTestWeight, doshutTestServiceSchedule) suite.Error(err) - suite.Contains(err.Error(), "Could not lookup contract year") + suite.Contains(err.Error(), "could not calculate escalated price") }) } diff --git a/pkg/services/ghcrateengine/domestic_origin_sit_pickup_pricer_test.go b/pkg/services/ghcrateengine/domestic_origin_sit_pickup_pricer_test.go index 83fab1322f8..45372caf1ef 100644 --- a/pkg/services/ghcrateengine/domestic_origin_sit_pickup_pricer_test.go +++ b/pkg/services/ghcrateengine/domestic_origin_sit_pickup_pricer_test.go @@ -194,7 +194,7 @@ func (suite *GHCRateEngineServiceSuite) TestDomesticOriginSITPickupPricer50Miles twoYearsLaterPickupDate := dopsitTestRequestedPickupDate.AddDate(2, 0, 0) _, _, err := pricer.Price(suite.AppContextForTest(), testdatagen.DefaultContractCode, twoYearsLaterPickupDate, dopsitTestWeight, dopsitTestServiceArea, dopsitTestSchedule, zipOriginal, zipActual, distance) suite.Error(err) - suite.Contains(err.Error(), "could not fetch contract year") + suite.Contains(err.Error(), "could not lookup contract year") }) } diff --git a/pkg/services/ghcrateengine/domestic_shorthaul_pricer.go b/pkg/services/ghcrateengine/domestic_shorthaul_pricer.go index 6485f74bcde..80846b04718 100644 --- a/pkg/services/ghcrateengine/domestic_shorthaul_pricer.go +++ b/pkg/services/ghcrateengine/domestic_shorthaul_pricer.go @@ -54,13 +54,11 @@ func (p domesticShorthaulPricer) Price(appCtx appcontext.AppContext, contractCod return 0, nil, fmt.Errorf("Could not lookup Domestic Service Area Price: %w", err) } - contractYear, err := fetchContractYear(appCtx, domServiceAreaPrice.ContractID, referenceDate) + basePrice := domServiceAreaPrice.PriceCents.Float64() * distance.Float64() * weight.ToCWTFloat64() + escalatedPrice, contractYear, err := escalatePriceForContractYear(appCtx, domServiceAreaPrice.ContractID, referenceDate, false, basePrice) if err != nil { - return 0, nil, fmt.Errorf("Could not lookup contract year: %w", err) + return 0, nil, fmt.Errorf("could not calculate escalated price: %w", err) } - - basePrice := domServiceAreaPrice.PriceCents.Float64() * distance.Float64() * weight.ToCWTFloat64() - escalatedPrice := basePrice * contractYear.EscalationCompounded totalCost = unit.Cents(math.Round(escalatedPrice)) var pricingRateEngineParams = services.PricingDisplayParams{ diff --git a/pkg/services/ghcrateengine/domestic_shorthaul_pricer_test.go b/pkg/services/ghcrateengine/domestic_shorthaul_pricer_test.go index c44a337cdfc..8ecbdb14d75 100644 --- a/pkg/services/ghcrateengine/domestic_shorthaul_pricer_test.go +++ b/pkg/services/ghcrateengine/domestic_shorthaul_pricer_test.go @@ -197,8 +197,8 @@ func (suite *GHCRateEngineServiceSuite) TestPriceDomesticShorthaul() { ) suite.Error(err) - suite.Equal("Could not lookup contract year: "+models.RecordNotFoundErrorString, err.Error()) suite.Nil(rateEngineParams) + suite.Contains(err.Error(), "could not calculate escalated price") }) suite.Run("weight below minimum", func() { diff --git a/pkg/services/ghcrateengine/pricer_helpers.go b/pkg/services/ghcrateengine/pricer_helpers.go index 79f520daeae..1df9484286a 100644 --- a/pkg/services/ghcrateengine/pricer_helpers.go +++ b/pkg/services/ghcrateengine/pricer_helpers.go @@ -46,21 +46,16 @@ func priceDomesticPackUnpack(appCtx appcontext.AppContext, packUnpackCode models return 0, nil, fmt.Errorf("Could not lookup domestic other price: %w", err) } - var contractYear models.ReContractYear - err = appCtx.DB().Where("contract_id = $1", domOtherPrice.ContractID). - Where("$2 between start_date and end_date", referenceDate). - First(&contractYear) - if err != nil { - return 0, nil, fmt.Errorf("Could not lookup contract year: %w", err) - } - finalWeight := weight if isPPM && weight < minDomesticWeight { finalWeight = minDomesticWeight } basePrice := domOtherPrice.PriceCents.Float64() * finalWeight.ToCWTFloat64() - escalatedPrice := basePrice * contractYear.EscalationCompounded + escalatedPrice, contractYear, err := escalatePriceForContractYear(appCtx, domOtherPrice.ContractID, referenceDate, false, basePrice) + if err != nil { + return 0, nil, fmt.Errorf("could not calculate escalated price: %w", err) + } displayParams := services.PricingDisplayParams{ { @@ -124,15 +119,12 @@ func priceDomesticFirstDaySIT(appCtx appcontext.AppContext, firstDaySITCode mode return unit.Cents(0), nil, fmt.Errorf("could not fetch domestic %s first day SIT rate: %w", sitType, err) } - contractYear, err := fetchContractYear(appCtx, serviceAreaPrice.ContractID, referenceDate) + baseTotalPrice := serviceAreaPrice.PriceCents.Float64() * weight.ToCWTFloat64() + escalatedPrice, contractYear, err := escalatePriceForContractYear(appCtx, serviceAreaPrice.ContractID, referenceDate, false, baseTotalPrice) if err != nil { - return unit.Cents(0), nil, fmt.Errorf("could not fetch contract year: %w", err) + return 0, nil, fmt.Errorf("could not calculate escalated price: %w", err) } - - baseTotalPrice := serviceAreaPrice.PriceCents.Float64() * weight.ToCWTFloat64() - escalatedTotalPrice := baseTotalPrice * contractYear.EscalationCompounded - - totalPriceCents := unit.Cents(math.Round(escalatedTotalPrice)) + totalPriceCents := unit.Cents(math.Round(escalatedPrice)) params := services.PricingDisplayParams{ {Key: models.ServiceItemParamNameContractYearName, Value: contractYear.Name}, @@ -164,13 +156,12 @@ func priceDomesticAdditionalDaysSIT(appCtx appcontext.AppContext, additionalDayS return unit.Cents(0), nil, fmt.Errorf("could not fetch domestic %s additional days SIT rate: %w", sitType, err) } - contractYear, err := fetchContractYear(appCtx, serviceAreaPrice.ContractID, referenceDate) + baseTotalPrice := serviceAreaPrice.PriceCents.Float64() * weight.ToCWTFloat64() + escalatedTotalPrice, contractYear, err := escalatePriceForContractYear(appCtx, serviceAreaPrice.ContractID, referenceDate, false, baseTotalPrice) if err != nil { - return unit.Cents(0), nil, fmt.Errorf("could not fetch contract year: %w", err) + return 0, nil, fmt.Errorf("could not calculate escalated price: %w", err) } - baseTotalPrice := serviceAreaPrice.PriceCents.Float64() * weight.ToCWTFloat64() - escalatedTotalPrice := baseTotalPrice * contractYear.EscalationCompounded totalForNumberOfDaysPrice := escalatedTotalPrice * float64(numberOfDaysInSIT) totalPriceCents := unit.Cents(math.Round(totalForNumberOfDaysPrice)) @@ -266,13 +257,11 @@ func priceDomesticPickupDeliverySIT(appCtx appcontext.AppContext, pickupDelivery if err != nil { return unit.Cents(0), nil, fmt.Errorf("could not fetch domestic %s SIT %s rate: %w", sitType, sitModifier, err) } - contractYear, err := fetchContractYear(appCtx, domOtherPrice.ContractID, referenceDate) + baseTotalPrice := domOtherPrice.PriceCents.Float64() * weight.ToCWTFloat64() + escalatedTotalPrice, contractYear, err := escalatePriceForContractYear(appCtx, domOtherPrice.ContractID, referenceDate, false, baseTotalPrice) if err != nil { - return unit.Cents(0), nil, fmt.Errorf("could not fetch contract year: %w", err) + return 0, nil, fmt.Errorf("could not calculate escalated price: %w", err) } - - baseTotalPrice := domOtherPrice.PriceCents.Float64() * weight.ToCWTFloat64() - escalatedTotalPrice := baseTotalPrice * contractYear.EscalationCompounded totalPriceCents := unit.Cents(math.Round(escalatedTotalPrice)) displayParams := services.PricingDisplayParams{ @@ -321,13 +310,11 @@ func priceDomesticShuttling(appCtx appcontext.AppContext, shuttlingCode models.R return 0, nil, fmt.Errorf("Could not lookup Domestic Accessorial Area Price: %w", err) } - contractYear, err := fetchContractYear(appCtx, domAccessorialPrice.ContractID, referenceDate) + basePrice := domAccessorialPrice.PerUnitCents.Float64() * weight.ToCWTFloat64() + escalatedPrice, contractYear, err := escalatePriceForContractYear(appCtx, domAccessorialPrice.ContractID, referenceDate, false, basePrice) if err != nil { - return 0, nil, fmt.Errorf("Could not lookup contract year: %w", err) + return 0, nil, fmt.Errorf("could not calculate escalated price: %w", err) } - - basePrice := domAccessorialPrice.PerUnitCents.Float64() * weight.ToCWTFloat64() - escalatedPrice := basePrice * contractYear.EscalationCompounded totalCost := unit.Cents(math.Round(escalatedPrice)) params := services.PricingDisplayParams{ @@ -361,11 +348,10 @@ func priceDomesticCrating(appCtx appcontext.AppContext, code models.ReServiceCod } basePrice := domAccessorialPrice.PerUnitCents.Float64() * float64(billedCubicFeet) - contractYear, err := fetchContractYear(appCtx, domAccessorialPrice.ContractID, referenceDate) + escalatedPrice, contractYear, err := escalatePriceForContractYear(appCtx, domAccessorialPrice.ContractID, referenceDate, false, basePrice) if err != nil { - return 0, nil, fmt.Errorf("could not lookup contract year: %w", err) + return 0, nil, fmt.Errorf("could not calculate escalated price: %w", err) } - escalatedPrice := basePrice * contractYear.EscalationCompounded totalCost := unit.Cents(math.Round(escalatedPrice)) params := services.PricingDisplayParams{ @@ -428,3 +414,34 @@ func createPricerGeneratedParams(appCtx appcontext.AppContext, paymentServiceIte } return paymentServiceItemParams, nil } + +// escalatePriceForContractYear calculates the escalated price from the base price, which is provided by the caller/pricer, +// and the escalation factor, which is provided by the contract year. The result is rounded to the nearest cent, or to the +// nearest tenth-cent before and after the escalation factor for linehaul prices. The contract year is also returned. +func escalatePriceForContractYear(appCtx appcontext.AppContext, contractID uuid.UUID, referenceDate time.Time, isLinehaul bool, basePrice float64) (float64, models.ReContractYear, error) { + contractYear, err := fetchContractYear(appCtx, contractID, referenceDate) + if err != nil { + return 0, contractYear, fmt.Errorf("could not lookup contract year: %w", err) + } + + escalatedPrice := basePrice + + // round escalated price to the nearest cent, or the nearest tenth-of-a-cent if linehaul + precision := 0 + if isLinehaul { + precision = 1 + escalatedPrice = roundToPrecision(escalatedPrice, precision) + } + + escalatedPrice = escalatedPrice * contractYear.EscalationCompounded + + escalatedPrice = roundToPrecision(escalatedPrice, precision) + return escalatedPrice, contractYear, nil +} + +// roundToPrecision rounds a float64 value to the number of decimal points indicated by the precision. +// TODO: Future cleanup could involve moving this function to a math/utility package with some simple tests +func roundToPrecision(value float64, precision int) float64 { + ratio := math.Pow(10, float64(precision)) + return math.Round(value*ratio) / ratio +} diff --git a/pkg/services/ghcrateengine/pricer_helpers_test.go b/pkg/services/ghcrateengine/pricer_helpers_test.go index 90ca95a64ce..389535de055 100644 --- a/pkg/services/ghcrateengine/pricer_helpers_test.go +++ b/pkg/services/ghcrateengine/pricer_helpers_test.go @@ -69,7 +69,7 @@ func (suite *GHCRateEngineServiceSuite) Test_priceDomesticPackUnpack() { isPPM := false _, _, err := priceDomesticPackUnpack(suite.AppContextForTest(), models.ReServiceCodeDNPK, testdatagen.DefaultContractCode, twoYearsLaterPickupDate, dnpkTestWeight, dnpkTestServicesScheduleOrigin, isPPM) suite.Error(err) - suite.Contains(err.Error(), "Could not lookup contract year") + suite.Contains(err.Error(), "could not lookup contract year") }) suite.Run("not finding shipment type price", func() { @@ -178,7 +178,7 @@ func (suite *GHCRateEngineServiceSuite) Test_priceDomesticFirstDaySIT() { twoYearsLaterPickupDate := ddfsitTestRequestedPickupDate.AddDate(2, 0, 0) _, _, err := priceDomesticFirstDaySIT(suite.AppContextForTest(), models.ReServiceCodeDDFSIT, testdatagen.DefaultContractCode, twoYearsLaterPickupDate, ddfsitTestWeight, ddfsitTestServiceArea, false) suite.Error(err) - suite.Contains(err.Error(), "could not fetch contract year") + suite.Contains(err.Error(), "could not lookup contract year") }) } @@ -239,7 +239,7 @@ func (suite *GHCRateEngineServiceSuite) Test_priceDomesticAdditionalDaysSIT() { twoYearsLaterPickupDate := ddasitTestRequestedPickupDate.AddDate(2, 0, 0) _, _, err := priceDomesticAdditionalDaysSIT(suite.AppContextForTest(), models.ReServiceCodeDDASIT, testdatagen.DefaultContractCode, twoYearsLaterPickupDate, ddasitTestWeight, ddasitTestServiceArea, ddasitTestNumberOfDaysInSIT, false) suite.Error(err) - suite.Contains(err.Error(), "could not fetch contract year") + suite.Contains(err.Error(), "could not lookup contract year") }) } @@ -365,7 +365,7 @@ func (suite *GHCRateEngineServiceSuite) Test_priceDomesticPickupDeliverySIT50Mil twoYearsLaterPickupDate := dddsitTestRequestedPickupDate.AddDate(2, 0, 0) _, _, err = priceDomesticPickupDeliverySIT(suite.AppContextForTest(), models.ReServiceCodeDDDSIT, testdatagen.DefaultContractCode, twoYearsLaterPickupDate, dddsitTestWeight, dddsitTestServiceArea, dddsitTestSchedule, domOtherZipDest, domOtherZipSITDest, domOtherDistance) suite.Error(err) - suite.Contains(err.Error(), "could not fetch contract year") + suite.Contains(err.Error(), "could not lookup contract year") }) } @@ -553,7 +553,7 @@ func (suite *GHCRateEngineServiceSuite) Test_priceDomesticShuttling() { _, _, err := priceDomesticShuttling(suite.AppContextForTest(), models.ReServiceCodeDDSHUT, testdatagen.DefaultContractCode, twoYearsLaterPickupDate, ddshutTestWeight, ddshutTestServiceSchedule) suite.Error(err) - suite.Contains(err.Error(), "Could not lookup contract year") + suite.Contains(err.Error(), "could not calculate escalated price: could not lookup contract year") }) } func (suite *GHCRateEngineServiceSuite) Test_priceDomesticCrating() { @@ -609,3 +609,51 @@ func (suite *GHCRateEngineServiceSuite) Test_priceDomesticCrating() { suite.Contains(err.Error(), "could not lookup contract year") }) } + +func (suite *GHCRateEngineServiceSuite) Test_escalatePriceForContractYear() { + suite.Run("escalated price is rounded to the nearest cent for non-linehaul pricing", func() { + escalationCompounded := 1.04071 + cy := testdatagen.MakeReContractYear(suite.DB(), + testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + EscalationCompounded: escalationCompounded, + }, + }) + isLinehaul := false + basePrice := 5117.0 + + escalatedPrice, contractYear, err := escalatePriceForContractYear(suite.AppContextForTest(), cy.ContractID, cy.StartDate.AddDate(0, 0, 1), isLinehaul, basePrice) + + suite.Nil(err) + suite.Equal(cy.ID, contractYear.ID) + suite.Equal(5325.0, escalatedPrice) + }) + + suite.Run("escalated price is rounded to the nearest tenth-cent for linehaul pricing", func() { + escalationCompounded := 1.04071 + cy := testdatagen.MakeReContractYear(suite.DB(), + testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + EscalationCompounded: escalationCompounded, + }, + }) + isLinehaul := true + basePrice := 5117.0 + + escalatedPrice, contractYear, err := escalatePriceForContractYear(suite.AppContextForTest(), cy.ContractID, cy.StartDate.AddDate(0, 0, 1), isLinehaul, basePrice) + + suite.Nil(err) + suite.Equal(cy.ID, contractYear.ID) + suite.Equal(5325.3, escalatedPrice) + }) + + suite.Run("not finding contract year", func() { + isLinehaul := true + basePrice := 5117.0 + + _, _, err := escalatePriceForContractYear(suite.AppContextForTest(), uuid.Nil, time.Time{}, isLinehaul, basePrice) + + suite.Error(err) + suite.Contains(err.Error(), "could not lookup contract year") + }) +} diff --git a/pkg/services/invoice/gex_sender_http_test.go b/pkg/services/invoice/gex_sender_http_test.go index 548e5c03e8f..ae422ee495f 100644 --- a/pkg/services/invoice/gex_sender_http_test.go +++ b/pkg/services/invoice/gex_sender_http_test.go @@ -33,9 +33,7 @@ func (suite *GexSuite) TestSendToGexHTTP_Call() { })) resp, err := NewGexSenderHTTP(mockServer.URL, false, nil, "", ""). SendToGex(services.GEXChannelInvoice, bodyString, "test_transaction") - if resp == nil || err != nil { - suite.T().Fatal(err, "Failed mock request") - } + suite.FatalNoError(err, "Failed mock request") expectedStatus := http.StatusOK suite.Equal(expectedStatus, resp.StatusCode) @@ -44,9 +42,7 @@ func (suite *GexSuite) TestSendToGexHTTP_Call() { })) resp, err = NewGexSenderHTTP(mockServer.URL, false, nil, "", ""). SendToGex(services.GEXChannelInvoice, bodyString, "test_transaction") - if resp == nil || err != nil { - suite.T().Fatal(err, "Failed mock request") - } + suite.FatalNoError(err, "Failed mock request") expectedStatus = http.StatusInternalServerError suite.Equal(expectedStatus, resp.StatusCode) @@ -59,9 +55,7 @@ func (suite *GexSuite) TestSendToGexHTTP_QueryParams() { })) resp, err := NewGexSenderHTTP(mockServer.URL, false, nil, "", ""). SendToGex(services.GEXChannelInvoice, bodyString, "test_filename") - if resp == nil || err != nil { - suite.T().Fatal(err, "Failed mock request") - } + suite.FatalNoError(err, "Failed mock request") expectedStatus := http.StatusOK suite.Equal(expectedStatus, resp.StatusCode) diff --git a/pkg/services/invoice/ghc_payment_request_invoice_generator.go b/pkg/services/invoice/ghc_payment_request_invoice_generator.go index dcff9281f2f..d11ccbdc9e0 100644 --- a/pkg/services/invoice/ghc_payment_request_invoice_generator.go +++ b/pkg/services/invoice/ghc_payment_request_invoice_generator.go @@ -5,6 +5,7 @@ import ( "fmt" "regexp" "strconv" + "strings" "github.com/benbjohnson/clock" "github.com/gofrs/uuid" @@ -558,7 +559,7 @@ func (g ghcPaymentRequestInvoiceGenerator) createOriginAndDestinationSegments(ap return nil } -func (g ghcPaymentRequestInvoiceGenerator) createLoaSegments(orders models.Order, shipment models.MTOShipment) (edisegment.FA1, []edisegment.FA2, error) { +func (g ghcPaymentRequestInvoiceGenerator) createLoaSegments(appCtx appcontext.AppContext, orders models.Order, shipment models.MTOShipment) (edisegment.FA1, []edisegment.FA2, error) { // We need to determine which TAC to use. We'll default to using the HHG TAC as that's what we've been doing // up to this point. But now we need to look at the service item's MTOShipment (if there is one -- some // service items like MS/CS aren't associated with a shipment) and see if it prefers the NTS TAC instead. @@ -623,24 +624,182 @@ func (g ghcPaymentRequestInvoiceGenerator) createLoaSegments(orders models.Order AgencyQualifierCode: agencyQualifierCode, } - fa2 := edisegment.FA2{ - BreakdownStructureDetailCode: "TA", + // May have multiple FA2 segments: TAC, SAC (optional), and many long LOA values + var fa2s []edisegment.FA2 + + // TAC + fa2TAC := edisegment.FA2{ + BreakdownStructureDetailCode: edisegment.FA2DetailCodeTA, FinancialInformationCode: tac, } + fa2s = append(fa2s, fa2TAC) - if sac == "" { + // SAC (optional) + if sac != "" { + fa2SAC := edisegment.FA2{ + BreakdownStructureDetailCode: edisegment.FA2DetailCodeZZ, + FinancialInformationCode: sac, + } + fa2s = append(fa2s, fa2SAC) + } - return fa1, []edisegment.FA2{fa2}, nil + fa2LongLoaSegments, err := g.createLongLoaSegments(appCtx, orders, tac) + if err != nil { + return edisegment.FA1{}, nil, err + } + fa2s = append(fa2s, fa2LongLoaSegments...) + return fa1, fa2s, nil +} + +func (g ghcPaymentRequestInvoiceGenerator) createLongLoaSegments(appCtx appcontext.AppContext, orders models.Order, tac string) ([]edisegment.FA2, error) { + var loas []models.LineOfAccounting + var loa models.LineOfAccounting + + // tac_fn_bl_mod_cd is a char(1) field. It has a mix of letters and numbers. We want to get lowest numbers first, and + // numbers before letters. This is the behavior we get from order by. + err := appCtx.DB().Q(). + Join("transportation_accounting_codes t", "t.loa_id = lines_of_accounting.id"). + Where("t.tac = ?", tac). + Where("? between loa_bgn_dt and loa_end_dt", orders.IssueDate). + Where("t.tac_fn_bl_mod_cd != 'P'"). + Where("loa_hs_gds_cd != ?", models.LineOfAccountingHouseholdGoodsCodeNTS). + Order("t.tac_fn_bl_mod_cd asc"). + Order("loa_bgn_dt desc"). + Order("t.tac_fy_txt desc"). + All(&loas) + if err != nil { + switch err { + case sql.ErrNoRows: + // If no matching rows, don't include any long lines of accounting. + return nil, nil + default: + return nil, apperror.NewQueryError("lineOfAccounting", err, "Unexpected error") + } + } + + if len(loas) == 0 { + return nil, nil } - fa2sac := edisegment.FA2{ - BreakdownStructureDetailCode: "ZZ", - FinancialInformationCode: sac, + //"HE" - E-1 through E-9 and Special Enlisted + //"HO" - O-1 Academy graduate through O-10, W1 - W5, Aviation Cadet, Academy Cadet, and Midshipman + //"HC" - Civilian employee + + if orders.ServiceMember.Rank == nil { + return nil, apperror.NewConflictError(orders.ServiceMember.ID, "this service member has no rank") } + rank := *orders.ServiceMember.Rank - return fa1, []edisegment.FA2{fa2, fa2sac}, nil + hhgCode := "" + if rank[:2] == "E_" { + hhgCode = "HE" + } else if rank[:2] == "O_" || rank[:2] == "W_" || rank == models.ServiceMemberRankACADEMYCADET || rank == models.ServiceMemberRankAVIATIONCADET || rank == models.ServiceMemberRankMIDSHIPMAN { + hhgCode = "HO" + } else if rank == models.ServiceMemberRankCIVILIANEMPLOYEE { + hhgCode = "HC" + } else { + return nil, apperror.NotImplementedError{} + } + // if just one, pick it + // if multiple,lowest FBMC + var loaWithMatchingCode []models.LineOfAccounting + + for _, line := range loas { + if line.LoaHsGdsCd != nil && *line.LoaHsGdsCd == hhgCode { + loaWithMatchingCode = append(loaWithMatchingCode, line) + } + } + if len(loaWithMatchingCode) == 0 { + // fall back to the whole set and then sort by fbmc + // take first thing from whole set + loa = loas[0] + } + if len(loaWithMatchingCode) >= 1 { + // take first of loaWithMatchingCode + loa = loaWithMatchingCode[0] + } + + var fa2LongLoaSegments []edisegment.FA2 + + var concatDate *string + if (loa.LoaBgnDt != nil && !loa.LoaBgnDt.IsZero()) && + (loa.LoaEndDt != nil && !loa.LoaEndDt.IsZero()) { + fiscalYearStr := fmt.Sprintf("%d%d", loa.LoaBgnDt.Year(), loa.LoaEndDt.Year()) + concatDate = &fiscalYearStr + } + + // Create long LOA FA2 segments + segmentInputs := []struct { + detailCode edisegment.FA2DetailCode + infoCode *string + }{ + // If order of these changes, tests will also need to be adjusted. Using alpha order by detailCode. + {edisegment.FA2DetailCodeA1, loa.LoaDptID}, + {edisegment.FA2DetailCodeA2, loa.LoaTnsfrDptNm}, + {edisegment.FA2DetailCodeA3, concatDate}, + {edisegment.FA2DetailCodeA4, loa.LoaBafID}, + {edisegment.FA2DetailCodeA5, loa.LoaTrsySfxTx}, + {edisegment.FA2DetailCodeA6, loa.LoaMajClmNm}, + {edisegment.FA2DetailCodeB1, loa.LoaOpAgncyID}, + {edisegment.FA2DetailCodeB2, loa.LoaAlltSnID}, + {edisegment.FA2DetailCodeB3, loa.LoaUic}, + {edisegment.FA2DetailCodeC1, loa.LoaPgmElmntID}, + {edisegment.FA2DetailCodeC2, loa.LoaTskBdgtSblnTx}, + {edisegment.FA2DetailCodeD1, loa.LoaDfAgncyAlctnRcpntID}, + {edisegment.FA2DetailCodeD4, loa.LoaJbOrdNm}, + {edisegment.FA2DetailCodeD6, loa.LoaSbaltmtRcpntID}, + {edisegment.FA2DetailCodeD7, loa.LoaWkCntrRcpntNm}, + {edisegment.FA2DetailCodeE1, loa.LoaMajRmbsmtSrcID}, + {edisegment.FA2DetailCodeE2, loa.LoaDtlRmbsmtSrcID}, + {edisegment.FA2DetailCodeE3, loa.LoaCustNm}, + {edisegment.FA2DetailCodeF1, loa.LoaObjClsID}, + {edisegment.FA2DetailCodeF3, loa.LoaSrvSrcID}, + {edisegment.FA2DetailCodeG2, loa.LoaSpclIntrID}, + {edisegment.FA2DetailCodeI1, loa.LoaBdgtAcntClsNm}, + {edisegment.FA2DetailCodeJ1, loa.LoaDocID}, + {edisegment.FA2DetailCodeK6, loa.LoaClsRefID}, + {edisegment.FA2DetailCodeL1, loa.LoaInstlAcntgActID}, + {edisegment.FA2DetailCodeM1, loa.LoaLclInstlID}, + {edisegment.FA2DetailCodeN1, loa.LoaTrnsnID}, + {edisegment.FA2DetailCodeP5, loa.LoaFmsTrnsactnID}, + } + + for _, input := range segmentInputs { + fa2, loaErr := createLongLoaSegment(input.detailCode, input.infoCode) + if loaErr != nil { + return nil, loaErr + } + if fa2 != nil { + fa2LongLoaSegments = append(fa2LongLoaSegments, *fa2) + } + } + + return fa2LongLoaSegments, nil +} + +func createLongLoaSegment(detailCode edisegment.FA2DetailCode, infoCode *string) (*edisegment.FA2, error) { + // If we don't have an infoCode value, then just ignore this segment + if infoCode == nil || strings.TrimSpace(*infoCode) == "" { + return nil, nil + } + value := *infoCode + + // Make sure we have a detailCode + if len(detailCode) != 2 { + return nil, apperror.NewImplementationError("Detail code should have length 2") + } + + // The FinancialInformationCode field is limited to 80 characters, so make sure the value doesn't exceed + // that (given our LOA field schema types, it shouldn't unless we've made a mistake somewhere). + if len(value) > 80 { + return nil, apperror.NewImplementationError(fmt.Sprintf("Value for FA2 code %s exceeds 80 character limit", detailCode)) + } + return &edisegment.FA2{ + BreakdownStructureDetailCode: detailCode, + FinancialInformationCode: value, + }, nil } func (g ghcPaymentRequestInvoiceGenerator) fetchPaymentServiceItemParam(appCtx appcontext.AppContext, serviceItemID uuid.UUID, key models.ServiceItemParamName) (models.PaymentServiceItemParam, error) { @@ -715,9 +874,9 @@ func (g ghcPaymentRequestInvoiceGenerator) getWeightAndDistanceParams(appCtx app var distanceModel models.ServiceItemParamName switch serviceItem.MTOServiceItem.ReService.Code { - case models.ReServiceCodeDDDSIT: + case models.ReServiceCodeDDDSIT, models.ReServiceCodeDDSFSC: distanceModel = models.ServiceItemParamNameDistanceZipSITDest - case models.ReServiceCodeDOPSIT: + case models.ReServiceCodeDOPSIT, models.ReServiceCodeDOSFSC: distanceModel = models.ServiceItemParamNameDistanceZipSITOrigin default: distanceModel = models.ServiceItemParamNameDistanceZip @@ -881,7 +1040,7 @@ func (g ghcPaymentRequestInvoiceGenerator) generatePaymentServiceItemSegments(ap } - fa1, fa2s, err := g.createLoaSegments(orders, serviceItem.MTOServiceItem.MTOShipment) + fa1, fa2s, err := g.createLoaSegments(appCtx, orders, serviceItem.MTOServiceItem.MTOShipment) if err != nil { return segments, l3, err } diff --git a/pkg/services/invoice/ghc_payment_request_invoice_generator_test.go b/pkg/services/invoice/ghc_payment_request_invoice_generator_test.go index 5305b455651..2edf920f0c6 100644 --- a/pkg/services/invoice/ghc_payment_request_invoice_generator_test.go +++ b/pkg/services/invoice/ghc_payment_request_invoice_generator_test.go @@ -283,11 +283,17 @@ func (suite *GHCInvoiceSuite) TestAllGenerateEdi() { KeyType: models.ServiceItemParamTypeInteger, Value: "44", } - dddsitParams := append(basicPaymentServiceItemParams, distanceZipSITDestParam) + destSITParams := append(basicPaymentServiceItemParams, distanceZipSITDestParam) dddsit := factory.BuildPaymentServiceItemWithParams( suite.DB(), models.ReServiceCodeDDDSIT, - dddsitParams, + destSITParams, + customizations, nil, + ) + ddsfsc := factory.BuildPaymentServiceItemWithParams( + suite.DB(), + models.ReServiceCodeDDSFSC, + destSITParams, customizations, nil, ) @@ -296,16 +302,22 @@ func (suite *GHCInvoiceSuite) TestAllGenerateEdi() { KeyType: models.ServiceItemParamTypeInteger, Value: "33", } - dopsitParams := append(basicPaymentServiceItemParams, distanceZipSITOriginParam) + origSITParams := append(basicPaymentServiceItemParams, distanceZipSITOriginParam) dopsit := factory.BuildPaymentServiceItemWithParams( suite.DB(), models.ReServiceCodeDOPSIT, - dopsitParams, + origSITParams, + customizations, nil, + ) + dosfsc := factory.BuildPaymentServiceItemWithParams( + suite.DB(), + models.ReServiceCodeDOSFSC, + origSITParams, customizations, nil, ) paymentServiceItems = models.PaymentServiceItems{} - paymentServiceItems = append(paymentServiceItems, dlh, fsc, ms, cs, dsh, dop, ddp, dpk, dnpk, dupk, ddfsit, ddasit, dofsit, doasit, doshut, ddshut, dcrt, ducrt, dddsit, dopsit) + paymentServiceItems = append(paymentServiceItems, dlh, fsc, ms, cs, dsh, dop, ddp, dpk, dnpk, dupk, ddfsit, ddasit, dofsit, doasit, doshut, ddshut, dcrt, ducrt, dddsit, ddsfsc, dopsit, dosfsc) // setup known next value icnErr := suite.icnSequencer.SetVal(suite.AppContextForTest(), 122) @@ -373,7 +385,7 @@ func (suite *GHCInvoiceSuite) TestAllGenerateEdi() { suite.Run("se segment has correct value", func() { setupTestData() // Will need to be updated as more service items are supported - suite.Equal(165, result.SE.NumberOfIncludedSegments) + suite.Equal(179, result.SE.NumberOfIncludedSegments) suite.Equal("0001", result.SE.TransactionSetControlNumber) }) @@ -927,7 +939,7 @@ func (suite *GHCInvoiceSuite) TestAllGenerateEdi() { suite.Run("adds fa2 service item segment", func() { fa2 := result.ServiceItems[segmentOffset].FA2s - suite.Equal("TA", fa2[0].BreakdownStructureDetailCode) + suite.Equal(edisegment.FA2DetailCodeTA, fa2[0].BreakdownStructureDetailCode) suite.Equal(*paymentRequest.MoveTaskOrder.Orders.TAC, fa2[0].FinancialInformationCode) }) @@ -1053,9 +1065,9 @@ func (suite *GHCInvoiceSuite) TestAllGenerateEdi() { switch serviceCode { case models.ReServiceCodeDSH: suite.Equal(float64(24246), l0.BilledRatedAsQuantity) - case models.ReServiceCodeDDDSIT: + case models.ReServiceCodeDDDSIT, models.ReServiceCodeDDSFSC: suite.Equal(float64(44), l0.BilledRatedAsQuantity) - case models.ReServiceCodeDOPSIT: + case models.ReServiceCodeDOPSIT, models.ReServiceCodeDOSFSC: suite.Equal(float64(33), l0.BilledRatedAsQuantity) default: suite.Equal(float64(24246), l0.BilledRatedAsQuantity) @@ -1080,10 +1092,11 @@ func (suite *GHCInvoiceSuite) TestAllGenerateEdi() { } }) + // shouldnt this be in the thing above? suite.Run("adds l3 service item segment", func() { l3 := result.L3 // Will need to be updated as more service items are supported - suite.Equal(int64(17760), l3.PriceCents) + suite.Equal(int64(19536), l3.PriceCents) }) } @@ -1396,9 +1409,11 @@ func (suite *GHCInvoiceSuite) TestNoApprovedPaymentServiceItems() { }) } -func (suite *GHCInvoiceSuite) TestTACs() { +func (suite *GHCInvoiceSuite) TestFA2s() { mockClock := clock.NewMock() currentTime := mockClock.Now() + sixMonthsBefore := currentTime.AddDate(0, -6, 0) + sixMonthsAfter := currentTime.AddDate(0, 6, 0) basicPaymentServiceItemParams := []factory.CreatePaymentServiceItemParams{ { Key: models.ServiceItemParamNameContractCode, @@ -1428,15 +1443,17 @@ func (suite *GHCInvoiceSuite) TestTACs() { ntsTAC := "2222" hhgSAC := "3333" + var move models.Move var mtoShipment models.MTOShipment var paymentRequest models.PaymentRequest setupTestData := func() { - move := factory.BuildMove(suite.DB(), []factory.Customization{ + move = factory.BuildMove(suite.DB(), []factory.Customization{ { Model: models.Order{ - TAC: &hhgTAC, - NtsTAC: &ntsTAC, + TAC: &hhgTAC, + NtsTAC: &ntsTAC, + IssueDate: currentTime, }, }, }, nil) @@ -1492,6 +1509,8 @@ func (suite *GHCInvoiceSuite) TestTACs() { mtoShipment.TACType = nil suite.MustSave(&mtoShipment) + // No long lines of accounting added, so there should be no extra FA2 segments + result, err := generator.Generate(suite.AppContextForTest(), paymentRequest, false) suite.NoError(err) suite.Len(result.ServiceItems[0].FA2s, 1) @@ -1504,6 +1523,8 @@ func (suite *GHCInvoiceSuite) TestTACs() { mtoShipment.TACType = &tacType suite.MustSave(&mtoShipment) + // No long lines of accounting added, so there should be no extra FA2 segments + result, err := generator.Generate(suite.AppContextForTest(), paymentRequest, false) suite.NoError(err) suite.Len(result.ServiceItems[0].FA2s, 1) @@ -1516,6 +1537,8 @@ func (suite *GHCInvoiceSuite) TestTACs() { mtoShipment.TACType = &tacType suite.MustSave(&mtoShipment) + // No long lines of accounting added, so there should be no extra FA2 segments + result, err := generator.Generate(suite.AppContextForTest(), paymentRequest, false) suite.NoError(err) suite.Len(result.ServiceItems[0].FA2s, 1) @@ -1530,6 +1553,8 @@ func (suite *GHCInvoiceSuite) TestTACs() { paymentRequest.MoveTaskOrder.Orders.TAC = nil suite.MustSave(&paymentRequest.MoveTaskOrder.Orders) + // No long lines of accounting added, so there should be no extra FA2 segments + _, err := generator.Generate(suite.AppContextForTest(), paymentRequest, false) suite.Error(err) suite.Contains(err.Error(), "Must have an HHG TAC value") @@ -1543,6 +1568,8 @@ func (suite *GHCInvoiceSuite) TestTACs() { paymentRequest.MoveTaskOrder.Orders.NtsTAC = nil suite.MustSave(&paymentRequest.MoveTaskOrder.Orders) + // No long lines of accounting added, so there should be no extra FA2 segments + _, err := generator.Generate(suite.AppContextForTest(), paymentRequest, false) suite.Error(err) suite.Contains(err.Error(), "Must have an NTS TAC value") @@ -1555,6 +1582,8 @@ func (suite *GHCInvoiceSuite) TestTACs() { paymentRequest.MoveTaskOrder.Orders.SAC = &hhgSAC suite.MustSave(&paymentRequest.MoveTaskOrder.Orders) + // No long lines of accounting added, so there should be no extra FA2 segments + result, err := generator.Generate(suite.AppContextForTest(), paymentRequest, false) suite.NoError(err) suite.Len(result.ServiceItems[0].FA2s, 2) @@ -1570,12 +1599,13 @@ func (suite *GHCInvoiceSuite) TestTACs() { paymentRequest.MoveTaskOrder.Orders.SAC = &hhgSAC suite.MustSave(&paymentRequest.MoveTaskOrder.Orders) + // No long lines of accounting added, so there should be no extra FA2 segments + result, err := generator.Generate(suite.AppContextForTest(), paymentRequest, false) suite.NoError(err) suite.Len(result.ServiceItems[0].FA2s, 2) suite.Equal(hhgTAC, result.ServiceItems[0].FA2s[0].FinancialInformationCode) suite.Equal(hhgSAC, result.ServiceItems[0].FA2s[1].FinancialInformationCode) - }) suite.Run("shipment with NTS SAC/SDN type set", func() { @@ -1584,6 +1614,8 @@ func (suite *GHCInvoiceSuite) TestTACs() { mtoShipment.TACType = &tacType suite.MustSave(&mtoShipment) + // No long lines of accounting added, so there should be no extra FA2 segments + result, err := generator.Generate(suite.AppContextForTest(), paymentRequest, false) suite.NoError(err) suite.Len(result.ServiceItems[0].FA2s, 1) @@ -1598,11 +1630,12 @@ func (suite *GHCInvoiceSuite) TestTACs() { paymentRequest.MoveTaskOrder.Orders.SAC = nil suite.MustSave(&paymentRequest.MoveTaskOrder.Orders) + // No long lines of accounting added, so there should be no extra FA2 segments + result, err := generator.Generate(suite.AppContextForTest(), paymentRequest, false) suite.NoError(err) suite.Len(result.ServiceItems[0].FA2s, 1) suite.Equal(ntsTAC, result.ServiceItems[0].FA2s[0].FinancialInformationCode) - }) suite.Run("shipment with HHG TAC set up and TAC, but no SAC/SDN; It will display TAC only", func() { @@ -1613,13 +1646,546 @@ func (suite *GHCInvoiceSuite) TestTACs() { paymentRequest.MoveTaskOrder.Orders.SAC = nil suite.MustSave(&paymentRequest.MoveTaskOrder.Orders) + // No long lines of accounting added, so there should be no extra FA2 segments + result, err := generator.Generate(suite.AppContextForTest(), paymentRequest, false) suite.NoError(err) suite.Len(result.ServiceItems[0].FA2s, 1) suite.Equal(hhgTAC, result.ServiceItems[0].FA2s[0].FinancialInformationCode) + }) + + suite.Run("shipment with complete long line of accounting", func() { + setupTestData() + + // Add TAC/LOA records with fully filled out LOA fields + loa := factory.BuildFullLineOfAccounting(nil) + loa.LoaBgnDt = &sixMonthsBefore + loa.LoaEndDt = &sixMonthsAfter + factory.BuildTransportationAccountingCode(suite.DB(), []factory.Customization{ + { + Model: models.TransportationAccountingCode{ + TAC: *move.Orders.TAC, + TacFnBlModCd: models.StringPointer("W"), + }, + }, + { + Model: loa, + }, + }, nil) + + result, err := generator.Generate(suite.AppContextForTest(), paymentRequest, false) + suite.NoError(err) + + concatDate := fmt.Sprintf("%d%d", loa.LoaBgnDt.Year(), loa.LoaEndDt.Year()) + fa2Assertions := []struct { + expectedDetailCode edisegment.FA2DetailCode + expectedInfoCode *string + }{ + {edisegment.FA2DetailCodeTA, move.Orders.TAC}, + {edisegment.FA2DetailCodeA1, loa.LoaDptID}, + {edisegment.FA2DetailCodeA2, loa.LoaTnsfrDptNm}, + {edisegment.FA2DetailCodeA3, &concatDate}, + {edisegment.FA2DetailCodeA4, loa.LoaBafID}, + {edisegment.FA2DetailCodeA5, loa.LoaTrsySfxTx}, + {edisegment.FA2DetailCodeA6, loa.LoaMajClmNm}, + {edisegment.FA2DetailCodeB1, loa.LoaOpAgncyID}, + {edisegment.FA2DetailCodeB2, loa.LoaAlltSnID}, + {edisegment.FA2DetailCodeB3, loa.LoaUic}, + {edisegment.FA2DetailCodeC1, loa.LoaPgmElmntID}, + {edisegment.FA2DetailCodeC2, loa.LoaTskBdgtSblnTx}, + {edisegment.FA2DetailCodeD1, loa.LoaDfAgncyAlctnRcpntID}, + {edisegment.FA2DetailCodeD4, loa.LoaJbOrdNm}, + {edisegment.FA2DetailCodeD6, loa.LoaSbaltmtRcpntID}, + {edisegment.FA2DetailCodeD7, loa.LoaWkCntrRcpntNm}, + {edisegment.FA2DetailCodeE1, loa.LoaMajRmbsmtSrcID}, + {edisegment.FA2DetailCodeE2, loa.LoaDtlRmbsmtSrcID}, + {edisegment.FA2DetailCodeE3, loa.LoaCustNm}, + {edisegment.FA2DetailCodeF1, loa.LoaObjClsID}, + {edisegment.FA2DetailCodeF3, loa.LoaSrvSrcID}, + {edisegment.FA2DetailCodeG2, loa.LoaSpclIntrID}, + {edisegment.FA2DetailCodeI1, loa.LoaBdgtAcntClsNm}, + {edisegment.FA2DetailCodeJ1, loa.LoaDocID}, + {edisegment.FA2DetailCodeK6, loa.LoaClsRefID}, + {edisegment.FA2DetailCodeL1, loa.LoaInstlAcntgActID}, + {edisegment.FA2DetailCodeM1, loa.LoaLclInstlID}, + {edisegment.FA2DetailCodeN1, loa.LoaTrnsnID}, + {edisegment.FA2DetailCodeP5, loa.LoaFmsTrnsactnID}, + } + + suite.Len(result.ServiceItems[0].FA2s, len(fa2Assertions)) + for i, fa2Assertion := range fa2Assertions { + fa2Segment := result.ServiceItems[0].FA2s[i] + suite.Equal(fa2Assertion.expectedDetailCode, fa2Segment.BreakdownStructureDetailCode) + suite.Equal(*fa2Assertion.expectedInfoCode, fa2Segment.FinancialInformationCode) + } + }) + + suite.Run("shipment with nil/blank long line of accounting (except fiscal year)", func() { + setupTestData() + + // Add TAC/LOA records, with an LOA containing empty strings and nils + emptyString := "" + tac := factory.BuildTransportationAccountingCode(suite.DB(), []factory.Customization{ + { + Model: models.TransportationAccountingCode{ + TAC: *move.Orders.TAC, // TA + TacFnBlModCd: models.StringPointer("W"), + }, + }, + { + Model: models.LineOfAccounting{ + LoaDptID: &emptyString, // A1 + LoaTnsfrDptNm: &emptyString, // A2 + LoaBgnDt: &sixMonthsBefore, // A3 (first part) + LoaEndDt: &sixMonthsAfter, // A3 (second part) + LoaHsGdsCd: models.StringPointer("HT"), + // rest of fields will be nil + }, + }, + }, nil) + + result, err := generator.Generate(suite.AppContextForTest(), paymentRequest, false) + suite.NoError(err) + + concatDate := fmt.Sprintf("%d%d", tac.LineOfAccounting.LoaBgnDt.Year(), tac.LineOfAccounting.LoaEndDt.Year()) + fa2Assertions := []struct { + expectedDetailCode edisegment.FA2DetailCode + expectedInfoCode *string + }{ + {edisegment.FA2DetailCodeTA, move.Orders.TAC}, + {edisegment.FA2DetailCodeA3, &concatDate}, + } + + suite.Len(result.ServiceItems[0].FA2s, len(fa2Assertions)) + for i, fa2Assertion := range fa2Assertions { + fa2Segment := result.ServiceItems[0].FA2s[i] + suite.Equal(fa2Assertion.expectedDetailCode, fa2Segment.BreakdownStructureDetailCode) + suite.Equal(*fa2Assertion.expectedInfoCode, fa2Segment.FinancialInformationCode) + } + }) + + suite.Run("shipment with partial long line of accounting (except fiscal year)", func() { + setupTestData() + + // Add TAC/LOA records, with the LOA containing only some of the values + tac := factory.BuildTransportationAccountingCode(suite.DB(), []factory.Customization{ + { + Model: models.TransportationAccountingCode{ + TAC: *move.Orders.TAC, // TA + TacFnBlModCd: models.StringPointer("W"), + }, + }, + { + Model: models.LineOfAccounting{ + LoaSysID: models.IntPointer(123456), + LoaDptID: models.StringPointer("12"), // A1 + LoaTnsfrDptNm: models.StringPointer("1234"), // A2 + LoaBafID: models.StringPointer("1234"), // A4 + LoaTrsySfxTx: models.StringPointer("1234"), // A5 + LoaMajClmNm: models.StringPointer("1234"), // A6 + LoaOpAgncyID: models.StringPointer("1234"), // B1 + LoaAlltSnID: models.StringPointer("12345"), // B2 + LoaPgmElmntID: models.StringPointer("123456789012"), // C1 + LoaTskBdgtSblnTx: models.StringPointer("88888888"), // C2 + LoaDfAgncyAlctnRcpntID: models.StringPointer("1234"), // D1 + LoaJbOrdNm: models.StringPointer("1234567890"), // D4 + LoaSbaltmtRcpntID: models.StringPointer("1"), // D6 + LoaWkCntrRcpntNm: models.StringPointer("123456"), // D7 + LoaBgnDt: &sixMonthsBefore, // A3 (first part) + LoaEndDt: &sixMonthsAfter, // A3 (second part) + LoaHsGdsCd: models.StringPointer("HT"), + // rest of fields will be nil + }, + }, + }, nil) + + loa := tac.LineOfAccounting + + result, err := generator.Generate(suite.AppContextForTest(), paymentRequest, false) + suite.NoError(err) + + concatDate := fmt.Sprintf("%d%d", tac.LineOfAccounting.LoaBgnDt.Year(), tac.LineOfAccounting.LoaEndDt.Year()) + fa2Assertions := []struct { + expectedDetailCode edisegment.FA2DetailCode + expectedInfoCode *string + }{ + {edisegment.FA2DetailCodeTA, move.Orders.TAC}, + {edisegment.FA2DetailCodeA1, loa.LoaDptID}, + {edisegment.FA2DetailCodeA2, loa.LoaTnsfrDptNm}, + {edisegment.FA2DetailCodeA3, &concatDate}, + {edisegment.FA2DetailCodeA4, loa.LoaBafID}, + {edisegment.FA2DetailCodeA5, loa.LoaTrsySfxTx}, + {edisegment.FA2DetailCodeA6, loa.LoaMajClmNm}, + {edisegment.FA2DetailCodeB1, loa.LoaOpAgncyID}, + {edisegment.FA2DetailCodeB2, loa.LoaAlltSnID}, + {edisegment.FA2DetailCodeC1, loa.LoaPgmElmntID}, + {edisegment.FA2DetailCodeC2, loa.LoaTskBdgtSblnTx}, + {edisegment.FA2DetailCodeD1, loa.LoaDfAgncyAlctnRcpntID}, + {edisegment.FA2DetailCodeD4, loa.LoaJbOrdNm}, + {edisegment.FA2DetailCodeD6, loa.LoaSbaltmtRcpntID}, + {edisegment.FA2DetailCodeD7, loa.LoaWkCntrRcpntNm}, + } + + suite.Len(result.ServiceItems[0].FA2s, len(fa2Assertions)) + for i, fa2Assertion := range fa2Assertions { + fa2Segment := result.ServiceItems[0].FA2s[i] + suite.Equal(fa2Assertion.expectedDetailCode, fa2Segment.BreakdownStructureDetailCode) + suite.Equal(*fa2Assertion.expectedInfoCode, fa2Segment.FinancialInformationCode) + } + }) + +} + +func (suite *GHCInvoiceSuite) TestUseTacToFindLoa() { + mockClock := clock.NewMock() + currentTime := mockClock.Now() + sixMonthsBefore := currentTime.AddDate(0, -6, 0) + sixMonthsAfter := currentTime.AddDate(0, 6, 0) + basicPaymentServiceItemParams := []factory.CreatePaymentServiceItemParams{ + { + Key: models.ServiceItemParamNameContractCode, + KeyType: models.ServiceItemParamTypeString, + Value: factory.DefaultContractCode, + }, + { + Key: models.ServiceItemParamNameReferenceDate, + KeyType: models.ServiceItemParamTypeDate, + Value: currentTime.Format(testDateFormat), + }, + { + Key: models.ServiceItemParamNameWeightBilled, + KeyType: models.ServiceItemParamTypeInteger, + Value: "4242", + }, + { + Key: models.ServiceItemParamNameDistanceZip, + KeyType: models.ServiceItemParamTypeInteger, + Value: "24246", + }, + } + + generator := NewGHCPaymentRequestInvoiceGenerator(suite.icnSequencer, mockClock) + + hhgTAC := "1111" + ntsTAC := "2222" + + var move models.Move + var mtoShipment models.MTOShipment + var paymentRequest models.PaymentRequest + setupLoaTestData := func() { + allLoaHsGdsCds := []string{models.LineOfAccountingHouseholdGoodsCodeCivilian, models.LineOfAccountingHouseholdGoodsCodeEnlisted, models.LineOfAccountingHouseholdGoodsCodeDual, models.LineOfAccountingHouseholdGoodsCodeOfficer, models.LineOfAccountingHouseholdGoodsCodeNTS, models.LineOfAccountingHouseholdGoodsCodeOther} + for i := range allLoaHsGdsCds { + loa := factory.BuildFullLineOfAccounting(nil) + loa.LoaBgnDt = &sixMonthsBefore + loa.LoaEndDt = &sixMonthsAfter + loa.LoaHsGdsCd = &allLoaHsGdsCds[i] + // The LoaDocID is not used in our LOA selection logic, and it appears in the final EDI. + // Most of the fields that we use internally to identify or pick the LOA are carried through to the final + // EDI. So we can use this LoaDocID field to identify which LOA was used to generate an EDI. + // This is a hack. Hopefully there's a better way. + loa.LoaDocID = &allLoaHsGdsCds[i] + + factory.BuildTransportationAccountingCode(suite.DB(), []factory.Customization{ + { + Model: models.TransportationAccountingCode{ + TAC: *move.Orders.TAC, + TacFnBlModCd: models.StringPointer("W"), + }, + }, + { + Model: loa, + }, + }, nil) + + } + } + + setupTestData := func() { + move = factory.BuildMove(suite.DB(), []factory.Customization{ + { + Model: models.Order{ + TAC: &hhgTAC, + NtsTAC: &ntsTAC, + IssueDate: currentTime, + }, + }, + }, nil) + + paymentRequest = factory.BuildPaymentRequest(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + { + Model: models.PaymentRequest{ + IsFinal: false, + Status: models.PaymentRequestStatusReviewed, + }, + }, + }, nil) + mtoShipment = factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + }, nil) + + factory.BuildPaymentServiceItemWithParams( + suite.DB(), + models.ReServiceCodeDNPK, + basicPaymentServiceItemParams, + []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + { + Model: mtoShipment, + LinkOnly: true, + }, + { + Model: paymentRequest, + LinkOnly: true, + }, + { + Model: models.PaymentServiceItem{ + Status: models.PaymentServiceItemStatusApproved, + }, + }, + }, nil, + ) + } + + setupLOA := func(loahgc string) models.LineOfAccounting { + loa := factory.BuildFullLineOfAccounting(nil) + loa.LoaBgnDt = &sixMonthsBefore + loa.LoaEndDt = &sixMonthsAfter + loa.LoaHsGdsCd = &loahgc + // The LoaDocID is not used in our LOA selection logic, and it appears in the final EDI. + // Most of the fields that we use internally to identify or pick the LOA are carried through to the final + // EDI. So we can use this LoaDocID field to identify which LOA was used to generate an EDI. + // This is a hack. Hopefully there's a better way. + loa.LoaDocID = &loahgc + + return loa + } + + suite.Run("when there are multiple LOAs for a given TAC, the one matching the customer's rank should be used", func() { + setupTestData() + setupLoaTestData() + + rankTestCases := []struct { + rank models.ServiceMemberRank + expectedLoaCode string + }{ + {models.ServiceMemberRankE1, models.LineOfAccountingHouseholdGoodsCodeEnlisted}, + {models.ServiceMemberRankE2, models.LineOfAccountingHouseholdGoodsCodeEnlisted}, + {models.ServiceMemberRankE3, models.LineOfAccountingHouseholdGoodsCodeEnlisted}, + {models.ServiceMemberRankE4, models.LineOfAccountingHouseholdGoodsCodeEnlisted}, + {models.ServiceMemberRankE5, models.LineOfAccountingHouseholdGoodsCodeEnlisted}, + {models.ServiceMemberRankE6, models.LineOfAccountingHouseholdGoodsCodeEnlisted}, + {models.ServiceMemberRankE7, models.LineOfAccountingHouseholdGoodsCodeEnlisted}, + {models.ServiceMemberRankE8, models.LineOfAccountingHouseholdGoodsCodeEnlisted}, + {models.ServiceMemberRankE9, models.LineOfAccountingHouseholdGoodsCodeEnlisted}, + {models.ServiceMemberRankE9SPECIALSENIORENLISTED, models.LineOfAccountingHouseholdGoodsCodeEnlisted}, + {models.ServiceMemberRankO1ACADEMYGRADUATE, models.LineOfAccountingHouseholdGoodsCodeOfficer}, + {models.ServiceMemberRankO2, models.LineOfAccountingHouseholdGoodsCodeOfficer}, + {models.ServiceMemberRankO3, models.LineOfAccountingHouseholdGoodsCodeOfficer}, + {models.ServiceMemberRankO4, models.LineOfAccountingHouseholdGoodsCodeOfficer}, + {models.ServiceMemberRankO5, models.LineOfAccountingHouseholdGoodsCodeOfficer}, + {models.ServiceMemberRankO6, models.LineOfAccountingHouseholdGoodsCodeOfficer}, + {models.ServiceMemberRankO7, models.LineOfAccountingHouseholdGoodsCodeOfficer}, + {models.ServiceMemberRankO8, models.LineOfAccountingHouseholdGoodsCodeOfficer}, + {models.ServiceMemberRankO9, models.LineOfAccountingHouseholdGoodsCodeOfficer}, + {models.ServiceMemberRankO10, models.LineOfAccountingHouseholdGoodsCodeOfficer}, + {models.ServiceMemberRankW1, models.LineOfAccountingHouseholdGoodsCodeOfficer}, + {models.ServiceMemberRankW2, models.LineOfAccountingHouseholdGoodsCodeOfficer}, + {models.ServiceMemberRankW3, models.LineOfAccountingHouseholdGoodsCodeOfficer}, + {models.ServiceMemberRankW4, models.LineOfAccountingHouseholdGoodsCodeOfficer}, + {models.ServiceMemberRankW5, models.LineOfAccountingHouseholdGoodsCodeOfficer}, + {models.ServiceMemberRankAVIATIONCADET, models.LineOfAccountingHouseholdGoodsCodeOfficer}, + {models.ServiceMemberRankCIVILIANEMPLOYEE, models.LineOfAccountingHouseholdGoodsCodeCivilian}, + {models.ServiceMemberRankACADEMYCADET, models.LineOfAccountingHouseholdGoodsCodeOfficer}, + {models.ServiceMemberRankMIDSHIPMAN, models.LineOfAccountingHouseholdGoodsCodeOfficer}, + } + + for _, testCase := range rankTestCases { + // Update service member rank + move.Orders.ServiceMember.Rank = &testCase.rank + paymentRequest.MoveTaskOrder.Orders.ServiceMember.Rank = &testCase.rank + err := suite.DB().Save(&move.Orders.ServiceMember) + suite.NoError(err) + + // Create invoice + result, err := generator.Generate(suite.AppContextForTest(), paymentRequest, false) + suite.NoError(err) + + // Check if invoice used the LOA we expected. + // The doc ID field would not work like this in real data, i'm just using it + // to get what the test needs into the EDI. + var actualDocID string + for _, fa2 := range result.ServiceItems[0].FA2s { + if fa2.BreakdownStructureDetailCode == edisegment.FA2DetailCodeJ1 { + actualDocID = fa2.FinancialInformationCode + break + } + } + suite.NotNil(actualDocID) + suite.Equal(testCase.expectedLoaCode, actualDocID) + } }) + suite.Run("test that we still get an LOA if none match the service member's rank", func() { + setupTestData() + + // Create only civilian LOAs + loa := setupLOA(models.LineOfAccountingHouseholdGoodsCodeCivilian) + factory.BuildTransportationAccountingCode(suite.DB(), []factory.Customization{ + { + Model: models.TransportationAccountingCode{ + TAC: *move.Orders.TAC, + TacFnBlModCd: models.StringPointer("W"), + }, + }, + { + Model: loa, + }, + }, nil) + + // Update service member rank to E1 knowing there is only Civilian LOAs + testCaseRank := models.ServiceMemberRankE1 + move.Orders.ServiceMember.Rank = &testCaseRank + paymentRequest.MoveTaskOrder.Orders.ServiceMember.Rank = &testCaseRank + err := suite.DB().Save(&move.Orders.ServiceMember) + suite.NoError(err) + + // Create invoice + result, err := generator.Generate(suite.AppContextForTest(), paymentRequest, false) + suite.NoError(err) + + // Check if invoice used the LOA we expected. + // The doc ID field would not work like this in real data, i'm just using it + // to get what the test needs into the EDI. + var actualDocID string + for _, fa2 := range result.ServiceItems[0].FA2s { + if fa2.BreakdownStructureDetailCode == edisegment.FA2DetailCodeJ1 { + actualDocID = fa2.FinancialInformationCode + break + } + } + suite.NotNil(actualDocID) + + // Should have gotten the civilian LOA since that is all that exists + suite.Equal(models.LineOfAccountingHouseholdGoodsCodeCivilian, actualDocID) + }) + + suite.Run("test that the lowest tac_fn_bl_mod_cd is used as a tiebreaker", func() { + setupTestData() + + // Create lowest FBMC LOA (value=1) + lowestLoa := setupLOA(models.LineOfAccountingHouseholdGoodsCodeCivilian) + factory.BuildTransportationAccountingCode(suite.DB(), []factory.Customization{ + { + Model: models.TransportationAccountingCode{ + TAC: *move.Orders.TAC, + TacFnBlModCd: models.StringPointer("1"), + }, + }, + { + Model: lowestLoa, + }, + }, nil) + + // Create higher FBMC LOA (value=2) + higherLoa := setupLOA(models.LineOfAccountingHouseholdGoodsCodeOfficer) + factory.BuildTransportationAccountingCode(suite.DB(), []factory.Customization{ + { + Model: models.TransportationAccountingCode{ + TAC: *move.Orders.TAC, + TacFnBlModCd: models.StringPointer("2"), + }, + }, + { + Model: higherLoa, + }, + }, nil) + + // Create invoice + result, err := generator.Generate(suite.AppContextForTest(), paymentRequest, false) + suite.NoError(err) + + // Check if invoice used the LOA we expected. + // The doc ID field would not work like this in real data, i'm just using it + // to get what the test needs into the EDI. + var actualDocID string + for _, fa2 := range result.ServiceItems[0].FA2s { + if fa2.BreakdownStructureDetailCode == edisegment.FA2DetailCodeJ1 { + actualDocID = fa2.FinancialInformationCode + break + } + } + suite.NotNil(actualDocID) + + // Should have gotten the civilian LOA since that is the lower tac_fn_bl_mod_cd + suite.Equal(models.LineOfAccountingHouseholdGoodsCodeCivilian, actualDocID) + }) + + suite.Run("test the most recent loa_bgn_dt is used as a tiebreaker", func() { + setupTestData() + fiveYearsAgo := currentTime.AddDate(-5, 0, 0) + + // Create LOA with old datetime (loa_bgn_dt) and civilian code + loahgc := models.LineOfAccountingHouseholdGoodsCodeCivilian + oldLoa := factory.BuildFullLineOfAccounting(nil) + oldLoa.LoaBgnDt = &fiveYearsAgo + oldLoa.LoaEndDt = &sixMonthsAfter // Still need to overlap the order issue date to be included + oldLoa.LoaHsGdsCd = &loahgc + oldLoa.LoaDocID = &loahgc + + factory.BuildTransportationAccountingCode(suite.DB(), []factory.Customization{ + { + Model: models.TransportationAccountingCode{ + TAC: *move.Orders.TAC, + TacFnBlModCd: models.StringPointer("1"), + }, + }, + { + Model: oldLoa, + }, + }, nil) + + // Create newer loa with officer code + newLoa := setupLOA(models.LineOfAccountingHouseholdGoodsCodeOfficer) + factory.BuildTransportationAccountingCode(suite.DB(), []factory.Customization{ + { + Model: models.TransportationAccountingCode{ + TAC: *move.Orders.TAC, + TacFnBlModCd: models.StringPointer("1"), + }, + }, + { + Model: newLoa, + }, + }, nil) + + // Create invoice + result, err := generator.Generate(suite.AppContextForTest(), paymentRequest, false) + suite.NoError(err) + + // Check if invoice used the LOA we expected. + var actualDocID string + for _, fa2 := range result.ServiceItems[0].FA2s { + if fa2.BreakdownStructureDetailCode == edisegment.FA2DetailCodeJ1 { + actualDocID = fa2.FinancialInformationCode + break + } + } + suite.NotNil(actualDocID) + + // Should have gotten the officer LOA since that is the more recent loa_bgn_dt + suite.Equal(models.LineOfAccountingHouseholdGoodsCodeOfficer, actualDocID) + }) } func (suite *GHCInvoiceSuite) TestDetermineDutyLocationPhoneLinesFunc() { 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 bf18ffd9ab3..d616d13a555 100644 --- a/pkg/services/move_task_order/move_task_order_fetcher.go +++ b/pkg/services/move_task_order/move_task_order_fetcher.go @@ -6,6 +6,7 @@ import ( "github.com/gobuffalo/pop/v6" "github.com/gofrs/uuid" + "go.uber.org/zap" "github.com/transcom/mymove/pkg/appcontext" "github.com/transcom/mymove/pkg/apperror" @@ -233,34 +234,41 @@ func (f moveTaskOrderFetcher) GetMove(appCtx appcontext.AppContext, searchParams findMoveQuery := appCtx.DB().Q() if searchParams == nil { - return &models.Move{}, errors.New("searchParams should not be nil since move ID or locator are required") + return nil, errors.New("searchParams should not be nil since move ID or locator are required") } // Find the move by ID or Locator if searchParams.MoveTaskOrderID != uuid.Nil { - findMoveQuery.Where("id = $1", searchParams.MoveTaskOrderID) + findMoveQuery.Where("moves.id = ?", searchParams.MoveTaskOrderID) } else if searchParams.Locator != "" { - findMoveQuery.Where("locator = $1", searchParams.Locator) + findMoveQuery.Where("locator = ?", searchParams.Locator) } else { - return &models.Move{}, errors.New("searchParams should have either a move ID or locator set") + return nil, errors.New("searchParams should have either a move ID or locator set") } if len(eagerAssociations) > 0 { findMoveQuery.EagerPreload(eagerAssociations...) } + if appCtx.Session() != nil && appCtx.Session().IsMilApp() { + findMoveQuery. + InnerJoin("orders", "orders.id = moves.orders_id"). + Where("orders.service_member_id = ?", appCtx.Session().ServiceMemberID) + } + setMTOQueryFilters(findMoveQuery, searchParams) err := findMoveQuery.First(move) + if err != nil { + appCtx.Logger().Error("error fetching move", zap.Error(err)) switch err { case sql.ErrNoRows: - return &models.Move{}, apperror.NewNotFoundError(searchParams.MoveTaskOrderID, "") + return nil, apperror.NewNotFoundError(searchParams.MoveTaskOrderID, "") default: - return &models.Move{}, apperror.NewQueryError("Move", err, "") + return nil, apperror.NewQueryError("Move", err, "") } } - return move, 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 a401a8377da..88892bbe814 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 @@ -6,6 +6,8 @@ import ( "github.com/gofrs/uuid" + "github.com/transcom/mymove/pkg/apperror" + "github.com/transcom/mymove/pkg/auth" "github.com/transcom/mymove/pkg/factory" "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/services" @@ -303,9 +305,20 @@ func (suite *MoveTaskOrderServiceSuite) TestMoveTaskOrderFetcher() { suite.Error(err) }) - //test GetMove() - suite.Run("success getting a move using GetMove", func() { - expectedMTO, _ := setupTestData() +} + +func (suite *MoveTaskOrderServiceSuite) TestGetMoveTaskOrderFetcher() { + setupTestData := func() models.Move { + + expectedMTO := factory.BuildMove(suite.DB(), nil, nil) + + return expectedMTO + } + + mtoFetcher := NewMoveTaskOrderFetcher() + + suite.Run("success getting a move using GetMove for Prime user", func() { + expectedMTO := setupTestData() searchParams := services.MoveTaskOrderFetcherParams{ MoveTaskOrderID: expectedMTO.ID, } @@ -332,6 +345,80 @@ func (suite *MoveTaskOrderServiceSuite) TestMoveTaskOrderFetcher() { suite.Error(err) suite.Contains(err.Error(), "not found") }) + + suite.Run("Can fetch a move if it is a customer app request by the customer it belongs to", func() { + expectedMTO := factory.BuildMove(suite.DB(), factory.GetTraitActiveServiceMemberUser(), nil) + + serviceMember := expectedMTO.Orders.ServiceMember + searchParams := services.MoveTaskOrderFetcherParams{ + MoveTaskOrderID: expectedMTO.ID, + } + + appCtx := suite.AppContextWithSessionForTest(&auth.Session{ + ApplicationName: auth.MilApp, + UserID: serviceMember.User.ID, + ServiceMemberID: serviceMember.ID, + }) + + moveReturned, err := mtoFetcher.GetMove( + appCtx, + &searchParams, + ) + + if suite.NoError(err) && suite.NotNil(moveReturned) { + suite.Equal(expectedMTO.ID, moveReturned.ID) + } + }) + + suite.Run("Returns a not found error if it is a customer app request by a customer that it does not belong to", func() { + badUser := factory.BuildExtendedServiceMember(suite.DB(), factory.GetTraitActiveServiceMemberUser(), nil) + + appCtx := suite.AppContextWithSessionForTest(&auth.Session{ + ApplicationName: auth.MilApp, + UserID: badUser.User.ID, + ServiceMemberID: badUser.ID, + }) + + expectedMTO := factory.BuildMove(suite.DB(), factory.GetTraitActiveServiceMemberUser(), nil) + searchParams := services.MoveTaskOrderFetcherParams{ + MoveTaskOrderID: expectedMTO.ID, + } + + moveReturned, err := mtoFetcher.GetMove( + appCtx, + &searchParams, + ) + + if suite.Error(err) && suite.Nil(moveReturned) { + suite.IsType(apperror.NotFoundError{}, err) + + suite.Contains(err.Error(), fmt.Sprintf("ID: %s not found", expectedMTO.ID)) + } + }) + + suite.Run("success getting a move for Office user", func() { + officeUser := factory.BuildOfficeUser(suite.DB(), factory.GetTraitActiveOfficeUser(), nil) + expectedMTO := factory.BuildMove(suite.DB(), nil, nil) + + searchParams := services.MoveTaskOrderFetcherParams{ + MoveTaskOrderID: expectedMTO.ID, + } + + appCtx := suite.AppContextWithSessionForTest(&auth.Session{ + ApplicationName: auth.OfficeApp, + UserID: officeUser.User.ID, + OfficeUserID: officeUser.ID, + }) + + moveReturned, err := mtoFetcher.GetMove( + appCtx, + &searchParams, + ) + + if suite.NoError(err) && suite.NotNil(moveReturned) { + suite.Equal(expectedMTO.ID, moveReturned.ID) + } + }) } // Checks that there are expectedMatchCount matches between the moves and move ID list diff --git a/pkg/services/moving_expense/moving_expense_creator.go b/pkg/services/moving_expense/moving_expense_creator.go index 8064f44709b..9b31a9c9207 100644 --- a/pkg/services/moving_expense/moving_expense_creator.go +++ b/pkg/services/moving_expense/moving_expense_creator.go @@ -1,8 +1,6 @@ package movingexpense import ( - "fmt" - "github.com/gofrs/uuid" "github.com/transcom/mymove/pkg/appcontext" @@ -23,18 +21,22 @@ func NewMovingExpenseCreator() services.MovingExpenseCreator { } func (f *movingExpenseCreator) CreateMovingExpense(appCtx appcontext.AppContext, ppmShipmentID uuid.UUID) (*models.MovingExpense, error) { + // TODO: Ideally this service would be passed in as a dependency to the `NewMovingExpenseCreator` function. + // Our docs have an example, though instead of using the dependency in the service function, it is being used in + // the check functions, but the idea is similar: + // https://transcom.github.io/mymove-docs/docs/backend/guides/service-objects/implementation#creating-an-instance-of-our-service-object ppmShipmentFetcher := ppmshipment.NewPPMShipmentFetcher() - ppmShipment, ppmShipmentErr := ppmShipmentFetcher.GetPPMShipment(appCtx, ppmShipmentID, []string{ppmshipment.EagerPreloadAssociationServiceMember}, []string{}) + // This serves as a way of ensuring that the PPM shipment exists. It also ensures a shipment belongs to the logged + // in user, for customer app requests. + ppmShipment, ppmShipmentErr := ppmShipmentFetcher.GetPPMShipment(appCtx, ppmShipmentID, nil, nil) + if ppmShipmentErr != nil { - return nil, apperror.NewInternalServerError(fmt.Sprintf("Error fetching PPM with ID %s", ppmShipmentID)) + return nil, ppmShipmentErr } - if ppmShipment.Shipment.MoveTaskOrder.Orders.ServiceMemberID != appCtx.Session().ServiceMemberID { - return nil, apperror.NewNotFoundError(ppmShipmentID, "No such shipment found for this service member") - } newMovingExpense := &models.MovingExpense{ - PPMShipmentID: ppmShipmentID, + PPMShipmentID: ppmShipment.ID, Document: models.Document{ ServiceMemberID: appCtx.Session().ServiceMemberID, }, diff --git a/pkg/services/moving_expense/moving_expense_creator_test.go b/pkg/services/moving_expense/moving_expense_creator_test.go index 1242ec47a03..849c8148d79 100644 --- a/pkg/services/moving_expense/moving_expense_creator_test.go +++ b/pkg/services/moving_expense/moving_expense_creator_test.go @@ -1,8 +1,6 @@ package movingexpense import ( - "fmt" - "github.com/gofrs/uuid" "github.com/transcom/mymove/pkg/apperror" @@ -16,6 +14,7 @@ func (suite *MovingExpenseSuite) TestMovingExpenseCreator() { serviceMemberID := ppmShipment.Shipment.MoveTaskOrder.Orders.ServiceMemberID session := &auth.Session{ + ApplicationName: auth.MilApp, ServiceMemberID: serviceMemberID, } @@ -32,6 +31,7 @@ func (suite *MovingExpenseSuite) TestMovingExpenseCreator() { suite.Run("Fails when an invalid ppmShipmentID is used", func() { serviceMember := factory.BuildServiceMember(suite.DB(), nil, nil) session := &auth.Session{ + ApplicationName: auth.MilApp, ServiceMemberID: serviceMember.ID, } @@ -39,12 +39,16 @@ func (suite *MovingExpenseSuite) TestMovingExpenseCreator() { movingExpense, err := movingExpenseCreator.CreateMovingExpense(suite.AppContextWithSessionForTest(session), uuid.Nil) suite.Nil(movingExpense) - suite.ErrorContains(err, fmt.Sprintf("Error fetching PPM with ID %s", uuid.Nil)) + + expectedErr := apperror.NewNotFoundError(uuid.Nil, "while looking for PPMShipment") + + suite.ErrorIs(err, expectedErr) }) suite.Run("Fails when session has invalid serviceMemberID", func() { session := &auth.Session{ - ServiceMemberID: uuid.Nil, + ApplicationName: auth.MilApp, + ServiceMemberID: uuid.Must(uuid.NewV4()), } ppmShipment := factory.BuildMinimalPPMShipment(suite.DB(), nil, nil) @@ -52,9 +56,9 @@ func (suite *MovingExpenseSuite) TestMovingExpenseCreator() { movingExpense, err := movingExpenseCreator.CreateMovingExpense(suite.AppContextWithSessionForTest(session), ppmShipment.ID) suite.Nil(movingExpense) - suite.NotNil(err) - suite.IsType(apperror.NotFoundError{}, err) - suite.Contains(err.Error(), "No such shipment found for this service member") - }) + expectedErr := apperror.NewNotFoundError(ppmShipment.ID, "while looking for PPMShipment") + + suite.ErrorIs(err, expectedErr) + }) } 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 1ec24f372e2..ec88984fe09 100644 --- a/pkg/services/mto_service_item/mto_service_item_validators.go +++ b/pkg/services/mto_service_item/mto_service_item_validators.go @@ -299,6 +299,9 @@ func (v *updateMTOServiceItemData) setNewMTOServiceItem() *models.MTOServiceItem newMTOServiceItem.SITDepartureDate = services.SetOptionalDateTimeField( v.updatedServiceItem.SITDepartureDate, newMTOServiceItem.SITDepartureDate) + newMTOServiceItem.SITCustomerContacted = services.SetOptionalDateTimeField(v.updatedServiceItem.SITCustomerContacted, newMTOServiceItem.SITCustomerContacted) + newMTOServiceItem.SITRequestedDelivery = services.SetOptionalDateTimeField(v.updatedServiceItem.SITRequestedDelivery, newMTOServiceItem.SITRequestedDelivery) + if v.updatedServiceItem.SITDestinationFinalAddress != nil { newMTOServiceItem.SITDestinationFinalAddress = v.updatedServiceItem.SITDestinationFinalAddress newMTOServiceItem.SITDestinationFinalAddressID = &v.updatedServiceItem.SITDestinationFinalAddress.ID diff --git a/pkg/services/mto_service_item/mto_service_item_validators_test.go b/pkg/services/mto_service_item/mto_service_item_validators_test.go index 4c43b9e1fb0..23bdbffd4f9 100644 --- a/pkg/services/mto_service_item/mto_service_item_validators_test.go +++ b/pkg/services/mto_service_item/mto_service_item_validators_test.go @@ -20,6 +20,7 @@ func (suite *MTOServiceItemServiceSuite) TestUpdateMTOServiceItemData() { // Set up the data needed for updateMTOServiceItemData obj checker := movetaskorder.NewMoveTaskOrderChecker() now := time.Now() + later := now.AddDate(0, 0, 3) setupTestData := func() (models.MTOServiceItem, models.MTOServiceItem) { // Create a service item to serve as the old object oldServiceItem := testdatagen.MakeDefaultMTOServiceItem(suite.DB()) @@ -355,6 +356,8 @@ func (suite *MTOServiceItemServiceSuite) TestUpdateMTOServiceItemData() { FirstAvailableDeliveryDate: time.Now().AddDate(0, 0, 5), }, } + editServiceItem.SITCustomerContacted = &now + editServiceItem.SITRequestedDelivery = &later serviceItemData := updateMTOServiceItemData{ updatedServiceItem: editServiceItem, oldServiceItem: oldServiceItem, @@ -367,6 +370,8 @@ func (suite *MTOServiceItemServiceSuite) TestUpdateMTOServiceItemData() { suite.Nil(newServiceItem.ApprovedAt) suite.Equal(newServiceItem.SITEntryDate, editServiceItem.SITEntryDate) suite.Equal(newServiceItem.Description, editServiceItem.Description) + suite.Equal(*newServiceItem.SITCustomerContacted, *editServiceItem.SITCustomerContacted) + suite.Equal(*newServiceItem.SITRequestedDelivery, *editServiceItem.SITRequestedDelivery) suite.NotEqual(newServiceItem.Description, oldServiceItem.Description) suite.NotEqual(newServiceItem.Description, serviceItemData.oldServiceItem.Description) suite.NotEqual(newServiceItem.CustomerContacts[0].TimeMilitary, serviceItemData.oldServiceItem.CustomerContacts[0].TimeMilitary) diff --git a/pkg/services/payment_request/payment_request_recalculator_test.go b/pkg/services/payment_request/payment_request_recalculator_test.go index 4b508faeb7f..dff50362132 100644 --- a/pkg/services/payment_request/payment_request_recalculator_test.go +++ b/pkg/services/payment_request/payment_request_recalculator_test.go @@ -182,7 +182,7 @@ func (suite *PaymentRequestServiceSuite) TestRecalculatePaymentRequestSuccess() { paymentRequest: &oldPaymentRequest, serviceCode: models.ReServiceCodeDOASIT, - priceCents: unit.Cents(254645), + priceCents: unit.Cents(254640), paramsToCheck: []paramMap{ {models.ServiceItemParamNameWeightOriginal, strTestOriginalWeight}, {models.ServiceItemParamNameWeightBilled, strTestOriginalWeight}, @@ -228,7 +228,7 @@ func (suite *PaymentRequestServiceSuite) TestRecalculatePaymentRequestSuccess() isNewPaymentRequest: true, paymentRequest: newPaymentRequest, serviceCode: models.ReServiceCodeDOASIT, - priceCents: unit.Cents(237910), // Price same as before since new weight still in same weight bracket + priceCents: unit.Cents(237920), // Price same as before since new weight still in same weight bracket paramsToCheck: []paramMap{ {models.ServiceItemParamNameWeightOriginal, strTestChangedOriginalWeight}, {models.ServiceItemParamNameWeightBilled, strTestChangedOriginalWeight}, diff --git a/pkg/services/ppmshipment/ppm_shipment_fetcher.go b/pkg/services/ppmshipment/ppm_shipment_fetcher.go index fba3de9de42..452cb19f1d0 100644 --- a/pkg/services/ppmshipment/ppm_shipment_fetcher.go +++ b/pkg/services/ppmshipment/ppm_shipment_fetcher.go @@ -82,7 +82,12 @@ func GetListOfAllPostloadAssociations() []string { } // GetPPMShipment returns a PPMShipment with any desired associations by ID -func (f ppmShipmentFetcher) GetPPMShipment(appCtx appcontext.AppContext, ppmShipmentID uuid.UUID, eagerPreloadAssociations []string, postloadAssociations []string) (*models.PPMShipment, error) { +func (f ppmShipmentFetcher) GetPPMShipment( + appCtx appcontext.AppContext, + ppmShipmentID uuid.UUID, + eagerPreloadAssociations []string, + postloadAssociations []string, +) (*models.PPMShipment, error) { if eagerPreloadAssociations != nil { validPreloadAssociations := make(map[string]bool) for _, v := range GetListOfAllPreloadAssociations() { @@ -91,7 +96,9 @@ func (f ppmShipmentFetcher) GetPPMShipment(appCtx appcontext.AppContext, ppmShip for _, association := range eagerPreloadAssociations { if !validPreloadAssociations[association] { - return nil, apperror.NewNotImplementedError(fmt.Sprintf("Requested eager preload association %s is not implemented", association)) + msg := fmt.Sprintf("Requested eager preload association %s is not implemented", association) + + return nil, apperror.NewNotImplementedError(msg) } } } @@ -99,12 +106,20 @@ func (f ppmShipmentFetcher) GetPPMShipment(appCtx appcontext.AppContext, ppmShip var ppmShipment models.PPMShipment q := appCtx.DB().Q(). - Scope(utilities.ExcludeDeletedScope()) + Scope(utilities.ExcludeDeletedScope(models.PPMShipment{})) if eagerPreloadAssociations != nil { q.EagerPreload(eagerPreloadAssociations...) } + if appCtx.Session() != nil && appCtx.Session().IsMilApp() { + q. + InnerJoin("mto_shipments", "mto_shipments.id = ppm_shipments.shipment_id"). + InnerJoin("moves", "moves.id = mto_shipments.move_id"). + InnerJoin("orders", "orders.id = moves.orders_id"). + Where("orders.service_member_id = ?", appCtx.Session().ServiceMemberID) + } + err := q.Find(&ppmShipment, ppmShipmentID) if err != nil { @@ -132,7 +147,11 @@ func (f ppmShipmentFetcher) GetPPMShipment(appCtx appcontext.AppContext, ppmShip } // PostloadAssociations loads associations that can't be eager preloaded due to bugs in pop -func (f ppmShipmentFetcher) PostloadAssociations(appCtx appcontext.AppContext, ppmShipment *models.PPMShipment, postloadAssociations []string) error { +func (f ppmShipmentFetcher) PostloadAssociations( + appCtx appcontext.AppContext, + ppmShipment *models.PPMShipment, + postloadAssociations []string, +) error { for _, association := range postloadAssociations { switch association { case PostLoadAssociationSignedCertification: diff --git a/pkg/services/ppmshipment/ppm_shipment_fetcher_test.go b/pkg/services/ppmshipment/ppm_shipment_fetcher_test.go index 89b92eb469b..285c67847f7 100644 --- a/pkg/services/ppmshipment/ppm_shipment_fetcher_test.go +++ b/pkg/services/ppmshipment/ppm_shipment_fetcher_test.go @@ -7,6 +7,7 @@ import ( "github.com/gofrs/uuid" "github.com/transcom/mymove/pkg/apperror" + "github.com/transcom/mymove/pkg/auth" "github.com/transcom/mymove/pkg/db/utilities" "github.com/transcom/mymove/pkg/factory" "github.com/transcom/mymove/pkg/models" @@ -75,11 +76,13 @@ func (suite *PPMShipmentSuite) TestPPMShipmentFetcher() { } suite.Run("GetPPMShipment", func() { - suite.Run("Can fetch a PPM Shipment", func() { + suite.Run("Can fetch a PPM Shipment if there is no session (e.g. a prime request)", func() { + appCtx := suite.AppContextWithSessionForTest(nil) + ppmShipment := factory.BuildPPMShipment(suite.DB(), nil, nil) ppmShipmentReturned, err := fetcher.GetPPMShipment( - suite.AppContextForTest(), + appCtx, ppmShipment.ID, nil, nil, @@ -90,6 +93,76 @@ func (suite *PPMShipmentSuite) TestPPMShipmentFetcher() { } }) + suite.Run("Can fetch a PPM Shipment if it is an office user making a request from the office app", func() { + officeUser := factory.BuildOfficeUser(suite.DB(), factory.GetTraitActiveOfficeUser(), nil) + + appCtx := suite.AppContextWithSessionForTest(&auth.Session{ + ApplicationName: auth.OfficeApp, + UserID: officeUser.User.ID, + OfficeUserID: officeUser.ID, + }) + + ppmShipment := factory.BuildPPMShipment(suite.DB(), nil, nil) + + ppmShipmentReturned, err := fetcher.GetPPMShipment( + appCtx, + ppmShipment.ID, + nil, + nil, + ) + + if suite.NoError(err) && suite.NotNil(ppmShipmentReturned) { + suite.Equal(ppmShipment.ID, ppmShipmentReturned.ID) + } + }) + + suite.Run("Can fetch a PPM Shipment if it is a customer app request by the customer it belongs to", func() { + ppmShipment := factory.BuildPPMShipment(suite.DB(), factory.GetTraitActiveServiceMemberUser(), nil) + serviceMember := ppmShipment.Shipment.MoveTaskOrder.Orders.ServiceMember + + appCtx := suite.AppContextWithSessionForTest(&auth.Session{ + ApplicationName: auth.MilApp, + UserID: serviceMember.User.ID, + ServiceMemberID: serviceMember.ID, + }) + + ppmShipmentReturned, err := fetcher.GetPPMShipment( + appCtx, + ppmShipment.ID, + nil, + nil, + ) + + if suite.NoError(err) && suite.NotNil(ppmShipmentReturned) { + suite.Equal(ppmShipment.ID, ppmShipmentReturned.ID) + } + }) + + suite.Run("Returns a not found error if it is a customer app request by a customer that it doesn't belong to", func() { + maliciousUser := factory.BuildExtendedServiceMember(suite.DB(), factory.GetTraitActiveServiceMemberUser(), nil) + + appCtx := suite.AppContextWithSessionForTest(&auth.Session{ + ApplicationName: auth.MilApp, + UserID: maliciousUser.User.ID, + ServiceMemberID: maliciousUser.ID, + }) + + ppmShipment := factory.BuildPPMShipment(suite.DB(), factory.GetTraitActiveServiceMemberUser(), nil) + + ppmShipmentReturned, err := fetcher.GetPPMShipment( + appCtx, + ppmShipment.ID, + nil, + nil, + ) + + if suite.Error(err) && suite.Nil(ppmShipmentReturned) { + suite.IsType(apperror.NotFoundError{}, err) + + suite.Equal(fmt.Sprintf("ID: %s not found while looking for PPMShipment", ppmShipment.ID), err.Error()) + } + }) + associationTestCases := map[string]struct { eagerPreloadAssociations []string successAssertionFunc func(*models.PPMShipment, *models.PPMShipment) @@ -325,7 +398,7 @@ func (suite *PPMShipmentSuite) TestPPMShipmentFetcher() { suite.Run("Can fetch a ppm shipment and get both eagerPreloadAssociations and postloadAssociations", func() { appCtx := suite.AppContextForTest() - ppmShipment := factory.BuildPPMShipmentReadyForFinalCustomerCloseOut(suite.DB(), nil) + ppmShipment := factory.BuildPPMShipmentReadyForFinalCustomerCloseOut(suite.DB(), nil, nil) ppmShipmentReturned, err := fetcher.GetPPMShipment( appCtx, @@ -352,7 +425,7 @@ func (suite *PPMShipmentSuite) TestPPMShipmentFetcher() { suite.Run("Doesn't return postload association if a necessary higher level association isn't eagerly preloaded", func() { appCtx := suite.AppContextForTest() - ppmShipment := factory.BuildPPMShipmentReadyForFinalCustomerCloseOut(suite.DB(), userUploader) + ppmShipment := factory.BuildPPMShipmentReadyForFinalCustomerCloseOut(suite.DB(), userUploader, nil) suite.FatalTrue(len(ppmShipment.WeightTickets) > 0, "Test data that was set up is invalid, no weight tickets found") @@ -360,7 +433,9 @@ func (suite *PPMShipmentSuite) TestPPMShipmentFetcher() { appCtx, ppmShipment.ID, nil, - nil, + []string{ + PostLoadAssociationWeightTicketUploads, + }, ) if suite.NoError(err) && suite.NotNil(ppmShipmentReturned) { @@ -770,7 +845,7 @@ func (suite *PPMShipmentSuite) TestFetchPPMShipment() { }) suite.Run("FindPPMShipment - loads weight tickets association", func() { - ppmShipment := factory.BuildPPMShipmentReadyForFinalCustomerCloseOut(suite.DB(), nil) + ppmShipment := factory.BuildPPMShipmentReadyForFinalCustomerCloseOut(suite.DB(), nil, nil) // No uploads are added by default for the ProofOfTrailerOwnershipDocument to the WeightTicket model testdatagen.GetOrCreateDocumentWithUploads(suite.DB(), @@ -787,7 +862,7 @@ func (suite *PPMShipmentSuite) TestFetchPPMShipment() { }) suite.Run("FindPPMShipment - loads ProgearWeightTicket and MovingExpense associations", func() { - ppmShipment := factory.BuildPPMShipmentReadyForFinalCustomerCloseOut(suite.DB(), nil) + ppmShipment := factory.BuildPPMShipmentReadyForFinalCustomerCloseOut(suite.DB(), nil, nil) factory.BuildProgearWeightTicket(suite.DB(), []factory.Customization{ { @@ -815,7 +890,7 @@ func (suite *PPMShipmentSuite) TestFetchPPMShipment() { suite.Run("FindPPMShipment - loads signed certification", func() { signedCertification := factory.BuildSignedCertification(suite.DB(), nil, nil) - ppmShipment := factory.BuildPPMShipmentReadyForFinalCustomerCloseOut(suite.DB(), nil) + ppmShipment := factory.BuildPPMShipmentReadyForFinalCustomerCloseOut(suite.DB(), nil, nil) signedCertification.PpmID = &ppmShipment.ID suite.NoError(suite.DB().Save(&signedCertification)) @@ -859,7 +934,7 @@ func (suite *PPMShipmentSuite) TestFetchPPMShipment() { }) suite.Run("FindPPMShipment - deleted uploads are removed", func() { - ppmShipment := factory.BuildPPMShipmentReadyForFinalCustomerCloseOut(suite.DB(), nil) + ppmShipment := factory.BuildPPMShipmentReadyForFinalCustomerCloseOut(suite.DB(), nil, nil) testdatagen.GetOrCreateDocumentWithUploads(suite.DB(), ppmShipment.WeightTickets[0].ProofOfTrailerOwnershipDocument, @@ -982,7 +1057,7 @@ func (suite *PPMShipmentSuite) TestFetchPPMShipment() { }) suite.Run("FindPPMShipmentByMTOID - Success deleted line items are excluded", func() { - ppmShipment := factory.BuildPPMShipmentReadyForFinalCustomerCloseOut(suite.DB(), nil) + ppmShipment := factory.BuildPPMShipmentReadyForFinalCustomerCloseOut(suite.DB(), nil, nil) weightTicketToDelete := factory.BuildWeightTicket(suite.DB(), []factory.Customization{ { diff --git a/pkg/services/ppmshipment/ppm_shipment_new_submitter.go b/pkg/services/ppmshipment/ppm_shipment_new_submitter.go index 4deac62d9d9..abfebcd0b3f 100644 --- a/pkg/services/ppmshipment/ppm_shipment_new_submitter.go +++ b/pkg/services/ppmshipment/ppm_shipment_new_submitter.go @@ -12,16 +12,19 @@ import ( // ppmShipmentNewSubmitter is the concrete struct implementing the services.PPMShipmentNewSubmitter interface type ppmShipmentNewSubmitter struct { + services.PPMShipmentFetcher services.SignedCertificationCreator services.PPMShipmentRouter } // NewPPMShipmentNewSubmitter creates a new ppmShipmentNewSubmitter func NewPPMShipmentNewSubmitter( + ppmShipmentFetcher services.PPMShipmentFetcher, signedCertificationCreator services.SignedCertificationCreator, ppmShipmentRouter services.PPMShipmentRouter, ) services.PPMShipmentNewSubmitter { return &ppmShipmentNewSubmitter{ + PPMShipmentFetcher: ppmShipmentFetcher, SignedCertificationCreator: signedCertificationCreator, PPMShipmentRouter: ppmShipmentRouter, } @@ -33,7 +36,22 @@ func (p *ppmShipmentNewSubmitter) SubmitNewCustomerCloseOut(appCtx appcontext.Ap return nil, apperror.NewBadDataError("PPM ID is required") } - ppmShipment, err := FindPPMShipment(appCtx, ppmShipmentID) + ppmShipment, err := p.GetPPMShipment( + appCtx, + ppmShipmentID, + []string{ + EagerPreloadAssociationShipment, + EagerPreloadAssociationWeightTickets, + EagerPreloadAssociationProgearWeightTickets, + EagerPreloadAssociationMovingExpenses, + EagerPreloadAssociationW2Address, + }, []string{ + PostLoadAssociationSignedCertification, + PostLoadAssociationWeightTicketUploads, + PostLoadAssociationProgearWeightTicketUploads, + PostLoadAssociationMovingExpenseUploads, + }, + ) if err != nil { return nil, err diff --git a/pkg/services/ppmshipment/ppm_shipment_new_submitter_test.go b/pkg/services/ppmshipment/ppm_shipment_new_submitter_test.go index 53468c501dd..77089eac1e3 100644 --- a/pkg/services/ppmshipment/ppm_shipment_new_submitter_test.go +++ b/pkg/services/ppmshipment/ppm_shipment_new_submitter_test.go @@ -18,6 +18,35 @@ import ( ) func (suite *PPMShipmentSuite) TestSubmitNewCustomerCloseOut() { + refectchPPMShipment := func(ppmShipmentID uuid.UUID) *models.PPMShipment { + // The submitter uses a copier which runs into an issue because of all of the extra references our test data + // will have filled out because of how our factories work, including some circular references. In practice, + // we wouldn't have all of those relationships loaded at once, so the copier works fine during regular usage. + // Here we'll only retrieve the bare minimum. + + var ppmShipment models.PPMShipment + + err := suite.DB().EagerPreload(EagerPreloadAssociationShipment).Find(&ppmShipment, ppmShipmentID) + + suite.FatalNoError(err) + + return &ppmShipment + } + + setUpPPMShipmentFetcherMock := func(returnValue ...interface{}) services.PPMShipmentFetcher { + mockFetcher := &mocks.PPMShipmentFetcher{} + + mockFetcher.On( + "GetPPMShipment", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("uuid.UUID"), + mock.AnythingOfType("[]string"), + mock.AnythingOfType("[]string"), + ).Return(returnValue...) + + return mockFetcher + } + setUpSignedCertificationCreatorMock := func(returnValue ...interface{}) services.SignedCertificationCreator { mockCreator := &mocks.SignedCertificationCreator{} @@ -44,6 +73,7 @@ func (suite *PPMShipmentSuite) TestSubmitNewCustomerCloseOut() { suite.Run("Returns an error if PPM ID is invalid", func() { submitter := NewPPMShipmentNewSubmitter( + setUpPPMShipmentFetcherMock(nil, nil), setUpSignedCertificationCreatorMock(nil, nil), setUpPPMShipperRouterMock(nil), ) @@ -62,10 +92,13 @@ func (suite *PPMShipmentSuite) TestSubmitNewCustomerCloseOut() { } }) - suite.Run("Returns an error if PPM shipment does not exist", func() { + suite.Run("Returns an error if there is a failure fetching the PPM shipment", func() { nonexistentPPMShipmentID := uuid.Must(uuid.NewV4()) + fakeErr := apperror.NewNotFoundError(nonexistentPPMShipmentID, "while looking for PPMShipment") + submitter := NewPPMShipmentNewSubmitter( + setUpPPMShipmentFetcherMock(nil, fakeErr), setUpSignedCertificationCreatorMock(nil, nil), setUpPPMShipperRouterMock(nil), ) @@ -76,16 +109,12 @@ func (suite *PPMShipmentSuite) TestSubmitNewCustomerCloseOut() { models.SignedCertification{}, ) - if suite.Error(err) { - suite.Nil(updatedPPMShipment) - - suite.IsType(apperror.NotFoundError{}, err) - suite.Contains(err.Error(), "not found while looking for PPMShipment") - } + suite.ErrorIs(err, fakeErr) + suite.Nil(updatedPPMShipment) }) suite.Run("Returns an error if creating a new signed certification fails", func() { - existingPPMShipment := factory.BuildPPMShipmentReadyForFinalCustomerCloseOut(suite.DB(), nil) + existingPPMShipment := factory.BuildPPMShipmentReadyForFinalCustomerCloseOut(suite.DB(), nil, nil) appCtx := suite.AppContextWithSessionForTest(&auth.Session{ UserID: existingPPMShipment.Shipment.MoveTaskOrder.Orders.ServiceMember.UserID, @@ -94,7 +123,11 @@ func (suite *PPMShipmentSuite) TestSubmitNewCustomerCloseOut() { fakeErr := apperror.NewQueryError("SignedCertification", nil, "Unable to create signed certification") creator := setUpSignedCertificationCreatorMock(nil, fakeErr) + expectedShipment := refectchPPMShipment(existingPPMShipment.ID) + mockFetcher := setUpPPMShipmentFetcherMock(expectedShipment, nil) + submitter := NewPPMShipmentNewSubmitter( + mockFetcher, creator, setUpPPMShipperRouterMock(nil), ) @@ -114,7 +147,7 @@ func (suite *PPMShipmentSuite) TestSubmitNewCustomerCloseOut() { }) suite.Run("Returns an error if submitting the close out documentation fails", func() { - existingPPMShipment := factory.BuildPPMShipmentReadyForFinalCustomerCloseOut(suite.DB(), nil) + existingPPMShipment := factory.BuildPPMShipmentReadyForFinalCustomerCloseOut(suite.DB(), nil, nil) appCtx := suite.AppContextWithSessionForTest(&auth.Session{ UserID: existingPPMShipment.Shipment.MoveTaskOrder.Orders.ServiceMember.UserID, @@ -126,7 +159,11 @@ func (suite *PPMShipmentSuite) TestSubmitNewCustomerCloseOut() { ) router := setUpPPMShipperRouterMock(fakeErr) + expectedShipment := refectchPPMShipment(existingPPMShipment.ID) + mockFetcher := setUpPPMShipmentFetcherMock(expectedShipment, nil) + submitter := NewPPMShipmentNewSubmitter( + mockFetcher, setUpSignedCertificationCreatorMock(nil, nil), router, ) @@ -146,7 +183,7 @@ func (suite *PPMShipmentSuite) TestSubmitNewCustomerCloseOut() { }) suite.Run("Can create a signed certification and route the PPMShipment properly", func() { - existingPPMShipment := factory.BuildPPMShipmentReadyForFinalCustomerCloseOut(suite.DB(), nil) + existingPPMShipment := factory.BuildPPMShipmentReadyForFinalCustomerCloseOut(suite.DB(), nil, nil) appCtx := suite.AppContextWithSessionForTest(&auth.Session{ UserID: existingPPMShipment.Shipment.MoveTaskOrder.Orders.ServiceMember.UserID, @@ -183,7 +220,11 @@ func (suite *PPMShipmentSuite) TestSubmitNewCustomerCloseOut() { return nil }) + expectedShipment := refectchPPMShipment(existingPPMShipment.ID) + mockFetcher := setUpPPMShipmentFetcherMock(expectedShipment, nil) + submitter := NewPPMShipmentNewSubmitter( + mockFetcher, creator, router, ) diff --git a/pkg/services/ppmshipment/ppm_shipment_router_test.go b/pkg/services/ppmshipment/ppm_shipment_router_test.go index 4a75de2235b..86151ef3b62 100644 --- a/pkg/services/ppmshipment/ppm_shipment_router_test.go +++ b/pkg/services/ppmshipment/ppm_shipment_router_test.go @@ -302,7 +302,7 @@ func (suite *PPMShipmentSuite) TestSubmitCloseOutDocumentation() { mtoShipmentRouterMethodToMock := "" suite.Run(fmt.Sprintf("Can set status to %s if it is currently %s", models.PPMShipmentStatusNeedsPaymentApproval, models.PPMShipmentStatusWaitingOnCustomer), func() { - ppmShipment := factory.BuildPPMShipmentReadyForFinalCustomerCloseOut(nil, nil) + ppmShipment := factory.BuildPPMShipmentReadyForFinalCustomerCloseOut(nil, nil, nil) ppmShipmentRouter := setUpPPMShipmentRouter(mtoShipmentRouterMethodToMock, nil) @@ -390,7 +390,7 @@ func (suite *PPMShipmentSuite) TestSubmitReviewPPMDocuments() { mtoShipmentRouterMethodToMock := "" suite.Run("Update PPMShipment Status to WAITING_ON_CUSTOMER when there are rejected weight tickets", func() { - ppmShipment := factory.BuildPPMShipmentReadyForFinalCustomerCloseOut(nil, nil) + ppmShipment := factory.BuildPPMShipmentReadyForFinalCustomerCloseOut(nil, nil, nil) rejected := models.PPMDocumentStatusRejected weightTicket := factory.BuildWeightTicket(suite.DB(), []factory.Customization{ { @@ -415,7 +415,7 @@ func (suite *PPMShipmentSuite) TestSubmitReviewPPMDocuments() { }) suite.Run("Update PPMShipment Status to WAITING_ON_CUSTOMER when there are rejected progear weight tickets", func() { - ppmShipment := factory.BuildPPMShipmentReadyForFinalCustomerCloseOut(nil, nil) + ppmShipment := factory.BuildPPMShipmentReadyForFinalCustomerCloseOut(nil, nil, nil) ppmShipment.Status = models.PPMShipmentStatusNeedsPaymentApproval rejected := models.PPMDocumentStatusRejected progear := factory.BuildProgearWeightTicket(suite.DB(), []factory.Customization{ @@ -440,7 +440,7 @@ func (suite *PPMShipmentSuite) TestSubmitReviewPPMDocuments() { }) suite.Run("Update PPMShipment Status to WAITING_ON_CUSTOMER when there are rejected moving expenses", func() { - ppmShipment := factory.BuildPPMShipmentReadyForFinalCustomerCloseOut(nil, nil) + ppmShipment := factory.BuildPPMShipmentReadyForFinalCustomerCloseOut(nil, nil, nil) ppmShipment.Status = models.PPMShipmentStatusNeedsPaymentApproval rejected := models.PPMDocumentStatusRejected movingExpense := factory.BuildMovingExpense(suite.DB(), []factory.Customization{ @@ -465,7 +465,7 @@ func (suite *PPMShipmentSuite) TestSubmitReviewPPMDocuments() { }) suite.Run("Update PPMShipment Status to PAYMENT_APPROVED when there are no rejected PPM Documents", func() { - ppmShipment := factory.BuildPPMShipmentReadyForFinalCustomerCloseOut(nil, nil) + ppmShipment := factory.BuildPPMShipmentReadyForFinalCustomerCloseOut(nil, nil, nil) ppmShipment.Status = models.PPMShipmentStatusNeedsPaymentApproval movingExpense := factory.BuildMovingExpense(suite.DB(), nil, nil) progear := factory.BuildProgearWeightTicket(suite.DB(), nil, nil) diff --git a/pkg/services/ppmshipment/ppm_shipment_updated_submitter_test.go b/pkg/services/ppmshipment/ppm_shipment_updated_submitter_test.go index b5984c09eb5..a0580527625 100644 --- a/pkg/services/ppmshipment/ppm_shipment_updated_submitter_test.go +++ b/pkg/services/ppmshipment/ppm_shipment_updated_submitter_test.go @@ -120,7 +120,7 @@ func (suite *PPMShipmentSuite) TestSubmitCustomerCloseOut() { }) suite.Run("Returns an error if submitting the close out documentation fails", func() { - existingPPMShipment := factory.BuildPPMShipmentReadyForFinalCustomerCloseOut(suite.DB(), nil) + existingPPMShipment := factory.BuildPPMShipmentReadyForFinalCustomerCloseOut(suite.DB(), nil, nil) appCtx := suite.AppContextWithSessionForTest(&auth.Session{ UserID: existingPPMShipment.Shipment.MoveTaskOrder.Orders.ServiceMember.User.ID, diff --git a/pkg/services/progear_weight_ticket/progear_weight_ticket_creator.go b/pkg/services/progear_weight_ticket/progear_weight_ticket_creator.go index e842cac70ce..263495ac562 100644 --- a/pkg/services/progear_weight_ticket/progear_weight_ticket_creator.go +++ b/pkg/services/progear_weight_ticket/progear_weight_ticket_creator.go @@ -28,18 +28,19 @@ func (f *progearWeightTicketCreator) CreateProgearWeightTicket(appCtx appcontext return nil, err } + // TODO: Ideally this service would be passed in as a dependency to the `NewMovingExpenseCreator` function. + // Our docs have an example, though instead of using the dependency in the service function, it is being used in + // the check functions, but the idea is similar: + // https://transcom.github.io/mymove-docs/docs/backend/guides/service-objects/implementation#creating-an-instance-of-our-service-object shipmentFetcher := ppmshipment.NewPPMShipmentFetcher() - ppmShipment, ppmShipmentErr := shipmentFetcher.GetPPMShipment(appCtx, ppmShipmentID, []string{ - ppmshipment.EagerPreloadAssociationServiceMember, - }, []string{}) + // This serves as a way of ensuring that the PPM shipment exists. It also ensures a shipment belongs to the logged + // in user, for customer app requests. + ppmShipment, ppmShipmentErr := shipmentFetcher.GetPPMShipment(appCtx, ppmShipmentID, nil, nil) if ppmShipmentErr != nil { return nil, ppmShipmentErr } - if ppmShipment.Shipment.MoveTaskOrder.Orders.ServiceMemberID != appCtx.Session().ServiceMemberID { - return nil, apperror.NewNotFoundError(ppmShipmentID, "No such shipment found for this service member") - } var progearWeightTicket models.ProgearWeightTicket @@ -60,7 +61,7 @@ func (f *progearWeightTicketCreator) CreateProgearWeightTicket(appCtx appcontext progearWeightTicket = models.ProgearWeightTicket{ Document: *document, DocumentID: document.ID, - PPMShipmentID: ppmShipmentID, + PPMShipmentID: ppmShipment.ID, } verrs, err = txnCtx.DB().ValidateAndCreate(&progearWeightTicket) diff --git a/pkg/services/progear_weight_ticket/progear_weight_ticket_creator_test.go b/pkg/services/progear_weight_ticket/progear_weight_ticket_creator_test.go index 51ece3fe89f..97a962230f0 100644 --- a/pkg/services/progear_weight_ticket/progear_weight_ticket_creator_test.go +++ b/pkg/services/progear_weight_ticket/progear_weight_ticket_creator_test.go @@ -14,6 +14,7 @@ func (suite *ProgearWeightTicketSuite) TestProgearWeightTicketCreator() { serviceMemberID := ppmShipment.Shipment.MoveTaskOrder.Orders.ServiceMemberID session := &auth.Session{ + ApplicationName: auth.MilApp, ServiceMemberID: serviceMemberID, } @@ -30,6 +31,7 @@ func (suite *ProgearWeightTicketSuite) TestProgearWeightTicketCreator() { suite.Run("Fails when an invalid ppmShipmentID is used", func() { serviceMember := factory.BuildServiceMember(suite.DB(), nil, nil) session := &auth.Session{ + ApplicationName: auth.MilApp, ServiceMemberID: serviceMember.ID, } @@ -37,12 +39,16 @@ func (suite *ProgearWeightTicketSuite) TestProgearWeightTicketCreator() { progearWeightTicket, err := progearWeightTicketCreator.CreateProgearWeightTicket(suite.AppContextWithSessionForTest(session), uuid.Nil) suite.Nil(progearWeightTicket) - suite.NotNil(err) + + expectedErr := apperror.NewNotFoundError(uuid.Nil, "while looking for PPMShipment") + + suite.ErrorIs(err, expectedErr) }) suite.Run("Fails when session has invalid serviceMemberID", func() { session := &auth.Session{ - ServiceMemberID: uuid.Nil, + ApplicationName: auth.MilApp, + ServiceMemberID: uuid.Must(uuid.NewV4()), } ppmShipment := factory.BuildMinimalPPMShipment(suite.DB(), nil, nil) @@ -50,8 +56,9 @@ func (suite *ProgearWeightTicketSuite) TestProgearWeightTicketCreator() { progearWeightTicket, err := progearWeightTicketCreator.CreateProgearWeightTicket(suite.AppContextWithSessionForTest(session), ppmShipment.ID) suite.Nil(progearWeightTicket) - suite.NotNil(err) - suite.IsType(apperror.NotFoundError{}, err) - suite.Contains(err.Error(), "No such shipment found for this service member") + + expectedErr := apperror.NewNotFoundError(ppmShipment.ID, "while looking for PPMShipment") + + suite.ErrorIs(err, expectedErr) }) } diff --git a/pkg/services/weight_ticket/weight_ticket_creator.go b/pkg/services/weight_ticket/weight_ticket_creator.go index 80c5f1f97d0..4cbfc2234e5 100644 --- a/pkg/services/weight_ticket/weight_ticket_creator.go +++ b/pkg/services/weight_ticket/weight_ticket_creator.go @@ -7,6 +7,7 @@ import ( "github.com/transcom/mymove/pkg/apperror" "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/services/ppmshipment" ) type weightTicketCreator struct { @@ -27,6 +28,20 @@ func (f *weightTicketCreator) CreateWeightTicket(appCtx appcontext.AppContext, p return nil, err } + shipmentFetcher := ppmshipment.NewPPMShipmentFetcher() + + ppmShipment, ppmShipmentErr := shipmentFetcher.GetPPMShipment(appCtx, ppmShipmentID, []string{ + ppmshipment.EagerPreloadAssociationServiceMember, + }, []string{}) + + if ppmShipmentErr != nil { + return nil, ppmShipmentErr + } + + if ppmShipment.Shipment.MoveTaskOrder.Orders.ServiceMemberID != appCtx.Session().ServiceMemberID { + return nil, apperror.NewNotFoundError(ppmShipmentID, "No such shipment found for this service member") + } + var weightTicket models.WeightTicket txnErr := appCtx.NewTransaction(func(txnCtx appcontext.AppContext) error { diff --git a/pkg/services/weight_ticket/weight_ticket_creator_test.go b/pkg/services/weight_ticket/weight_ticket_creator_test.go index 57b69fff966..3902389afc9 100644 --- a/pkg/services/weight_ticket/weight_ticket_creator_test.go +++ b/pkg/services/weight_ticket/weight_ticket_creator_test.go @@ -10,12 +10,13 @@ import ( func (suite *WeightTicketSuite) TestWeightTicketCreator() { suite.Run("Successfully creates a WeightTicket", func() { - serviceMember := factory.BuildServiceMember(suite.DB(), nil, nil) + ppmShipment := factory.BuildMinimalPPMShipment(suite.DB(), nil, nil) + serviceMember := ppmShipment.Shipment.MoveTaskOrder.Orders.ServiceMember + session := &auth.Session{ ServiceMemberID: serviceMember.ID, } - ppmShipment := factory.BuildMinimalPPMShipment(suite.DB(), nil, nil) weightTicketCreator := NewCustomerWeightTicketCreator() weightTicket, err := weightTicketCreator.CreateWeightTicket(suite.AppContextWithSessionForTest(session), ppmShipment.ID) @@ -54,7 +55,7 @@ func (suite *WeightTicketSuite) TestWeightTicketCreator() { suite.Nil(weightTicket) suite.NotNil(err) - suite.IsType(apperror.InvalidInputError{}, err) - suite.Equal("Invalid input found while creating the Document.", err.Error()) + suite.IsType(apperror.NotFoundError{}, err) + suite.Contains(err.Error(), "No such shipment found for this service member") }) } diff --git a/pkg/testdatagen/testharness/dispatch.go b/pkg/testdatagen/testharness/dispatch.go index dcb65d372bd..13d8b723116 100644 --- a/pkg/testdatagen/testharness/dispatch.go +++ b/pkg/testdatagen/testharness/dispatch.go @@ -119,6 +119,9 @@ var actionDispatcher = map[string]actionFunc{ "PrimeSimulatorMoveNeedsShipmentUpdate": func(appCtx appcontext.AppContext) testHarnessResponse { return MakePrimeSimulatorMoveNeedsShipmentUpdate(appCtx) }, + "MakePrimeSimulatorMoveSameBasePointCity": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakePrimeSimulatorMoveSameBasePointCity(appCtx) + }, "NeedsOrdersUser": func(appCtx appcontext.AppContext) testHarnessResponse { return MakeNeedsOrdersUser(appCtx.DB()) }, diff --git a/pkg/testdatagen/testharness/make_move.go b/pkg/testdatagen/testharness/make_move.go index 53929163f4e..3710e1178e7 100644 --- a/pkg/testdatagen/testharness/make_move.go +++ b/pkg/testdatagen/testharness/make_move.go @@ -394,6 +394,90 @@ func MakeHHGMoveWithServiceItemsAndPaymentRequestsAndFilesForTOO(appCtx appconte } scenario.MakeSITExtensionsForShipment(appCtx, MTOShipment) + currentTimeDCRT := time.Now() + + basicPaymentServiceItemParamsDCRT := []factory.CreatePaymentServiceItemParams{ + { + Key: models.ServiceItemParamNameContractYearName, + KeyType: models.ServiceItemParamTypeString, + Value: factory.DefaultContractCode, + }, + { + Key: models.ServiceItemParamNameEscalationCompounded, + KeyType: models.ServiceItemParamTypeString, + Value: strconv.FormatFloat(1.125, 'f', 5, 64), + }, + { + Key: models.ServiceItemParamNamePriceRateOrFactor, + KeyType: models.ServiceItemParamTypeString, + Value: "1.71", + }, + { + Key: models.ServiceItemParamNameRequestedPickupDate, + KeyType: models.ServiceItemParamTypeDate, + Value: currentTimeDCRT.Format("2006-01-03"), + }, + { + Key: models.ServiceItemParamNameReferenceDate, + KeyType: models.ServiceItemParamTypeDate, + Value: currentTimeDCRT.Format("2006-01-03"), + }, + { + Key: models.ServiceItemParamNameCubicFeetBilled, + KeyType: models.ServiceItemParamTypeString, + Value: "4.00", + }, + { + Key: models.ServiceItemParamNameServicesScheduleOrigin, + KeyType: models.ServiceItemParamTypeInteger, + Value: strconv.Itoa(2), + }, + { + Key: models.ServiceItemParamNameServiceAreaOrigin, + KeyType: models.ServiceItemParamTypeInteger, + Value: "004", + }, + { + Key: models.ServiceItemParamNameZipPickupAddress, + KeyType: models.ServiceItemParamTypeString, + Value: "32210", + }, + { + Key: models.ServiceItemParamNameDimensionHeight, + KeyType: models.ServiceItemParamTypeString, + Value: "10", + }, + { + Key: models.ServiceItemParamNameDimensionLength, + KeyType: models.ServiceItemParamTypeString, + Value: "12", + }, + { + Key: models.ServiceItemParamNameDimensionWidth, + KeyType: models.ServiceItemParamTypeString, + Value: "3", + }, + } + + factory.BuildPaymentServiceItemWithParams( + appCtx.DB(), + models.ReServiceCodeDCRT, + basicPaymentServiceItemParamsDCRT, + []factory.Customization{ + { + Model: mto, + LinkOnly: true, + }, + { + Model: MTOShipment, + LinkOnly: true, + }, + { + Model: paymentRequest, + LinkOnly: true, + }, + }, nil) + dcrtCost := unit.Cents(99999) mtoServiceItemDCRT := testdatagen.MakeMTOServiceItemDomesticCrating(appCtx.DB(), testdatagen.Assertions{ Move: mto, @@ -676,6 +760,124 @@ func MakePrimeSimulatorMoveNeedsShipmentUpdate(appCtx appcontext.AppContext) mod return *newmove } +func MakePrimeSimulatorMoveSameBasePointCity(appCtx appcontext.AppContext) models.Move { + now := time.Now() + move := factory.BuildMove(appCtx.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusAPPROVED, + AvailableToPrimeAt: &now, + ApprovalsRequestedAt: &now, + SubmittedAt: &now, + }, + }, + }, nil) + factory.BuildMTOServiceItemBasic(appCtx.DB(), []factory.Customization{ + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusApproved, + }, + }, + { + Model: move, + LinkOnly: true, + }, + { + Model: models.ReService{ + Code: models.ReServiceCodeMS, + }, + }, + }, nil) + + requestedPickupDate := time.Now().AddDate(0, 3, 0) + requestedDeliveryDate := requestedPickupDate.AddDate(0, 1, 0) + pickupAddress := factory.BuildAddress(appCtx.DB(), []factory.Customization{ + { + Model: models.Address{ + ID: uuid.Must(uuid.NewV4()), + StreetAddress1: "1 First St", + StreetAddress2: models.StringPointer("Apt 1"), + City: "Miami Gardens", + State: "FL", + PostalCode: "33169", + Country: models.StringPointer("US"), + }, + }, + }, nil) + destinationAddress := factory.BuildAddress(appCtx.DB(), []factory.Customization{ + { + Model: models.Address{ + ID: uuid.Must(uuid.NewV4()), + StreetAddress1: "2 Second St", + StreetAddress2: models.StringPointer("Bldg 2"), + City: "Key West", + State: "FL", + PostalCode: "33040", + Country: models.StringPointer("US"), + }, + }, + }, nil) + + estimatedWeight := unit.Pound(1400) + actualWeight := unit.Pound(2000) + shipmentFields := models.MTOShipment{ + PrimeEstimatedWeight: &estimatedWeight, + PrimeActualWeight: &actualWeight, + Status: models.MTOShipmentStatusApproved, + RequestedPickupDate: &requestedPickupDate, + RequestedDeliveryDate: &requestedDeliveryDate, + } + + firstShipment := factory.BuildMTOShipmentMinimal(appCtx.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + { + Model: shipmentFields, + }, + { + Model: pickupAddress, + LinkOnly: true, + Type: &factory.Addresses.PickupAddress, + }, + { + Model: destinationAddress, + LinkOnly: true, + Type: &factory.Addresses.DeliveryAddress, + }, + }, nil) + + factory.BuildMTOServiceItem(appCtx.DB(), []factory.Customization{ + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusApproved, + }, + }, + { + Model: models.ReService{ + Code: models.ReServiceCodeDLH, + }, + }, + { + Model: firstShipment, + LinkOnly: true, + }, + { + Model: move, + LinkOnly: true, + }, + }, nil) + + // re-fetch the move so that we ensure we have exactly what is in + // the db + newmove, err := models.FetchMove(appCtx.DB(), &auth.Session{}, move.ID) + if err != nil { + log.Panic(fmt.Errorf("Failed to fetch move: %w", err)) + } + return *newmove +} + // MakeHHGMoveWithNTSAndNeedsSC is similar to old shared.createUserWithLocatorAndDODID func MakeHHGMoveWithNTSAndNeedsSC(appCtx appcontext.AppContext) models.Move { diff --git a/scripts/README.md b/scripts/README.md index 1c49b39d116..708f1c88ea2 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -11,17 +11,17 @@ If you want to see if scripts are not listed in this file you can run These scripts are primarily used for managing the developers environment. -| Script Name | Description | -| ---------------------- | -------------------------------------------------------------------------------------------------- | -| `check-changes` | checks for changes since the last `git pull` using `git diff` for any file changes to a given path | -| `check-go-version` | checks the go version required for the project | -| `check-gopath` | checks the go path is correct for the project | -| `check-hosts-file` | adds necessary entries to /etc/hosts | -| `check-node-version` | checks the node version required for the project | -| `kill-process-on-port` | asks to kill a process running on the specified port | -| `prereqs` | checks if all prerequisite programs have been installed | -| `server-dev` | Runs the milmove app server in dev | -| `setup` | installs all prerequisites and sets up the shell file | +| Script Name | Description | +| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `check-changes` | checks for changes since the last `git pull` using `git diff` for any file changes to a given path | +| `check-go-version` | checks the go version required for the project | +| `check-gopath` | checks the go path is correct for the project | +| `check-hosts-file` | adds necessary entries to /etc/hosts | +| `check-node-version` | checks the node version required for the project | +| `kill-process-on-port` | asks to kill a process running on the specified port | +| `prereqs` | checks if all prerequisite programs have been installed | +| `server-dev` | Runs the MilMove app server in dev using `air`. Use the `--help` flag for more information. This is similar to `make server_run` but does not use `gin`. | +| `setup` | installs all prerequisites and sets up the shell file | ## AWS Scripts diff --git a/src/App/index.jsx b/src/App/index.jsx index c3faa09a122..6ad79b33655 100644 --- a/src/App/index.jsx +++ b/src/App/index.jsx @@ -6,6 +6,7 @@ import { QueryClientProvider, QueryClient } from '@tanstack/react-query'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import LoadingPlaceholder from 'shared/LoadingPlaceholder'; +import SomethingWentWrong from 'shared/SomethingWentWrong'; import { isOfficeSite, isAdminSite, serviceName } from 'shared/constants'; import { store, persistor } from 'shared/store'; import { AppContext, defaultOfficeContext, defaultMyMoveContext, defaultAdminContext } from 'shared/AppContext'; @@ -15,6 +16,7 @@ import '../icons'; import 'shared/shared.css'; import './index.css'; import MarkerIO from 'components/ThirdParty/MarkerIO'; +import MilMoveErrorBoundary from 'components/MilMoveErrorBoundary'; import ScrollToTop from 'components/ScrollToTop'; import PageTitle from 'components/PageTitle'; @@ -58,53 +60,64 @@ const officeQueryConfig = new QueryClient({ const App = () => { configureLogger(serviceName(), { loggingType, loggingLevel }); + // We need an error boundary around each of the main apps (Office, + // SystemAdmin, MyMove) because they are lazy loaded and it's + // possible we could get a ChunkLoadError when trying to load them. + // Each of the main apps has its own componentDidCatch which would + // mean the MilMoveErrorBoundary is probably unlikely to be reached if (isOfficeSite) { return ( - - - } persistor={persistor}> - - - }> - - - - {flags.markerIO && } - - - - - - - + }> + + + } persistor={persistor}> + + + }> + + + + {flags.markerIO && } + + + + + + + + ); } if (isAdminSite) { return ( - - - }> - - - - - + }> + + + }> + + + + + + ); } return ( - - - - }> - - - - {flags.markerIO && } - - - - + }> + + + + }> + + + + {flags.markerIO && } + + + + + ); }; diff --git a/src/components/MilMoveErrorBoundary/MilMoveErrorBoundary.test.jsx b/src/components/MilMoveErrorBoundary/MilMoveErrorBoundary.test.jsx new file mode 100644 index 00000000000..0a64b751332 --- /dev/null +++ b/src/components/MilMoveErrorBoundary/MilMoveErrorBoundary.test.jsx @@ -0,0 +1,53 @@ +import React from 'react'; +import { render, screen, waitFor } from '@testing-library/react'; + +import MilMoveErrorBoundary from './index'; + +jest.mock('utils/milmoveLog', () => ({ + milmoveLogger: { + error: jest.fn(), + }, +})); + +jest.mock('utils/retryPageLoading', () => ({ + retryPageLoading: jest.fn(), +})); + +describe('MoveMoveErrorBoundary', () => { + // to quiet the test, mock console.error + beforeEach(() => { + jest.spyOn(console, 'error').mockImplementation(() => null); + }); + + const Fallback = () => { + return ( +
+ My Fallback +
+ ); + }; + it('catches errors', async () => { + const Thrower = () => { + throw new Error('MyError'); + }; + render( + }> + + , + ); + await waitFor(() => { + expect(screen.getByText('My Fallback')).toBeVisible(); + }); + }); + + it('shows children', () => { + render( + }> +
+ All Good +
+
, + ); + expect(screen.getByText('All Good')).toBeVisible(); + }); +}); diff --git a/src/components/MilMoveErrorBoundary/index.jsx b/src/components/MilMoveErrorBoundary/index.jsx new file mode 100644 index 00000000000..b1c1ed37523 --- /dev/null +++ b/src/components/MilMoveErrorBoundary/index.jsx @@ -0,0 +1,37 @@ +import React from 'react'; +import * as PropTypes from 'prop-types'; + +import { milmoveLogger } from 'utils/milmoveLog'; +import { retryPageLoading } from 'utils/retryPageLoading'; + +// This error boundary will probably not be reached for most errors. +// See more comments in App/index.jsx +export class MilMoveErrorBoundary extends React.Component { + constructor(props) { + super(props); + this.state = { hasError: false }; + } + + componentDidCatch(error, info) { + this.setState({ hasError: true }); + const { message } = error; + milmoveLogger.error({ message, info }); + retryPageLoading(error); + } + + render() { + const { hasError } = this.state; + const { children, fallback } = this.props; + if (hasError) { + return fallback; + } + return children; + } +} + +MilMoveErrorBoundary.propTypes = { + children: PropTypes.node.isRequired, + fallback: PropTypes.node.isRequired, +}; + +export default MilMoveErrorBoundary; diff --git a/src/components/Office/ShipmentAddressUpdateReviewRequestModal/ShipmentAddressUpdateReviewRequestModal.jsx b/src/components/Office/ShipmentAddressUpdateReviewRequestModal/ShipmentAddressUpdateReviewRequestModal.jsx index 12eafe62fd8..5c4b0823c0c 100644 --- a/src/components/Office/ShipmentAddressUpdateReviewRequestModal/ShipmentAddressUpdateReviewRequestModal.jsx +++ b/src/components/Office/ShipmentAddressUpdateReviewRequestModal/ShipmentAddressUpdateReviewRequestModal.jsx @@ -1,9 +1,10 @@ import React from 'react'; -import { Button, Textarea, Label, FormGroup, Radio } from '@trussworks/react-uswds'; // Tag Label +import { Alert, Button, Textarea, Label, FormGroup, Radio } from '@trussworks/react-uswds'; // Tag Label import { Formik, Field } from 'formik'; import * as Yup from 'yup'; import * as PropTypes from 'prop-types'; import classnames from 'classnames'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import styles from './ShipmentAddressUpdateReviewRequestModal.module.scss'; @@ -15,14 +16,19 @@ import ShipmentTag from 'components/ShipmentTag/ShipmentTag'; import { ShipmentShape } from 'types'; import Fieldset from 'shared/Fieldset'; import { ADDRESS_UPDATE_STATUS } from 'constants/shipments'; -import Alert from 'shared/Alert'; const formSchema = Yup.object().shape({ addressUpdateReviewStatus: Yup.string().required('Required'), officeRemarks: Yup.string().required('Required'), }); -export const ShipmentAddressUpdateReviewRequestModal = ({ onSubmit, shipment, errorMessage, onClose }) => { +export const ShipmentAddressUpdateReviewRequestModal = ({ + onSubmit, + shipment, + errorMessage, + setErrorMessage, + onClose, +}) => { const handleSubmit = async (values, { setSubmitting }) => { const { addressUpdateReviewStatus, officeRemarks } = values; @@ -31,6 +37,12 @@ export const ShipmentAddressUpdateReviewRequestModal = ({ onSubmit, shipment, er setSubmitting(false); }; + const errorMessageAlertControl = ( + + ); + return ( onClose()} /> @@ -38,7 +50,7 @@ export const ShipmentAddressUpdateReviewRequestModal = ({ onSubmit, shipment, er

Review request

{errorMessage && ( - + {errorMessage} )} @@ -111,10 +123,12 @@ ShipmentAddressUpdateReviewRequestModal.propTypes = { onSubmit: PropTypes.func.isRequired, onClose: PropTypes.func.isRequired, errorMessage: PropTypes.node, + setErrorMessage: PropTypes.func, }; ShipmentAddressUpdateReviewRequestModal.defaultProps = { errorMessage: null, + setErrorMessage: undefined, }; ShipmentAddressUpdateReviewRequestModal.displayName = 'ShipmentAddressUpdateReviewRequestModal'; diff --git a/src/components/Office/ShipmentAddressUpdateReviewRequestModal/ShipmentAddressUpdateReviewRequestModal.module.scss b/src/components/Office/ShipmentAddressUpdateReviewRequestModal/ShipmentAddressUpdateReviewRequestModal.module.scss index daca8f910a8..7fc1ced1153 100644 --- a/src/components/Office/ShipmentAddressUpdateReviewRequestModal/ShipmentAddressUpdateReviewRequestModal.module.scss +++ b/src/components/Office/ShipmentAddressUpdateReviewRequestModal/ShipmentAddressUpdateReviewRequestModal.module.scss @@ -6,6 +6,10 @@ padding-top: 1rem; } +.alertClose { + color: $base-darkest; +} + .modalbody { @include u-border('base-lighter'); @include u-border('1px'); diff --git a/src/components/Office/ShipmentForm/ShipmentForm.jsx b/src/components/Office/ShipmentForm/ShipmentForm.jsx index 3078c5104ab..a6ea263ff97 100644 --- a/src/components/Office/ShipmentForm/ShipmentForm.jsx +++ b/src/components/Office/ShipmentForm/ShipmentForm.jsx @@ -5,6 +5,7 @@ import { generatePath, useNavigate, useParams } from 'react-router-dom'; import { useQueryClient, useMutation } from '@tanstack/react-query'; import { Alert, Button, Checkbox, Fieldset, FormGroup, Radio } from '@trussworks/react-uswds'; import classNames from 'classnames'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import getShipmentOptions from '../../Customer/MtoShipmentForm/getShipmentOptions'; import { CloseoutOfficeInput } from '../../form/fields/CloseoutOfficeInput'; @@ -39,7 +40,6 @@ import { ADDRESS_UPDATE_STATUS, shipmentDestinationTypes } from 'constants/shipm import { officeRoles, roleTypes } from 'constants/userRoles'; import { deleteShipment, reviewShipmentAddressUpdate, updateMoveCloseoutOffice } from 'services/ghcApi'; import { SHIPMENT_OPTIONS } from 'shared/constants'; -import MilMoveAlert from 'shared/Alert'; import formStyles from 'styles/form.module.scss'; import { AccountingCodesShape } from 'types/accountingCodes'; import { AddressShape, SimpleAddressShape } from 'types/address'; @@ -170,6 +170,12 @@ const ShipmentForm = (props) => { setIsCancelModalVisible(true); }; + const successMessageAlertControl = ( + + ); + const deliveryAddressUpdateRequested = mtoShipment?.deliveryAddressUpdate?.status === ADDRESS_UPDATE_STATUS.REQUESTED; const isHHG = shipmentType === SHIPMENT_OPTIONS.HHG; @@ -526,6 +532,7 @@ const ShipmentForm = (props) => { ); }} errorMessage={shipmentAddressUpdateReviewErrorMessage} + setErrorMessage={setShipmentAddressUpdateReviewErrorMessage} /> {errorMessage && ( @@ -535,9 +542,9 @@ const ShipmentForm = (props) => { )} {successMessage && ( - setSuccessMessage(null)}> + {successMessage} - + )} {isTOO && mtoShipment.usesExternalVendor && ( diff --git a/src/components/Office/ShipmentForm/ShipmentForm.module.scss b/src/components/Office/ShipmentForm/ShipmentForm.module.scss index 7a5f4bb2789..2fff6281845 100644 --- a/src/components/Office/ShipmentForm/ShipmentForm.module.scss +++ b/src/components/Office/ShipmentForm/ShipmentForm.module.scss @@ -111,6 +111,10 @@ } } +.alertClose { + color: $base-darkest; +} + .Fieldset { h2 { @include u-margin-bottom(2); diff --git a/src/constants/routes.js b/src/constants/routes.js index 2c52066cf5c..9d569219d2a 100644 --- a/src/constants/routes.js +++ b/src/constants/routes.js @@ -120,6 +120,7 @@ export const primeSimulatorRoutes = { UPDATE_SHIPMENT_PATH: `${BASE_PRIME_SIMULATOR_PATH}/shipments/:shipmentId`, CREATE_PAYMENT_REQUEST_PATH: `${BASE_PRIME_SIMULATOR_PATH}/payment-requests/new`, CREATE_SERVICE_ITEM_PATH: `${BASE_PRIME_SIMULATOR_PATH}/shipments/:shipmentId/service-items/new`, + UPDATE_SERVICE_ITEMS_PATH: `${BASE_PRIME_SIMULATOR_PATH}/mto-service-items/update`, UPLOAD_DOCUMENTS_PATH: `${BASE_PRIME_SIMULATOR_PATH}/payment-requests/:paymentRequestId/upload`, UPLOAD_SERVICE_REQUEST_DOCUMENTS_PATH: `${BASE_PRIME_SIMULATOR_PATH}/mto-service-items/:mtoServiceItemId/upload`, SHIPMENT_UPDATE_ADDRESS_PATH: `${BASE_PRIME_SIMULATOR_PATH}/shipments/:shipmentId/addresses/update`, diff --git a/src/containers/LoginButton/LoginButton.jsx b/src/containers/LoginButton/LoginButton.jsx index 024a1fd4a95..532b5ec3e60 100644 --- a/src/containers/LoginButton/LoginButton.jsx +++ b/src/containers/LoginButton/LoginButton.jsx @@ -17,7 +17,7 @@ import ConnectedEulaModal from 'components/EulaModal'; import { customerRoutes } from 'constants/routes'; import { selectIsProfileComplete } from 'store/entities/selectors'; -const LoginButton = ({ isLoggedIn, logOut, showDevlocalButton, isProfileComplete }) => { +const LoginButton = ({ isLoggedIn, logOut, showDevlocalButton, showTestharnessList, isProfileComplete }) => { const [showEula, setShowEula] = useState(false); if (!isLoggedIn) { @@ -42,6 +42,13 @@ const LoginButton = ({ isLoggedIn, logOut, showDevlocalButton, isProfileComplete )} + {showTestharnessList && ( +
  • + + View Testharness Data Scenarios + +
  • + )}
  • @@ -115,10 +120,12 @@ SignIn.propTypes = { showLoginWarning: bool, }).isRequired, showLocalDevLogin: bool, + showTestharnessList: bool, }; SignIn.defaultProps = { showLocalDevLogin: isDevelopment, + showTestharnessList: isDevelopment, }; export default withContext(SignIn); diff --git a/src/registerServiceWorker.js b/src/registerServiceWorker.js index 0bd0a817935..6715a1128e6 100644 --- a/src/registerServiceWorker.js +++ b/src/registerServiceWorker.js @@ -84,7 +84,8 @@ export function unregister() { } export default function register() { - if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { + const isServiceWorkerEnv = process.env.NODE_ENV === 'production' || process.env.REACT_APP_SERVICE_WORKER === 'true'; + if (isServiceWorkerEnv && 'serviceWorker' in navigator) { // The URL constructor is available in all browsers that support SW. const publicUrl = new URL(process.env.PUBLIC_URL, window.location); if (publicUrl.origin !== window.location.origin) { diff --git a/src/scenes/MyMove/index.jsx b/src/scenes/MyMove/index.jsx index d3bc6e60db5..e832199b433 100644 --- a/src/scenes/MyMove/index.jsx +++ b/src/scenes/MyMove/index.jsx @@ -11,6 +11,7 @@ import { getWorkflowRoutes } from './getWorkflowRoutes'; // Logger import { milmoveLogger } from 'utils/milmoveLog'; +import { retryPageLoading } from 'utils/retryPageLoading'; import BypassBlock from 'components/BypassBlock'; import CUIHeader from 'components/CUIHeader/CUIHeader'; import LoggedOutHeader from 'containers/Headers/LoggedOutHeader'; @@ -102,6 +103,7 @@ export class CustomerApp extends Component { error, info, }); + retryPageLoading(error); } render() { diff --git a/src/scenes/SystemAdmin/index.jsx b/src/scenes/SystemAdmin/index.jsx index 08dcde4adde..45982b14369 100644 --- a/src/scenes/SystemAdmin/index.jsx +++ b/src/scenes/SystemAdmin/index.jsx @@ -7,6 +7,7 @@ import { GetLoggedInUser } from 'utils/api'; import CUIHeader from 'components/CUIHeader/CUIHeader'; // Logger import { milmoveLogger } from 'utils/milmoveLog'; +import { retryPageLoading } from 'utils/retryPageLoading'; // Lazy load these dependencies (they correspond to unique routes & only need to be loaded when that URL is accessed) const SignIn = lazy(() => import('pages/SignIn/SignIn')); const InvalidPermissions = lazy(() => import('pages/InvalidPermissions/InvalidPermissions')); @@ -28,6 +29,7 @@ class AdminWrapper extends Component { componentDidCatch(error, info) { const { message } = error; milmoveLogger.error({ message, info }); + retryPageLoading(error); } render() { diff --git a/src/services/primeApi.js b/src/services/primeApi.js index 45ba4c10dae..4b90e153ab8 100644 --- a/src/services/primeApi.js +++ b/src/services/primeApi.js @@ -96,6 +96,14 @@ export function createServiceItem({ body }) { return makePrimeSimulatorRequest('mtoServiceItem.createMTOServiceItem', { body: { ...body } }, { normalize: false }); } +export function createSITAddressUpdateRequest({ body }) { + return makePrimeSimulatorRequest( + 'sitAddressUpdate.createSITAddressUpdateRequest', + { body: { ...body } }, + { normalize: false }, + ); +} + export function updatePrimeMTOShipmentAddress({ mtoShipmentID, ifMatchETag, diff --git a/src/shared/SomethingWentWrong/index.jsx b/src/shared/SomethingWentWrong/index.jsx index b9995843a3a..406fc4d401e 100644 --- a/src/shared/SomethingWentWrong/index.jsx +++ b/src/shared/SomethingWentWrong/index.jsx @@ -2,7 +2,7 @@ import React from 'react'; import sadComputer from 'shared/images/sad-computer.png'; -const SomethingWentWrong = ({ error, info }) => ( +const SomethingWentWrong = () => (

    diff --git a/src/utils/retryPageLoading.js b/src/utils/retryPageLoading.js new file mode 100644 index 00000000000..dd04b2feac2 --- /dev/null +++ b/src/utils/retryPageLoading.js @@ -0,0 +1,18 @@ +const MilmoveHasBeenForceRefreshed = 'milmove-has-been-force-refreshed'; + +export const retryPageLoading = (error) => { + // if we see a chunk load error, try to reload the window to get + // the latest version of the code + if (!!error && error.name === 'ChunkLoadError' && !!window) { + const pageHasAlreadyBeenForceRefreshed = window.localStorage.getItem(MilmoveHasBeenForceRefreshed) === 'true'; + + if (!pageHasAlreadyBeenForceRefreshed) { + window.localStorage.setItem(MilmoveHasBeenForceRefreshed, 'true'); + return window.location.reload(); + } + window.localStorage.setItem(MilmoveHasBeenForceRefreshed, 'false'); + } + return false; +}; + +export default retryPageLoading; diff --git a/src/utils/retryPageLoading.test.js b/src/utils/retryPageLoading.test.js new file mode 100644 index 00000000000..68a95f403cd --- /dev/null +++ b/src/utils/retryPageLoading.test.js @@ -0,0 +1,51 @@ +import { retryPageLoading } from './retryPageLoading'; + +describe('retryPageLoading', () => { + let windowSpy; + let windowObj; + + const setUpWindow = (localStorageItem) => { + windowObj = { + localStorage: { + getItem: jest.fn().mockImplementation(() => localStorageItem), + setItem: jest.fn(), + }, + location: { + reload: jest.fn(), + }, + }; + windowSpy.mockImplementation(() => windowObj); + }; + + beforeEach(() => { + windowSpy = jest.spyOn(global, 'window', 'get'); + }); + + afterEach(() => { + windowSpy.mockRestore(); + }); + + it('does not reload on non chunk errors', () => { + setUpWindow(null); + retryPageLoading({ name: 'SomethingError' }); + expect(windowObj.localStorage.getItem).not.toBeCalled(); + expect(windowObj.localStorage.setItem).not.toBeCalled(); + expect(windowObj.location.reload).not.toBeCalled(); + }); + + it('reloads on first chunk error', () => { + setUpWindow('false'); + retryPageLoading({ name: 'ChunkLoadError' }); + expect(windowObj.localStorage.getItem).toBeCalled(); + expect(windowObj.localStorage.setItem).toBeCalledWith(expect.any(String), 'true'); + expect(windowObj.location.reload).toBeCalled(); + }); + + it('does not reload on 2nd chunk error', () => { + setUpWindow('true'); + retryPageLoading({ name: 'ChunkLoadError' }); + expect(windowObj.localStorage.getItem).toBeCalled(); + expect(windowObj.localStorage.setItem).toBeCalledWith(expect.any(String), 'false'); + expect(windowObj.location.reload).not.toBeCalled(); + }); +}); diff --git a/swagger-def/definitions/prime/MTOServiceItemDestSIT.yaml b/swagger-def/definitions/prime/MTOServiceItemDestSIT.yaml index 61acdfd3150..388e9a5bb7f 100644 --- a/swagger-def/definitions/prime/MTOServiceItemDestSIT.yaml +++ b/swagger-def/definitions/prime/MTOServiceItemDestSIT.yaml @@ -60,6 +60,16 @@ allOf: x-omitempty: false sitAddressUpdates: $ref: 'SitAddressUpdates.yaml' + sitRequestedDelivery: + format: date + type: string + description: Date when the customer has requested delivery out of SIT. + x-nullable: true + sitCustomerContacted: + format: date + type: string + description: Date when the customer contacted the prime for a delivery out of SIT. + x-nullable: true required: - reServiceCode - sitEntryDate diff --git a/swagger-def/prime.yaml b/swagger-def/prime.yaml index ce09268b5f7..95fcb934b58 100644 --- a/swagger-def/prime.yaml +++ b/swagger-def/prime.yaml @@ -36,7 +36,6 @@ tags: approved service item. sitAddressUpdates with a distance less than or equal to 50 miles will be automatically approved while a distance greater than 50 miles will typically require office user approval. - x-tagGroups: - name: Endpoints tags: @@ -1489,7 +1488,7 @@ definitions: type: array MoveTaskOrder: $ref: 'definitions/prime/MoveTaskOrder.yaml' - MTOServiceItemBasic: # spectral oas2-unused-definition is OK here due to polymorphism + MTOServiceItemBasic: # spectral oas2-unused-definition is OK here due to polymorphism $ref: 'definitions/prime/MTOServiceItemBasic.yaml' MTOServiceItemDestSIT: # spectral oas2-unused-definition is OK here due to polymorphism $ref: 'definitions/prime/MTOServiceItemDestSIT.yaml' @@ -1672,6 +1671,16 @@ definitions: type: string description: Second available date that Prime can deliver SIT service item. x-nullable: true + sitRequestedDelivery: + format: date + type: string + description: Date when the customer has requested delivery out of SIT. + x-nullable: true + sitCustomerContacted: + format: date + type: string + description: Date when the customer contacted the prime for a delivery out of SIT. + x-nullable: true UpdateMTOShipment: properties: scheduledPickupDate: diff --git a/swagger/prime.yaml b/swagger/prime.yaml index 70208006aed..fd8c78b4b9f 100644 --- a/swagger/prime.yaml +++ b/swagger/prime.yaml @@ -1892,6 +1892,18 @@ definitions: x-omitempty: false sitAddressUpdates: $ref: '#/definitions/SitAddressUpdates' + sitRequestedDelivery: + format: date + type: string + description: Date when the customer has requested delivery out of SIT. + x-nullable: true + sitCustomerContacted: + format: date + type: string + description: >- + Date when the customer contacted the prime for a delivery out of + SIT. + x-nullable: true required: - reServiceCode - sitEntryDate @@ -2226,6 +2238,18 @@ definitions: type: string description: Second available date that Prime can deliver SIT service item. x-nullable: true + sitRequestedDelivery: + format: date + type: string + description: Date when the customer has requested delivery out of SIT. + x-nullable: true + sitCustomerContacted: + format: date + type: string + description: >- + Date when the customer contacted the prime for a delivery out of + SIT. + x-nullable: true UpdateMTOShipment: properties: scheduledPickupDate: diff --git a/swagger/prime_v2.yaml b/swagger/prime_v2.yaml index edece313a53..cf3762510ad 100644 --- a/swagger/prime_v2.yaml +++ b/swagger/prime_v2.yaml @@ -155,6 +155,18 @@ definitions: x-omitempty: false sitAddressUpdates: $ref: '#/definitions/SitAddressUpdates' + sitRequestedDelivery: + format: date + type: string + description: Date when the customer has requested delivery out of SIT. + x-nullable: true + sitCustomerContacted: + format: date + type: string + description: >- + Date when the customer contacted the prime for a delivery out of + SIT. + x-nullable: true required: - reServiceCode - sitEntryDate