diff --git a/.envrc b/.envrc index 28215aae921..4d24f935289 100644 --- a/.envrc +++ b/.envrc @@ -170,6 +170,9 @@ export FEATURE_FLAG_VALIDATION_CODE_REQUIRED=false # We don't want this validati # Feature flag to disable/enable DODID validation and enforce unique constraints in the backend export FEATURE_FLAG_DODID_UNIQUE=false +# Maintenance Flag +require MAINTENANCE_FLAG "See 'DISABLE_AWS_VAULT_WRAPPER=1 AWS_REGION=us-gov-west-1 aws vault exec transcom-gov-dev -- chamber read app-devlocal maintenance_flag'" + # Okta.mil configuration # Tenant diff --git a/cmd/milmove/serve.go b/cmd/milmove/serve.go index cfddd0bfca4..505936d3868 100644 --- a/cmd/milmove/serve.go +++ b/cmd/milmove/serve.go @@ -124,6 +124,9 @@ func initServeFlags(flag *pflag.FlagSet) { // Telemetry flag config cli.InitTelemetryFlags(flag) + // Maintenance Flags + cli.InitMaintenanceFlags(flag) + // Sort command line flags flag.SortFlags = true } @@ -224,6 +227,10 @@ func checkServeConfig(v *viper.Viper, logger *zap.Logger) error { return err } + if err := cli.CheckMaintenance(v); err != nil { + return err + } + return cli.CheckFeatureFlag(v) } @@ -728,6 +735,7 @@ func serveFunction(cmd *cobra.Command, args []string) error { go startListener(healthServer, logger, false) } + maintenanceFlag := v.GetBool(cli.MaintenanceFlag) noTLSEnabled := v.GetBool(cli.NoTLSListenerFlag) var noTLSServer *server.NamedServer if noTLSEnabled { @@ -735,7 +743,7 @@ func serveFunction(cmd *cobra.Command, args []string) error { noTLSPort := v.GetInt(cli.NoTLSPortFlag) // initialize the router site, err := routing.InitRouting(serverName, appCtx, redisPool, - routingConfig, telemetryConfig) + routingConfig, telemetryConfig, maintenanceFlag) if err != nil { return err } @@ -760,7 +768,7 @@ func serveFunction(cmd *cobra.Command, args []string) error { tlsPort := v.GetInt(cli.TLSPortFlag) // initialize the router site, err := routing.InitRouting(serverName, appCtx, redisPool, - routingConfig, telemetryConfig) + routingConfig, telemetryConfig, maintenanceFlag) if err != nil { return err } @@ -786,7 +794,7 @@ func serveFunction(cmd *cobra.Command, args []string) error { mtlsPort := v.GetInt(cli.MutualTLSPortFlag) // initialize the router site, err := routing.InitRouting(serverName, appCtx, redisPool, - routingConfig, telemetryConfig) + routingConfig, telemetryConfig, maintenanceFlag) if err != nil { return err } diff --git a/go.mod b/go.mod index 1e80d6e73b1..e528f684f9d 100644 --- a/go.mod +++ b/go.mod @@ -95,12 +95,12 @@ require ( go.opentelemetry.io/otel/sdk/metric v1.28.0 go.opentelemetry.io/otel/trace v1.31.0 go.uber.org/zap v1.27.0 - golang.org/x/crypto v0.26.0 - golang.org/x/net v0.28.0 - golang.org/x/oauth2 v0.22.0 + golang.org/x/crypto v0.27.0 + golang.org/x/net v0.29.0 + golang.org/x/oauth2 v0.23.0 golang.org/x/text v0.18.0 golang.org/x/tools v0.24.0 - google.golang.org/grpc v1.65.0 + google.golang.org/grpc v1.68.0 gopkg.in/dnaeon/go-vcr.v3 v3.2.0 gotest.tools/gotestsum v1.12.0 pault.ag/go/pksigner v1.0.2 @@ -262,11 +262,11 @@ require ( golang.org/x/image v0.18.0 // indirect golang.org/x/mod v0.20.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.24.0 // indirect - golang.org/x/term v0.23.0 // indirect + golang.org/x/sys v0.25.0 // indirect + golang.org/x/term v0.24.0 // indirect google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect diff --git a/go.sum b/go.sum index 7926369f782..edfdd1c49f0 100644 --- a/go.sum +++ b/go.sum @@ -723,8 +723,8 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= @@ -773,12 +773,12 @@ golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= -golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= +golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -831,8 +831,8 @@ golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -849,8 +849,8 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= -golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= -golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= +golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -900,17 +900,17 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda h1:wu/KJm9KJwpfHWhkkZGohVC6KRrc1oJNr4jwtQMOQXw= google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda/go.mod h1:g2LLCvCeCSir/JJSWosk19BR4NVxGqHUC6rxIRsd7Aw= -google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 h1:0+ozOGcrp+Y8Aq8TLNN2Aliibms5LEzsq99ZZmAGYm0= -google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094/go.mod h1:fJ/e3If/Q67Mj99hin0hMhiNyCRmt6BQ2aWIJshUSJw= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc= +google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= -google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= +google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0= +google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= diff --git a/migrations/app/migrations_manifest.txt b/migrations/app/migrations_manifest.txt index c57c2ce4dc2..5e9fd7d7de1 100644 --- a/migrations/app/migrations_manifest.txt +++ b/migrations/app/migrations_manifest.txt @@ -1030,5 +1030,8 @@ 20241029144404_hdt-614-adjust-accomack-county.up.sql 20241107180705_add_alternative_AK_HI_duty_location_names.up.sql 20241109002854_add_gsr_table_to_move_history.up.sql +20241111203514_add_external_crate_and_remove_icrtsa.up.sql 20241111223224_change_international_sit_services_to_accessorials.up.sql 20241115214553_create_re_fsc_multipliers_table.up.sql +20241202163059_create_test_sequence_dev_env.up.sql +20241203024453_add_ppm_max_incentive_column.up.sql diff --git a/migrations/app/schema/20241111203514_add_external_crate_and_remove_icrtsa.up.sql b/migrations/app/schema/20241111203514_add_external_crate_and_remove_icrtsa.up.sql new file mode 100644 index 00000000000..14c1f37a751 --- /dev/null +++ b/migrations/app/schema/20241111203514_add_external_crate_and_remove_icrtsa.up.sql @@ -0,0 +1,13 @@ +-- Add external_crate to mto_service_items +ALTER TABLE mto_service_items ADD COLUMN IF NOT EXISTS external_crate bool NULL; +COMMENT ON COLUMN mto_service_items.external_crate IS 'Boolean value indicating whether the international crate is externally crated.'; + +-- removing 'International crating - standalone' (ICRTSA) from the tables +delete from service_params sp +where service_id in (select id from re_services where code in ('ICRTSA')); + +delete from re_intl_accessorial_prices reiap +where service_id in (select id from re_services where code in ('ICRTSA')); + +delete from re_services rs +where code in ('ICRTSA'); \ No newline at end of file diff --git a/migrations/app/schema/20241203024453_add_ppm_max_incentive_column.up.sql b/migrations/app/schema/20241203024453_add_ppm_max_incentive_column.up.sql new file mode 100644 index 00000000000..0d93306107b --- /dev/null +++ b/migrations/app/schema/20241203024453_add_ppm_max_incentive_column.up.sql @@ -0,0 +1,2 @@ +ALTER TABLE ppm_shipments ADD COLUMN IF NOT EXISTS max_incentive int; +COMMENT ON COLUMN ppm_shipments.max_incentive IS 'The max incentive a PPM can have, based on the max entitlement allowed.'; \ No newline at end of file diff --git a/migrations/app/secure/20241202163059_create_test_sequence_dev_env.up.sql b/migrations/app/secure/20241202163059_create_test_sequence_dev_env.up.sql new file mode 100644 index 00000000000..bab7b7da203 --- /dev/null +++ b/migrations/app/secure/20241202163059_create_test_sequence_dev_env.up.sql @@ -0,0 +1,7 @@ +-- Local test migration. +-- This will be run on development environments. +-- It should mirror what you intend to apply on loadtest/demo/exp/stg/prd +-- DO NOT include any sensitive data. + +-- Create test_sequence in migration, moved from sequencer_test.go +CREATE SEQUENCE IF NOT EXISTS test_sequence; \ No newline at end of file diff --git a/package.json b/package.json index b4c0f5b14f2..54d501cfe4b 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "@opentelemetry/core": "^1.15.1", "@tanstack/react-query": "^4.29.12", "@tanstack/react-query-devtools": "^5.17.12", - "@transcom/react-file-viewer": "git+https://github.com/transcom/react-file-viewer#v1.2.4", + "@transcom/react-file-viewer": "git+https://github.com/transcom/react-file-viewer#v1.2.5", "@trussworks/react-uswds": "3.2.0", "axe-playwright": "^1.2.3", "bytes": "^3.1.2", diff --git a/pkg/assets/sql_scripts/move_history_fetcher.sql b/pkg/assets/sql_scripts/move_history_fetcher.sql index 7e4a7d80d28..3f981dd6e70 100644 --- a/pkg/assets/sql_scripts/move_history_fetcher.sql +++ b/pkg/assets/sql_scripts/move_history_fetcher.sql @@ -362,6 +362,16 @@ WITH move AS ( FROM audit_history JOIN move_shipments ON move_shipments.secondary_delivery_address_id = audit_history.object_id AND audit_history."table_name" = 'addresses' UNION + SELECT + audit_history.object_id, + 'tertiaryDestinationAddress', + move_shipments.shipment_type, + move_shipments.id::TEXT, + NULL, + move_shipments.shipment_locator + FROM audit_history + JOIN move_shipments ON move_shipments.tertiary_delivery_address_id = audit_history.object_id AND audit_history."table_name" = 'addresses' + UNION SELECT audit_history.object_id, 'pickupAddress', @@ -382,6 +392,16 @@ WITH move AS ( FROM audit_history JOIN move_shipments ON move_shipments.secondary_pickup_address_id = audit_history.object_id AND audit_history."table_name" = 'addresses' UNION + SELECT + audit_history.object_id, + 'tertiaryPickupAddress', + move_shipments.shipment_type, + move_shipments.id::TEXT, + NULL, + move_shipments.shipment_locator + FROM audit_history + JOIN move_shipments ON move_shipments.tertiary_pickup_address_id = audit_history.object_id AND audit_history."table_name" = 'addresses' + UNION SELECT audit_history.object_id, 'residentialAddress', diff --git a/pkg/cli/maintenance.go b/pkg/cli/maintenance.go new file mode 100644 index 00000000000..ee584ec8f54 --- /dev/null +++ b/pkg/cli/maintenance.go @@ -0,0 +1,19 @@ +package cli + +import ( + "github.com/spf13/pflag" + "github.com/spf13/viper" +) + +const ( + // Maintenance Flag + MaintenanceFlag string = "maintenance_flag" +) + +func InitMaintenanceFlags(flag *pflag.FlagSet) { + flag.Bool(MaintenanceFlag, false, "Flag for tracking app maintenance.") +} + +func CheckMaintenance(v *viper.Viper) error { + return nil +} diff --git a/pkg/cli/maintenance_test.go b/pkg/cli/maintenance_test.go new file mode 100644 index 00000000000..f792e2c7cb8 --- /dev/null +++ b/pkg/cli/maintenance_test.go @@ -0,0 +1,6 @@ +package cli + +func (suite *cliTestSuite) TestMaintenance() { + suite.Setup(InitMaintenanceFlags, []string{}) + suite.NoError(CheckMaintenance(suite.viper)) +} diff --git a/pkg/db/sequence/sequencer_test.go b/pkg/db/sequence/sequencer_test.go index 5dd06751e01..cd2c7f56407 100644 --- a/pkg/db/sequence/sequencer_test.go +++ b/pkg/db/sequence/sequencer_test.go @@ -13,9 +13,7 @@ type SequenceSuite struct { } func (suite *SequenceSuite) SetupTest() { - err := suite.DB().RawQuery("CREATE SEQUENCE IF NOT EXISTS test_sequence;").Exec() - suite.NoError(err, "Error creating test sequence") - err = suite.DB().RawQuery("SELECT setval($1, 1);", testSequence).Exec() + err := suite.DB().RawQuery("SELECT setval($1, 1);", testSequence).Exec() suite.NoError(err, "Error resetting sequence") } diff --git a/pkg/factory/move_factory.go b/pkg/factory/move_factory.go index bdaf2692c95..e2192ae93f0 100644 --- a/pkg/factory/move_factory.go +++ b/pkg/factory/move_factory.go @@ -167,7 +167,7 @@ func BuildMoveWithShipment(db *pop.Connection, customs []Customization, traits [ func BuildMoveWithPPMShipment(db *pop.Connection, customs []Customization, traits []Trait) models.Move { move := BuildMove(db, customs, traits) - mtoShipment := buildMTOShipmentWithBuildType(db, customs, traits, mtoShipmentBuildBasic) + mtoShipment := buildMTOShipmentWithBuildType(db, customs, traits, mtoShipmentPPM) mtoShipment.MoveTaskOrder = move mtoShipment.MoveTaskOrderID = move.ID diff --git a/pkg/factory/mto_shipment_factory.go b/pkg/factory/mto_shipment_factory.go index 74f45e3d937..2be575fb33b 100644 --- a/pkg/factory/mto_shipment_factory.go +++ b/pkg/factory/mto_shipment_factory.go @@ -21,6 +21,7 @@ const ( mtoShipmentBuildBasic mtoShipmentBuildType = iota mtoShipmentBuild mtoShipmentNTS + mtoShipmentPPM mtoShipmentNTSR ) @@ -66,6 +67,9 @@ func buildMTOShipmentWithBuildType(db *pop.Connection, customs []Customization, shipmentHasDeliveryDetails = true case mtoShipmentBuildBasic: setupPickupAndDelivery = false + case mtoShipmentPPM: + defaultShipmentType = models.MTOShipmentTypePPM + setupPickupAndDelivery = false default: defaultShipmentType = models.MTOShipmentTypeHHG setupPickupAndDelivery = true diff --git a/pkg/gen/ghcapi/configure_mymove.go b/pkg/gen/ghcapi/configure_mymove.go index 9bdd30ed065..40957c936cf 100644 --- a/pkg/gen/ghcapi/configure_mymove.go +++ b/pkg/gen/ghcapi/configure_mymove.go @@ -110,6 +110,11 @@ func configureAPI(api *ghcoperations.MymoveAPI) http.Handler { return middleware.NotImplemented("operation payment_requests.BulkDownload has not yet been implemented") }) } + if api.MoveCheckForLockedMovesAndUnlockHandler == nil { + api.MoveCheckForLockedMovesAndUnlockHandler = move.CheckForLockedMovesAndUnlockHandlerFunc(func(params move.CheckForLockedMovesAndUnlockParams) middleware.Responder { + return middleware.NotImplemented("operation move.CheckForLockedMovesAndUnlock has not yet been implemented") + }) + } if api.OrderCounselingUpdateAllowanceHandler == nil { api.OrderCounselingUpdateAllowanceHandler = order.CounselingUpdateAllowanceHandlerFunc(func(params order.CounselingUpdateAllowanceParams) middleware.Responder { return middleware.NotImplemented("operation order.CounselingUpdateAllowance has not yet been implemented") diff --git a/pkg/gen/ghcapi/embedded_spec.go b/pkg/gen/ghcapi/embedded_spec.go index 266f7906d02..20d1791a2a1 100644 --- a/pkg/gen/ghcapi/embedded_spec.go +++ b/pkg/gen/ghcapi/embedded_spec.go @@ -439,7 +439,7 @@ func init() { "minLength": 1, "x-nullable": true }, - "dodID": { + "edipi": { "description": "DOD ID", "type": "string", "maxLength": 10, @@ -472,7 +472,7 @@ func init() { "type": "string", "enum": [ "customerName", - "dodID", + "edipi", "emplid", "branch", "personalEmail", @@ -2036,7 +2036,7 @@ func init() { "type": "string", "x-nullable": true }, - "dodID": { + "edipi": { "description": "DOD ID", "type": "string", "maxLength": 10, @@ -2094,7 +2094,7 @@ func init() { "type": "string", "enum": [ "customerName", - "dodID", + "edipi", "emplid", "branch", "locator", @@ -2563,7 +2563,7 @@ func init() { }, "/moves/{moveID}/financial-review-flag": { "post": { - "description": "This sets a flag which indicates that the move should be reviewed by a fincancial office. For example, if the origin or destination address of a shipment is far from the duty location and may incur excess costs to the customer.", + "description": "This sets a flag which indicates that the move should be reviewed by a fincancial office. For example, if the origin or delivery address of a shipment is far from the duty location and may incur excess costs to the customer.", "consumes": [ "application/json" ], @@ -2797,6 +2797,48 @@ func init() { ] } }, + "/moves/{officeUserID}/CheckForLockedMovesAndUnlock": { + "patch": { + "description": "Finds and unlocks any locked moves by an office user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "move" + ], + "operationId": "checkForLockedMovesAndUnlock", + "responses": { + "200": { + "description": "Successfully unlocked officer's move(s).", + "schema": { + "type": "object", + "properties": { + "successMessage": { + "type": "string", + "example": "OK" + } + } + } + }, + "500": { + "$ref": "#/responses/ServerError" + } + } + }, + "parameters": [ + { + "type": "string", + "format": "uuid", + "description": "ID of the move's officer", + "name": "officeUserID", + "in": "path", + "required": true + } + ] + }, "/mto-shipments": { "post": { "description": "Creates a MTO shipment for the specified Move Task Order.\nRequired fields include:\n* Shipment Type\n* Customer requested pick-up date\n* Pick-up Address\n* Delivery Address\n* Releasing / Receiving agents\nOptional fields include:\n* Delivery Address Type\n* Customer Remarks\n* Releasing / Receiving agents\n* An array of optional accessorial service item codes\n", @@ -4259,7 +4301,7 @@ func init() { { "enum": [ "customerName", - "dodID", + "edipi", "emplid", "branch", "locator", @@ -4318,7 +4360,7 @@ func init() { { "type": "string", "description": "filters to match the unique service member's DoD ID", - "name": "dodID", + "name": "edipi", "in": "query" }, { @@ -4513,7 +4555,7 @@ func init() { { "enum": [ "customerName", - "dodID", + "edipi", "emplid", "branch", "locator", @@ -4522,7 +4564,8 @@ func init() { "destinationDutyLocation", "requestedMoveDate", "appearedInTooAt", - "assignedTo" + "assignedTo", + "counselingOffice" ], "type": "string", "description": "field that results should be sorted by", @@ -4556,7 +4599,7 @@ func init() { }, { "type": "string", - "name": "dodID", + "name": "edipi", "in": "query" }, { @@ -4623,6 +4666,12 @@ func init() { "description": "Used to illustrate which user is assigned to this move.\n", "name": "assignedTo", "in": "query" + }, + { + "type": "string", + "description": "filters using a counselingOffice name of the move", + "name": "counselingOffice", + "in": "query" } ], "responses": { @@ -4660,11 +4709,12 @@ func init() { "submittedAt", "branch", "status", - "dodID", + "edipi", "emplid", "age", "originDutyLocation", - "assignedTo" + "assignedTo", + "counselingOffice" ], "type": "string", "description": "field that results should be sorted by", @@ -4717,7 +4767,7 @@ func init() { }, { "type": "string", - "name": "dodID", + "name": "edipi", "in": "query" }, { @@ -4741,6 +4791,12 @@ func init() { "name": "assignedTo", "in": "query" }, + { + "type": "string", + "description": "filters using a counselingOffice name of the move", + "name": "counselingOffice", + "in": "query" + }, { "uniqueItems": true, "type": "array", @@ -8983,6 +9039,10 @@ func init() { "x-nullable": true, "example": 2500 }, + "externalCrate": { + "type": "boolean", + "x-nullable": true + }, "feeType": { "type": "string", "enum": [ @@ -9002,6 +9062,16 @@ func init() { "format": "cents", "x-nullable": true }, + "market": { + "description": "To identify whether the service was provided within (CONUS) or (OCONUS)", + "type": "string", + "enum": [ + "CONUS", + "OCONUS" + ], + "x-nullable": true, + "example": "CONUS" + }, "moveTaskOrderID": { "type": "string", "format": "uuid", @@ -9307,7 +9377,7 @@ func init() { "MTOShipment": { "properties": { "actualDeliveryDate": { - "description": "The actual date that the shipment was delivered to the destination address by the Prime", + "description": "The actual date that the shipment was delivered to the delivery address by the Prime", "type": "string", "format": "date", "x-nullable": true @@ -9775,7 +9845,7 @@ func init() { "type": "string", "x-nullable": true, "readOnly": true, - "example": "Destination address is too far from duty location" + "example": "Delivery Address is too far from duty location" }, "id": { "type": "string", @@ -11464,6 +11534,13 @@ func init() { "x-omitempty": false, "example": false }, + "maxIncentive": { + "description": "The max amount the government will pay the service member to move their belongings based on the moving date, locations, and shipment weight.", + "type": "integer", + "format": "cents", + "x-nullable": true, + "x-omitempty": false + }, "movingExpenses": { "description": "All expense documentation receipt records of this PPM shipment.", "type": "array", @@ -12300,6 +12377,10 @@ func init() { "availableOfficeUsers": { "$ref": "#/definitions/AvailableOfficeUsers" }, + "counselingOffice": { + "type": "string", + "x-nullable": true + }, "customer": { "$ref": "#/definitions/Customer" }, @@ -12737,7 +12818,7 @@ func init() { "branch": { "type": "string" }, - "dodID": { + "edipi": { "type": "string", "x-nullable": true }, @@ -12813,7 +12894,7 @@ func init() { "destinationGBLOC": { "$ref": "#/definitions/GBLOC" }, - "dodID": { + "edipi": { "type": "string", "x-nullable": true, "example": 1234567890 @@ -13042,7 +13123,7 @@ func init() { } }, "ShipmentAddressUpdate": { - "description": "This represents a destination address change request made by the Prime that is either auto-approved or requires review if the pricing criteria has changed. If criteria has changed, then it must be approved or rejected by a TOO.\n", + "description": "This represents a delivery address change request made by the Prime that is either auto-approved or requires review if the pricing criteria has changed. If criteria has changed, then it must be approved or rejected by a TOO.\n", "type": "object", "required": [ "id", @@ -13070,7 +13151,7 @@ func init() { "$ref": "#/definitions/Address" }, "newSitDistanceBetween": { - "description": "The distance between the original SIT address and requested new destination address of shipment", + "description": "The distance between the original SIT address and requested new delivery address of shipment", "type": "integer", "example": 88 }, @@ -13082,7 +13163,7 @@ func init() { "example": "This is an office remark" }, "oldSitDistanceBetween": { - "description": "The distance between the original SIT address and the previous/old destination address of shipment", + "description": "The distance between the original SIT address and the previous/old delivery address of shipment", "type": "integer", "example": 50 }, @@ -15189,7 +15270,7 @@ func init() { "minLength": 1, "x-nullable": true }, - "dodID": { + "edipi": { "description": "DOD ID", "type": "string", "maxLength": 10, @@ -15222,7 +15303,7 @@ func init() { "type": "string", "enum": [ "customerName", - "dodID", + "edipi", "emplid", "branch", "personalEmail", @@ -17239,7 +17320,7 @@ func init() { "type": "string", "x-nullable": true }, - "dodID": { + "edipi": { "description": "DOD ID", "type": "string", "maxLength": 10, @@ -17297,7 +17378,7 @@ func init() { "type": "string", "enum": [ "customerName", - "dodID", + "edipi", "emplid", "branch", "locator", @@ -17880,7 +17961,7 @@ func init() { }, "/moves/{moveID}/financial-review-flag": { "post": { - "description": "This sets a flag which indicates that the move should be reviewed by a fincancial office. For example, if the origin or destination address of a shipment is far from the duty location and may incur excess costs to the customer.", + "description": "This sets a flag which indicates that the move should be reviewed by a fincancial office. For example, if the origin or delivery address of a shipment is far from the duty location and may incur excess costs to the customer.", "consumes": [ "application/json" ], @@ -18147,6 +18228,51 @@ func init() { ] } }, + "/moves/{officeUserID}/CheckForLockedMovesAndUnlock": { + "patch": { + "description": "Finds and unlocks any locked moves by an office user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "move" + ], + "operationId": "checkForLockedMovesAndUnlock", + "responses": { + "200": { + "description": "Successfully unlocked officer's move(s).", + "schema": { + "type": "object", + "properties": { + "successMessage": { + "type": "string", + "example": "OK" + } + } + } + }, + "500": { + "description": "A server error occurred", + "schema": { + "$ref": "#/definitions/Error" + } + } + } + }, + "parameters": [ + { + "type": "string", + "format": "uuid", + "description": "ID of the move's officer", + "name": "officeUserID", + "in": "path", + "required": true + } + ] + }, "/mto-shipments": { "post": { "description": "Creates a MTO shipment for the specified Move Task Order.\nRequired fields include:\n* Shipment Type\n* Customer requested pick-up date\n* Pick-up Address\n* Delivery Address\n* Releasing / Receiving agents\nOptional fields include:\n* Delivery Address Type\n* Customer Remarks\n* Releasing / Receiving agents\n* An array of optional accessorial service item codes\n", @@ -20038,7 +20164,7 @@ func init() { { "enum": [ "customerName", - "dodID", + "edipi", "emplid", "branch", "locator", @@ -20097,7 +20223,7 @@ func init() { { "type": "string", "description": "filters to match the unique service member's DoD ID", - "name": "dodID", + "name": "edipi", "in": "query" }, { @@ -20304,7 +20430,7 @@ func init() { { "enum": [ "customerName", - "dodID", + "edipi", "emplid", "branch", "locator", @@ -20313,7 +20439,8 @@ func init() { "destinationDutyLocation", "requestedMoveDate", "appearedInTooAt", - "assignedTo" + "assignedTo", + "counselingOffice" ], "type": "string", "description": "field that results should be sorted by", @@ -20347,7 +20474,7 @@ func init() { }, { "type": "string", - "name": "dodID", + "name": "edipi", "in": "query" }, { @@ -20414,6 +20541,12 @@ func init() { "description": "Used to illustrate which user is assigned to this move.\n", "name": "assignedTo", "in": "query" + }, + { + "type": "string", + "description": "filters using a counselingOffice name of the move", + "name": "counselingOffice", + "in": "query" } ], "responses": { @@ -20457,11 +20590,12 @@ func init() { "submittedAt", "branch", "status", - "dodID", + "edipi", "emplid", "age", "originDutyLocation", - "assignedTo" + "assignedTo", + "counselingOffice" ], "type": "string", "description": "field that results should be sorted by", @@ -20514,7 +20648,7 @@ func init() { }, { "type": "string", - "name": "dodID", + "name": "edipi", "in": "query" }, { @@ -20538,6 +20672,12 @@ func init() { "name": "assignedTo", "in": "query" }, + { + "type": "string", + "description": "filters using a counselingOffice name of the move", + "name": "counselingOffice", + "in": "query" + }, { "uniqueItems": true, "type": "array", @@ -25148,6 +25288,10 @@ func init() { "x-nullable": true, "example": 2500 }, + "externalCrate": { + "type": "boolean", + "x-nullable": true + }, "feeType": { "type": "string", "enum": [ @@ -25167,6 +25311,16 @@ func init() { "format": "cents", "x-nullable": true }, + "market": { + "description": "To identify whether the service was provided within (CONUS) or (OCONUS)", + "type": "string", + "enum": [ + "CONUS", + "OCONUS" + ], + "x-nullable": true, + "example": "CONUS" + }, "moveTaskOrderID": { "type": "string", "format": "uuid", @@ -25472,7 +25626,7 @@ func init() { "MTOShipment": { "properties": { "actualDeliveryDate": { - "description": "The actual date that the shipment was delivered to the destination address by the Prime", + "description": "The actual date that the shipment was delivered to the delivery address by the Prime", "type": "string", "format": "date", "x-nullable": true @@ -25940,7 +26094,7 @@ func init() { "type": "string", "x-nullable": true, "readOnly": true, - "example": "Destination address is too far from duty location" + "example": "Delivery Address is too far from duty location" }, "id": { "type": "string", @@ -27702,6 +27856,13 @@ func init() { "x-omitempty": false, "example": false }, + "maxIncentive": { + "description": "The max amount the government will pay the service member to move their belongings based on the moving date, locations, and shipment weight.", + "type": "integer", + "format": "cents", + "x-nullable": true, + "x-omitempty": false + }, "movingExpenses": { "description": "All expense documentation receipt records of this PPM shipment.", "type": "array", @@ -28540,6 +28701,10 @@ func init() { "availableOfficeUsers": { "$ref": "#/definitions/AvailableOfficeUsers" }, + "counselingOffice": { + "type": "string", + "x-nullable": true + }, "customer": { "$ref": "#/definitions/Customer" }, @@ -29027,7 +29192,7 @@ func init() { "branch": { "type": "string" }, - "dodID": { + "edipi": { "type": "string", "x-nullable": true }, @@ -29103,7 +29268,7 @@ func init() { "destinationGBLOC": { "$ref": "#/definitions/GBLOC" }, - "dodID": { + "edipi": { "type": "string", "x-nullable": true, "example": 1234567890 @@ -29332,7 +29497,7 @@ func init() { } }, "ShipmentAddressUpdate": { - "description": "This represents a destination address change request made by the Prime that is either auto-approved or requires review if the pricing criteria has changed. If criteria has changed, then it must be approved or rejected by a TOO.\n", + "description": "This represents a delivery address change request made by the Prime that is either auto-approved or requires review if the pricing criteria has changed. If criteria has changed, then it must be approved or rejected by a TOO.\n", "type": "object", "required": [ "id", @@ -29360,7 +29525,7 @@ func init() { "$ref": "#/definitions/Address" }, "newSitDistanceBetween": { - "description": "The distance between the original SIT address and requested new destination address of shipment", + "description": "The distance between the original SIT address and requested new delivery address of shipment", "type": "integer", "minimum": 0, "example": 88 @@ -29373,7 +29538,7 @@ func init() { "example": "This is an office remark" }, "oldSitDistanceBetween": { - "description": "The distance between the original SIT address and the previous/old destination address of shipment", + "description": "The distance between the original SIT address and the previous/old delivery address of shipment", "type": "integer", "minimum": 0, "example": 50 diff --git a/pkg/gen/ghcapi/ghcoperations/customer/search_customers.go b/pkg/gen/ghcapi/ghcoperations/customer/search_customers.go index 59e77ba6d94..02f2dc2ab3a 100644 --- a/pkg/gen/ghcapi/ghcoperations/customer/search_customers.go +++ b/pkg/gen/ghcapi/ghcoperations/customer/search_customers.go @@ -79,7 +79,7 @@ type SearchCustomersBody struct { // DOD ID // Max Length: 10 // Min Length: 10 - DodID *string `json:"dodID,omitempty"` + Edipi *string `json:"edipi,omitempty"` // EMPLID // Max Length: 7 @@ -97,7 +97,7 @@ type SearchCustomersBody struct { PerPage int64 `json:"perPage,omitempty"` // sort - // Enum: [customerName dodID emplid branch personalEmail telephone] + // Enum: [customerName edipi emplid branch personalEmail telephone] Sort *string `json:"sort,omitempty"` } @@ -113,7 +113,7 @@ func (o *SearchCustomersBody) Validate(formats strfmt.Registry) error { res = append(res, err) } - if err := o.validateDodID(formats); err != nil { + if err := o.validateEdipi(formats); err != nil { res = append(res, err) } @@ -159,16 +159,16 @@ func (o *SearchCustomersBody) validateCustomerName(formats strfmt.Registry) erro return nil } -func (o *SearchCustomersBody) validateDodID(formats strfmt.Registry) error { - if swag.IsZero(o.DodID) { // not required +func (o *SearchCustomersBody) validateEdipi(formats strfmt.Registry) error { + if swag.IsZero(o.Edipi) { // not required return nil } - if err := validate.MinLength("body"+"."+"dodID", "body", *o.DodID, 10); err != nil { + if err := validate.MinLength("body"+"."+"edipi", "body", *o.Edipi, 10); err != nil { return err } - if err := validate.MaxLength("body"+"."+"dodID", "body", *o.DodID, 10); err != nil { + if err := validate.MaxLength("body"+"."+"edipi", "body", *o.Edipi, 10); err != nil { return err } @@ -237,7 +237,7 @@ var searchCustomersBodyTypeSortPropEnum []interface{} func init() { var res []string - if err := json.Unmarshal([]byte(`["customerName","dodID","emplid","branch","personalEmail","telephone"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["customerName","edipi","emplid","branch","personalEmail","telephone"]`), &res); err != nil { panic(err) } for _, v := range res { @@ -250,8 +250,8 @@ const ( // SearchCustomersBodySortCustomerName captures enum value "customerName" SearchCustomersBodySortCustomerName string = "customerName" - // SearchCustomersBodySortDodID captures enum value "dodID" - SearchCustomersBodySortDodID string = "dodID" + // SearchCustomersBodySortEdipi captures enum value "edipi" + SearchCustomersBodySortEdipi string = "edipi" // SearchCustomersBodySortEmplid captures enum value "emplid" SearchCustomersBodySortEmplid string = "emplid" diff --git a/pkg/gen/ghcapi/ghcoperations/move/check_for_locked_moves_and_unlock.go b/pkg/gen/ghcapi/ghcoperations/move/check_for_locked_moves_and_unlock.go new file mode 100644 index 00000000000..1d723e61825 --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/move/check_for_locked_moves_and_unlock.go @@ -0,0 +1,97 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package move + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "context" + "net/http" + + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// CheckForLockedMovesAndUnlockHandlerFunc turns a function with the right signature into a check for locked moves and unlock handler +type CheckForLockedMovesAndUnlockHandlerFunc func(CheckForLockedMovesAndUnlockParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn CheckForLockedMovesAndUnlockHandlerFunc) Handle(params CheckForLockedMovesAndUnlockParams) middleware.Responder { + return fn(params) +} + +// CheckForLockedMovesAndUnlockHandler interface for that can handle valid check for locked moves and unlock params +type CheckForLockedMovesAndUnlockHandler interface { + Handle(CheckForLockedMovesAndUnlockParams) middleware.Responder +} + +// NewCheckForLockedMovesAndUnlock creates a new http.Handler for the check for locked moves and unlock operation +func NewCheckForLockedMovesAndUnlock(ctx *middleware.Context, handler CheckForLockedMovesAndUnlockHandler) *CheckForLockedMovesAndUnlock { + return &CheckForLockedMovesAndUnlock{Context: ctx, Handler: handler} +} + +/* + CheckForLockedMovesAndUnlock swagger:route PATCH /moves/{officeUserID}/CheckForLockedMovesAndUnlock move checkForLockedMovesAndUnlock + +Finds and unlocks any locked moves by an office user +*/ +type CheckForLockedMovesAndUnlock struct { + Context *middleware.Context + Handler CheckForLockedMovesAndUnlockHandler +} + +func (o *CheckForLockedMovesAndUnlock) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewCheckForLockedMovesAndUnlockParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} + +// CheckForLockedMovesAndUnlockOKBody check for locked moves and unlock o k body +// +// swagger:model CheckForLockedMovesAndUnlockOKBody +type CheckForLockedMovesAndUnlockOKBody struct { + + // success message + // Example: OK + SuccessMessage string `json:"successMessage,omitempty"` +} + +// Validate validates this check for locked moves and unlock o k body +func (o *CheckForLockedMovesAndUnlockOKBody) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this check for locked moves and unlock o k body based on context it is used +func (o *CheckForLockedMovesAndUnlockOKBody) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (o *CheckForLockedMovesAndUnlockOKBody) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *CheckForLockedMovesAndUnlockOKBody) UnmarshalBinary(b []byte) error { + var res CheckForLockedMovesAndUnlockOKBody + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} diff --git a/pkg/gen/ghcapi/ghcoperations/move/check_for_locked_moves_and_unlock_parameters.go b/pkg/gen/ghcapi/ghcoperations/move/check_for_locked_moves_and_unlock_parameters.go new file mode 100644 index 00000000000..46162a72b81 --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/move/check_for_locked_moves_and_unlock_parameters.go @@ -0,0 +1,91 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package move + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/validate" +) + +// NewCheckForLockedMovesAndUnlockParams creates a new CheckForLockedMovesAndUnlockParams object +// +// There are no default values defined in the spec. +func NewCheckForLockedMovesAndUnlockParams() CheckForLockedMovesAndUnlockParams { + + return CheckForLockedMovesAndUnlockParams{} +} + +// CheckForLockedMovesAndUnlockParams contains all the bound params for the check for locked moves and unlock operation +// typically these are obtained from a http.Request +// +// swagger:parameters checkForLockedMovesAndUnlock +type CheckForLockedMovesAndUnlockParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /*ID of the move's officer + Required: true + In: path + */ + OfficeUserID strfmt.UUID +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewCheckForLockedMovesAndUnlockParams() beforehand. +func (o *CheckForLockedMovesAndUnlockParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + rOfficeUserID, rhkOfficeUserID, _ := route.Params.GetOK("officeUserID") + if err := o.bindOfficeUserID(rOfficeUserID, rhkOfficeUserID, route.Formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindOfficeUserID binds and validates parameter OfficeUserID from path. +func (o *CheckForLockedMovesAndUnlockParams) bindOfficeUserID(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: true + // Parameter is provided by construction from the route + + // Format: uuid + value, err := formats.Parse("uuid", raw) + if err != nil { + return errors.InvalidType("officeUserID", "path", "strfmt.UUID", raw) + } + o.OfficeUserID = *(value.(*strfmt.UUID)) + + if err := o.validateOfficeUserID(formats); err != nil { + return err + } + + return nil +} + +// validateOfficeUserID carries on validations for parameter OfficeUserID +func (o *CheckForLockedMovesAndUnlockParams) validateOfficeUserID(formats strfmt.Registry) error { + + if err := validate.FormatOf("officeUserID", "path", "uuid", o.OfficeUserID.String(), formats); err != nil { + return err + } + return nil +} diff --git a/pkg/gen/ghcapi/ghcoperations/move/check_for_locked_moves_and_unlock_responses.go b/pkg/gen/ghcapi/ghcoperations/move/check_for_locked_moves_and_unlock_responses.go new file mode 100644 index 00000000000..8b5abbefb35 --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/move/check_for_locked_moves_and_unlock_responses.go @@ -0,0 +1,104 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package move + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "github.com/transcom/mymove/pkg/gen/ghcmessages" +) + +// CheckForLockedMovesAndUnlockOKCode is the HTTP code returned for type CheckForLockedMovesAndUnlockOK +const CheckForLockedMovesAndUnlockOKCode int = 200 + +/* +CheckForLockedMovesAndUnlockOK Successfully unlocked officer's move(s). + +swagger:response checkForLockedMovesAndUnlockOK +*/ +type CheckForLockedMovesAndUnlockOK struct { + + /* + In: Body + */ + Payload *CheckForLockedMovesAndUnlockOKBody `json:"body,omitempty"` +} + +// NewCheckForLockedMovesAndUnlockOK creates CheckForLockedMovesAndUnlockOK with default headers values +func NewCheckForLockedMovesAndUnlockOK() *CheckForLockedMovesAndUnlockOK { + + return &CheckForLockedMovesAndUnlockOK{} +} + +// WithPayload adds the payload to the check for locked moves and unlock o k response +func (o *CheckForLockedMovesAndUnlockOK) WithPayload(payload *CheckForLockedMovesAndUnlockOKBody) *CheckForLockedMovesAndUnlockOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the check for locked moves and unlock o k response +func (o *CheckForLockedMovesAndUnlockOK) SetPayload(payload *CheckForLockedMovesAndUnlockOKBody) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *CheckForLockedMovesAndUnlockOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// CheckForLockedMovesAndUnlockInternalServerErrorCode is the HTTP code returned for type CheckForLockedMovesAndUnlockInternalServerError +const CheckForLockedMovesAndUnlockInternalServerErrorCode int = 500 + +/* +CheckForLockedMovesAndUnlockInternalServerError A server error occurred + +swagger:response checkForLockedMovesAndUnlockInternalServerError +*/ +type CheckForLockedMovesAndUnlockInternalServerError struct { + + /* + In: Body + */ + Payload *ghcmessages.Error `json:"body,omitempty"` +} + +// NewCheckForLockedMovesAndUnlockInternalServerError creates CheckForLockedMovesAndUnlockInternalServerError with default headers values +func NewCheckForLockedMovesAndUnlockInternalServerError() *CheckForLockedMovesAndUnlockInternalServerError { + + return &CheckForLockedMovesAndUnlockInternalServerError{} +} + +// WithPayload adds the payload to the check for locked moves and unlock internal server error response +func (o *CheckForLockedMovesAndUnlockInternalServerError) WithPayload(payload *ghcmessages.Error) *CheckForLockedMovesAndUnlockInternalServerError { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the check for locked moves and unlock internal server error response +func (o *CheckForLockedMovesAndUnlockInternalServerError) SetPayload(payload *ghcmessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *CheckForLockedMovesAndUnlockInternalServerError) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(500) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/pkg/gen/ghcapi/ghcoperations/move/check_for_locked_moves_and_unlock_urlbuilder.go b/pkg/gen/ghcapi/ghcoperations/move/check_for_locked_moves_and_unlock_urlbuilder.go new file mode 100644 index 00000000000..e103de7869d --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/move/check_for_locked_moves_and_unlock_urlbuilder.go @@ -0,0 +1,101 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package move + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" + "strings" + + "github.com/go-openapi/strfmt" +) + +// CheckForLockedMovesAndUnlockURL generates an URL for the check for locked moves and unlock operation +type CheckForLockedMovesAndUnlockURL struct { + OfficeUserID strfmt.UUID + + _basePath string + // avoid unkeyed usage + _ struct{} +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *CheckForLockedMovesAndUnlockURL) WithBasePath(bp string) *CheckForLockedMovesAndUnlockURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *CheckForLockedMovesAndUnlockURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *CheckForLockedMovesAndUnlockURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/moves/{officeUserID}/CheckForLockedMovesAndUnlock" + + officeUserID := o.OfficeUserID.String() + if officeUserID != "" { + _path = strings.Replace(_path, "{officeUserID}", officeUserID, -1) + } else { + return nil, errors.New("officeUserId is required on CheckForLockedMovesAndUnlockURL") + } + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/ghc/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *CheckForLockedMovesAndUnlockURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *CheckForLockedMovesAndUnlockURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *CheckForLockedMovesAndUnlockURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on CheckForLockedMovesAndUnlockURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on CheckForLockedMovesAndUnlockURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *CheckForLockedMovesAndUnlockURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/pkg/gen/ghcapi/ghcoperations/move/search_moves.go b/pkg/gen/ghcapi/ghcoperations/move/search_moves.go index f22f0fbf7cf..d54a3739ead 100644 --- a/pkg/gen/ghcapi/ghcoperations/move/search_moves.go +++ b/pkg/gen/ghcapi/ghcoperations/move/search_moves.go @@ -86,7 +86,7 @@ type SearchMovesBody struct { // DOD ID // Max Length: 10 // Min Length: 10 - DodID *string `json:"dodID,omitempty"` + Edipi *string `json:"edipi,omitempty"` // EMPLID // Max Length: 7 @@ -123,7 +123,7 @@ type SearchMovesBody struct { ShipmentsCount *int64 `json:"shipmentsCount,omitempty"` // sort - // Enum: [customerName dodID emplid branch locator status originPostalCode destinationPostalCode shipmentsCount] + // Enum: [customerName edipi emplid branch locator status originPostalCode destinationPostalCode shipmentsCount] Sort *string `json:"sort,omitempty"` // Filtering for the status. @@ -143,7 +143,7 @@ func (o *SearchMovesBody) Validate(formats strfmt.Registry) error { res = append(res, err) } - if err := o.validateDodID(formats); err != nil { + if err := o.validateEdipi(formats); err != nil { res = append(res, err) } @@ -201,16 +201,16 @@ func (o *SearchMovesBody) validateDeliveryDate(formats strfmt.Registry) error { return nil } -func (o *SearchMovesBody) validateDodID(formats strfmt.Registry) error { - if swag.IsZero(o.DodID) { // not required +func (o *SearchMovesBody) validateEdipi(formats strfmt.Registry) error { + if swag.IsZero(o.Edipi) { // not required return nil } - if err := validate.MinLength("body"+"."+"dodID", "body", *o.DodID, 10); err != nil { + if err := validate.MinLength("body"+"."+"edipi", "body", *o.Edipi, 10); err != nil { return err } - if err := validate.MaxLength("body"+"."+"dodID", "body", *o.DodID, 10); err != nil { + if err := validate.MaxLength("body"+"."+"edipi", "body", *o.Edipi, 10); err != nil { return err } @@ -307,7 +307,7 @@ var searchMovesBodyTypeSortPropEnum []interface{} func init() { var res []string - if err := json.Unmarshal([]byte(`["customerName","dodID","emplid","branch","locator","status","originPostalCode","destinationPostalCode","shipmentsCount"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["customerName","edipi","emplid","branch","locator","status","originPostalCode","destinationPostalCode","shipmentsCount"]`), &res); err != nil { panic(err) } for _, v := range res { @@ -320,8 +320,8 @@ const ( // SearchMovesBodySortCustomerName captures enum value "customerName" SearchMovesBodySortCustomerName string = "customerName" - // SearchMovesBodySortDodID captures enum value "dodID" - SearchMovesBodySortDodID string = "dodID" + // SearchMovesBodySortEdipi captures enum value "edipi" + SearchMovesBodySortEdipi string = "edipi" // SearchMovesBodySortEmplid captures enum value "emplid" SearchMovesBodySortEmplid string = "emplid" diff --git a/pkg/gen/ghcapi/ghcoperations/move/set_financial_review_flag.go b/pkg/gen/ghcapi/ghcoperations/move/set_financial_review_flag.go index 6ff0754f458..439a4363a7f 100644 --- a/pkg/gen/ghcapi/ghcoperations/move/set_financial_review_flag.go +++ b/pkg/gen/ghcapi/ghcoperations/move/set_financial_review_flag.go @@ -39,7 +39,7 @@ func NewSetFinancialReviewFlag(ctx *middleware.Context, handler SetFinancialRevi # Flags a move for financial office review -This sets a flag which indicates that the move should be reviewed by a fincancial office. For example, if the origin or destination address of a shipment is far from the duty location and may incur excess costs to the customer. +This sets a flag which indicates that the move should be reviewed by a fincancial office. For example, if the origin or delivery address of a shipment is far from the duty location and may incur excess costs to the customer. */ type SetFinancialReviewFlag struct { Context *middleware.Context diff --git a/pkg/gen/ghcapi/ghcoperations/mymove_api.go b/pkg/gen/ghcapi/ghcoperations/mymove_api.go index 580340af7cd..f89554384f9 100644 --- a/pkg/gen/ghcapi/ghcoperations/mymove_api.go +++ b/pkg/gen/ghcapi/ghcoperations/mymove_api.go @@ -93,6 +93,9 @@ func NewMymoveAPI(spec *loads.Document) *MymoveAPI { PaymentRequestsBulkDownloadHandler: payment_requests.BulkDownloadHandlerFunc(func(params payment_requests.BulkDownloadParams) middleware.Responder { return middleware.NotImplemented("operation payment_requests.BulkDownload has not yet been implemented") }), + MoveCheckForLockedMovesAndUnlockHandler: move.CheckForLockedMovesAndUnlockHandlerFunc(func(params move.CheckForLockedMovesAndUnlockParams) middleware.Responder { + return middleware.NotImplemented("operation move.CheckForLockedMovesAndUnlock has not yet been implemented") + }), OrderCounselingUpdateAllowanceHandler: order.CounselingUpdateAllowanceHandlerFunc(func(params order.CounselingUpdateAllowanceParams) middleware.Responder { return middleware.NotImplemented("operation order.CounselingUpdateAllowance has not yet been implemented") }), @@ -440,6 +443,8 @@ type MymoveAPI struct { ReportViolationsAssociateReportViolationsHandler report_violations.AssociateReportViolationsHandler // PaymentRequestsBulkDownloadHandler sets the operation handler for the bulk download operation PaymentRequestsBulkDownloadHandler payment_requests.BulkDownloadHandler + // MoveCheckForLockedMovesAndUnlockHandler sets the operation handler for the check for locked moves and unlock operation + MoveCheckForLockedMovesAndUnlockHandler move.CheckForLockedMovesAndUnlockHandler // OrderCounselingUpdateAllowanceHandler sets the operation handler for the counseling update allowance operation OrderCounselingUpdateAllowanceHandler order.CounselingUpdateAllowanceHandler // OrderCounselingUpdateOrderHandler sets the operation handler for the counseling update order operation @@ -737,6 +742,9 @@ func (o *MymoveAPI) Validate() error { if o.PaymentRequestsBulkDownloadHandler == nil { unregistered = append(unregistered, "payment_requests.BulkDownloadHandler") } + if o.MoveCheckForLockedMovesAndUnlockHandler == nil { + unregistered = append(unregistered, "move.CheckForLockedMovesAndUnlockHandler") + } if o.OrderCounselingUpdateAllowanceHandler == nil { unregistered = append(unregistered, "order.CounselingUpdateAllowanceHandler") } @@ -1149,6 +1157,10 @@ func (o *MymoveAPI) initHandlerCache() { if o.handlers["PATCH"] == nil { o.handlers["PATCH"] = make(map[string]http.Handler) } + o.handlers["PATCH"]["/moves/{officeUserID}/CheckForLockedMovesAndUnlock"] = move.NewCheckForLockedMovesAndUnlock(o.context, o.MoveCheckForLockedMovesAndUnlockHandler) + if o.handlers["PATCH"] == nil { + o.handlers["PATCH"] = make(map[string]http.Handler) + } o.handlers["PATCH"]["/counseling/orders/{orderID}/allowances"] = order.NewCounselingUpdateAllowance(o.context, o.OrderCounselingUpdateAllowanceHandler) if o.handlers["PATCH"] == nil { o.handlers["PATCH"] = make(map[string]http.Handler) diff --git a/pkg/gen/ghcapi/ghcoperations/queues/get_moves_queue_parameters.go b/pkg/gen/ghcapi/ghcoperations/queues/get_moves_queue_parameters.go index 4d51b99e7a2..1538d7b4b9a 100644 --- a/pkg/gen/ghcapi/ghcoperations/queues/get_moves_queue_parameters.go +++ b/pkg/gen/ghcapi/ghcoperations/queues/get_moves_queue_parameters.go @@ -47,6 +47,10 @@ type GetMovesQueueParams struct { In: query */ Branch *string + /*filters using a counselingOffice name of the move + In: query + */ + CounselingOffice *string /* In: query */ @@ -58,7 +62,7 @@ type GetMovesQueueParams struct { /* In: query */ - DodID *string + Edipi *string /* In: query */ @@ -135,6 +139,11 @@ func (o *GetMovesQueueParams) BindRequest(r *http.Request, route *middleware.Mat res = append(res, err) } + qCounselingOffice, qhkCounselingOffice, _ := qs.GetOK("counselingOffice") + if err := o.bindCounselingOffice(qCounselingOffice, qhkCounselingOffice, route.Formats); err != nil { + res = append(res, err) + } + qCustomerName, qhkCustomerName, _ := qs.GetOK("customerName") if err := o.bindCustomerName(qCustomerName, qhkCustomerName, route.Formats); err != nil { res = append(res, err) @@ -145,8 +154,8 @@ func (o *GetMovesQueueParams) BindRequest(r *http.Request, route *middleware.Mat res = append(res, err) } - qDodID, qhkDodID, _ := qs.GetOK("dodID") - if err := o.bindDodID(qDodID, qhkDodID, route.Formats); err != nil { + qEdipi, qhkEdipi, _ := qs.GetOK("edipi") + if err := o.bindEdipi(qEdipi, qhkEdipi, route.Formats); err != nil { res = append(res, err) } @@ -283,6 +292,24 @@ func (o *GetMovesQueueParams) bindBranch(rawData []string, hasKey bool, formats return nil } +// bindCounselingOffice binds and validates parameter CounselingOffice from query. +func (o *GetMovesQueueParams) bindCounselingOffice(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + o.CounselingOffice = &raw + + return nil +} + // bindCustomerName binds and validates parameter CustomerName from query. func (o *GetMovesQueueParams) bindCustomerName(rawData []string, hasKey bool, formats strfmt.Registry) error { var raw string @@ -319,8 +346,8 @@ func (o *GetMovesQueueParams) bindDestinationDutyLocation(rawData []string, hasK return nil } -// bindDodID binds and validates parameter DodID from query. -func (o *GetMovesQueueParams) bindDodID(rawData []string, hasKey bool, formats strfmt.Registry) error { +// bindEdipi binds and validates parameter Edipi from query. +func (o *GetMovesQueueParams) bindEdipi(rawData []string, hasKey bool, formats strfmt.Registry) error { var raw string if len(rawData) > 0 { raw = rawData[len(rawData)-1] @@ -332,7 +359,7 @@ func (o *GetMovesQueueParams) bindDodID(rawData []string, hasKey bool, formats s if raw == "" { // empty values pass all other validations return nil } - o.DodID = &raw + o.Edipi = &raw return nil } @@ -547,7 +574,7 @@ func (o *GetMovesQueueParams) bindSort(rawData []string, hasKey bool, formats st // validateSort carries on validations for parameter Sort func (o *GetMovesQueueParams) validateSort(formats strfmt.Registry) error { - if err := validate.EnumCase("sort", "query", *o.Sort, []interface{}{"customerName", "dodID", "emplid", "branch", "locator", "status", "originDutyLocation", "destinationDutyLocation", "requestedMoveDate", "appearedInTooAt", "assignedTo"}, true); err != nil { + if err := validate.EnumCase("sort", "query", *o.Sort, []interface{}{"customerName", "edipi", "emplid", "branch", "locator", "status", "originDutyLocation", "destinationDutyLocation", "requestedMoveDate", "appearedInTooAt", "assignedTo", "counselingOffice"}, true); err != nil { return err } diff --git a/pkg/gen/ghcapi/ghcoperations/queues/get_moves_queue_urlbuilder.go b/pkg/gen/ghcapi/ghcoperations/queues/get_moves_queue_urlbuilder.go index cc634c8957a..ec05a50a3eb 100644 --- a/pkg/gen/ghcapi/ghcoperations/queues/get_moves_queue_urlbuilder.go +++ b/pkg/gen/ghcapi/ghcoperations/queues/get_moves_queue_urlbuilder.go @@ -19,9 +19,10 @@ type GetMovesQueueURL struct { AppearedInTooAt *strfmt.DateTime AssignedTo *string Branch *string + CounselingOffice *string CustomerName *string DestinationDutyLocation *string - DodID *string + Edipi *string Emplid *string Locator *string Order *string @@ -92,6 +93,14 @@ func (o *GetMovesQueueURL) Build() (*url.URL, error) { qs.Set("branch", branchQ) } + var counselingOfficeQ string + if o.CounselingOffice != nil { + counselingOfficeQ = *o.CounselingOffice + } + if counselingOfficeQ != "" { + qs.Set("counselingOffice", counselingOfficeQ) + } + var customerNameQ string if o.CustomerName != nil { customerNameQ = *o.CustomerName @@ -108,12 +117,12 @@ func (o *GetMovesQueueURL) Build() (*url.URL, error) { qs.Set("destinationDutyLocation", destinationDutyLocationQ) } - var dodIDQ string - if o.DodID != nil { - dodIDQ = *o.DodID + var edipiQ string + if o.Edipi != nil { + edipiQ = *o.Edipi } - if dodIDQ != "" { - qs.Set("dodID", dodIDQ) + if edipiQ != "" { + qs.Set("edipi", edipiQ) } var emplidQ string diff --git a/pkg/gen/ghcapi/ghcoperations/queues/get_payment_requests_queue_parameters.go b/pkg/gen/ghcapi/ghcoperations/queues/get_payment_requests_queue_parameters.go index 4016167d41d..be6e0fe5d60 100644 --- a/pkg/gen/ghcapi/ghcoperations/queues/get_payment_requests_queue_parameters.go +++ b/pkg/gen/ghcapi/ghcoperations/queues/get_payment_requests_queue_parameters.go @@ -43,6 +43,10 @@ type GetPaymentRequestsQueueParams struct { In: query */ Branch *string + /*filters using a counselingOffice name of the move + In: query + */ + CounselingOffice *string /* In: query */ @@ -54,7 +58,7 @@ type GetPaymentRequestsQueueParams struct { /* In: query */ - DodID *string + Edipi *string /* In: query */ @@ -124,6 +128,11 @@ func (o *GetPaymentRequestsQueueParams) BindRequest(r *http.Request, route *midd res = append(res, err) } + qCounselingOffice, qhkCounselingOffice, _ := qs.GetOK("counselingOffice") + if err := o.bindCounselingOffice(qCounselingOffice, qhkCounselingOffice, route.Formats); err != nil { + res = append(res, err) + } + qCustomerName, qhkCustomerName, _ := qs.GetOK("customerName") if err := o.bindCustomerName(qCustomerName, qhkCustomerName, route.Formats); err != nil { res = append(res, err) @@ -134,8 +143,8 @@ func (o *GetPaymentRequestsQueueParams) BindRequest(r *http.Request, route *midd res = append(res, err) } - qDodID, qhkDodID, _ := qs.GetOK("dodID") - if err := o.bindDodID(qDodID, qhkDodID, route.Formats); err != nil { + qEdipi, qhkEdipi, _ := qs.GetOK("edipi") + if err := o.bindEdipi(qEdipi, qhkEdipi, route.Formats); err != nil { res = append(res, err) } @@ -235,6 +244,24 @@ func (o *GetPaymentRequestsQueueParams) bindBranch(rawData []string, hasKey bool return nil } +// bindCounselingOffice binds and validates parameter CounselingOffice from query. +func (o *GetPaymentRequestsQueueParams) bindCounselingOffice(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + o.CounselingOffice = &raw + + return nil +} + // bindCustomerName binds and validates parameter CustomerName from query. func (o *GetPaymentRequestsQueueParams) bindCustomerName(rawData []string, hasKey bool, formats strfmt.Registry) error { var raw string @@ -271,8 +298,8 @@ func (o *GetPaymentRequestsQueueParams) bindDestinationDutyLocation(rawData []st return nil } -// bindDodID binds and validates parameter DodID from query. -func (o *GetPaymentRequestsQueueParams) bindDodID(rawData []string, hasKey bool, formats strfmt.Registry) error { +// bindEdipi binds and validates parameter Edipi from query. +func (o *GetPaymentRequestsQueueParams) bindEdipi(rawData []string, hasKey bool, formats strfmt.Registry) error { var raw string if len(rawData) > 0 { raw = rawData[len(rawData)-1] @@ -284,7 +311,7 @@ func (o *GetPaymentRequestsQueueParams) bindDodID(rawData []string, hasKey bool, if raw == "" { // empty values pass all other validations return nil } - o.DodID = &raw + o.Edipi = &raw return nil } @@ -464,7 +491,7 @@ func (o *GetPaymentRequestsQueueParams) bindSort(rawData []string, hasKey bool, // validateSort carries on validations for parameter Sort func (o *GetPaymentRequestsQueueParams) validateSort(formats strfmt.Registry) error { - if err := validate.EnumCase("sort", "query", *o.Sort, []interface{}{"customerName", "locator", "submittedAt", "branch", "status", "dodID", "emplid", "age", "originDutyLocation", "assignedTo"}, true); err != nil { + if err := validate.EnumCase("sort", "query", *o.Sort, []interface{}{"customerName", "locator", "submittedAt", "branch", "status", "edipi", "emplid", "age", "originDutyLocation", "assignedTo", "counselingOffice"}, true); err != nil { return err } diff --git a/pkg/gen/ghcapi/ghcoperations/queues/get_payment_requests_queue_urlbuilder.go b/pkg/gen/ghcapi/ghcoperations/queues/get_payment_requests_queue_urlbuilder.go index 3d092ff6eb1..1b5aa0e8b3b 100644 --- a/pkg/gen/ghcapi/ghcoperations/queues/get_payment_requests_queue_urlbuilder.go +++ b/pkg/gen/ghcapi/ghcoperations/queues/get_payment_requests_queue_urlbuilder.go @@ -18,9 +18,10 @@ import ( type GetPaymentRequestsQueueURL struct { AssignedTo *string Branch *string + CounselingOffice *string CustomerName *string DestinationDutyLocation *string - DodID *string + Edipi *string Emplid *string Locator *string Order *string @@ -83,6 +84,14 @@ func (o *GetPaymentRequestsQueueURL) Build() (*url.URL, error) { qs.Set("branch", branchQ) } + var counselingOfficeQ string + if o.CounselingOffice != nil { + counselingOfficeQ = *o.CounselingOffice + } + if counselingOfficeQ != "" { + qs.Set("counselingOffice", counselingOfficeQ) + } + var customerNameQ string if o.CustomerName != nil { customerNameQ = *o.CustomerName @@ -99,12 +108,12 @@ func (o *GetPaymentRequestsQueueURL) Build() (*url.URL, error) { qs.Set("destinationDutyLocation", destinationDutyLocationQ) } - var dodIDQ string - if o.DodID != nil { - dodIDQ = *o.DodID + var edipiQ string + if o.Edipi != nil { + edipiQ = *o.Edipi } - if dodIDQ != "" { - qs.Set("dodID", dodIDQ) + if edipiQ != "" { + qs.Set("edipi", edipiQ) } var emplidQ string diff --git a/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_parameters.go b/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_parameters.go index 81ae98c3178..13bb6b7aea5 100644 --- a/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_parameters.go +++ b/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_parameters.go @@ -66,7 +66,7 @@ type GetServicesCounselingQueueParams struct { /*filters to match the unique service member's DoD ID In: query */ - DodID *string + Edipi *string /*filters to match the unique service member's EMPLID In: query */ @@ -183,8 +183,8 @@ func (o *GetServicesCounselingQueueParams) BindRequest(r *http.Request, route *m res = append(res, err) } - qDodID, qhkDodID, _ := qs.GetOK("dodID") - if err := o.bindDodID(qDodID, qhkDodID, route.Formats); err != nil { + qEdipi, qhkEdipi, _ := qs.GetOK("edipi") + if err := o.bindEdipi(qEdipi, qhkEdipi, route.Formats); err != nil { res = append(res, err) } @@ -418,8 +418,8 @@ func (o *GetServicesCounselingQueueParams) bindDestinationDutyLocation(rawData [ return nil } -// bindDodID binds and validates parameter DodID from query. -func (o *GetServicesCounselingQueueParams) bindDodID(rawData []string, hasKey bool, formats strfmt.Registry) error { +// bindEdipi binds and validates parameter Edipi from query. +func (o *GetServicesCounselingQueueParams) bindEdipi(rawData []string, hasKey bool, formats strfmt.Registry) error { var raw string if len(rawData) > 0 { raw = rawData[len(rawData)-1] @@ -431,7 +431,7 @@ func (o *GetServicesCounselingQueueParams) bindDodID(rawData []string, hasKey bo if raw == "" { // empty values pass all other validations return nil } - o.DodID = &raw + o.Edipi = &raw return nil } @@ -751,7 +751,7 @@ func (o *GetServicesCounselingQueueParams) bindSort(rawData []string, hasKey boo // validateSort carries on validations for parameter Sort func (o *GetServicesCounselingQueueParams) validateSort(formats strfmt.Registry) error { - if err := validate.EnumCase("sort", "query", *o.Sort, []interface{}{"customerName", "dodID", "emplid", "branch", "locator", "status", "requestedMoveDate", "submittedAt", "originGBLOC", "originDutyLocation", "destinationDutyLocation", "ppmType", "closeoutInitiated", "closeoutLocation", "ppmStatus", "counselingOffice", "assignedTo"}, true); err != nil { + if err := validate.EnumCase("sort", "query", *o.Sort, []interface{}{"customerName", "edipi", "emplid", "branch", "locator", "status", "requestedMoveDate", "submittedAt", "originGBLOC", "originDutyLocation", "destinationDutyLocation", "ppmType", "closeoutInitiated", "closeoutLocation", "ppmStatus", "counselingOffice", "assignedTo"}, true); err != nil { return err } diff --git a/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_urlbuilder.go b/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_urlbuilder.go index 6e961e7143f..d7ad7668c07 100644 --- a/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_urlbuilder.go +++ b/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_urlbuilder.go @@ -23,7 +23,7 @@ type GetServicesCounselingQueueURL struct { CounselingOffice *string CustomerName *string DestinationDutyLocation *string - DodID *string + Edipi *string Emplid *string Locator *string NeedsPPMCloseout *bool @@ -131,12 +131,12 @@ func (o *GetServicesCounselingQueueURL) Build() (*url.URL, error) { qs.Set("destinationDutyLocation", destinationDutyLocationQ) } - var dodIDQ string - if o.DodID != nil { - dodIDQ = *o.DodID + var edipiQ string + if o.Edipi != nil { + edipiQ = *o.Edipi } - if dodIDQ != "" { - qs.Set("dodID", dodIDQ) + if edipiQ != "" { + qs.Set("edipi", edipiQ) } var emplidQ string diff --git a/pkg/gen/ghcmessages/m_t_o_service_item.go b/pkg/gen/ghcmessages/m_t_o_service_item.go index 710c1109490..9f20d0d5a7b 100644 --- a/pkg/gen/ghcmessages/m_t_o_service_item.go +++ b/pkg/gen/ghcmessages/m_t_o_service_item.go @@ -62,6 +62,9 @@ type MTOServiceItem struct { // Example: 2500 EstimatedWeight *int64 `json:"estimatedWeight,omitempty"` + // external crate + ExternalCrate *bool `json:"externalCrate,omitempty"` + // fee type // Enum: [COUNSELING CRATING TRUCKING SHUTTLE] FeeType string `json:"feeType,omitempty"` @@ -75,6 +78,11 @@ type MTOServiceItem struct { // locked price cents LockedPriceCents *int64 `json:"lockedPriceCents,omitempty"` + // To identify whether the service was provided within (CONUS) or (OCONUS) + // Example: CONUS + // Enum: [CONUS OCONUS] + Market *string `json:"market,omitempty"` + // move task order ID // Example: 1f2270c7-7166-40ae-981e-b200ebdf3054 // Required: true @@ -206,6 +214,10 @@ func (m *MTOServiceItem) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validateMarket(formats); err != nil { + res = append(res, err) + } + if err := m.validateMoveTaskOrderID(formats); err != nil { res = append(res, err) } @@ -415,6 +427,48 @@ func (m *MTOServiceItem) validateID(formats strfmt.Registry) error { return nil } +var mTOServiceItemTypeMarketPropEnum []interface{} + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["CONUS","OCONUS"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + mTOServiceItemTypeMarketPropEnum = append(mTOServiceItemTypeMarketPropEnum, v) + } +} + +const ( + + // MTOServiceItemMarketCONUS captures enum value "CONUS" + MTOServiceItemMarketCONUS string = "CONUS" + + // MTOServiceItemMarketOCONUS captures enum value "OCONUS" + MTOServiceItemMarketOCONUS string = "OCONUS" +) + +// prop value enum +func (m *MTOServiceItem) validateMarketEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, mTOServiceItemTypeMarketPropEnum, true); err != nil { + return err + } + return nil +} + +func (m *MTOServiceItem) validateMarket(formats strfmt.Registry) error { + if swag.IsZero(m.Market) { // not required + return nil + } + + // value enum + if err := m.validateMarketEnum("market", "body", *m.Market); err != nil { + return err + } + + return nil +} + func (m *MTOServiceItem) validateMoveTaskOrderID(formats strfmt.Registry) error { if err := validate.Required("moveTaskOrderID", "body", m.MoveTaskOrderID); err != nil { diff --git a/pkg/gen/ghcmessages/m_t_o_shipment.go b/pkg/gen/ghcmessages/m_t_o_shipment.go index ca5097209b4..78a6420cee4 100644 --- a/pkg/gen/ghcmessages/m_t_o_shipment.go +++ b/pkg/gen/ghcmessages/m_t_o_shipment.go @@ -20,7 +20,7 @@ import ( // swagger:model MTOShipment type MTOShipment struct { - // The actual date that the shipment was delivered to the destination address by the Prime + // The actual date that the shipment was delivered to the delivery address by the Prime // Format: date ActualDeliveryDate *strfmt.Date `json:"actualDeliveryDate,omitempty"` diff --git a/pkg/gen/ghcmessages/move.go b/pkg/gen/ghcmessages/move.go index 1bea0079581..bb67e748b11 100644 --- a/pkg/gen/ghcmessages/move.go +++ b/pkg/gen/ghcmessages/move.go @@ -82,7 +82,7 @@ type Move struct { FinancialReviewFlag bool `json:"financialReviewFlag,omitempty"` // financial review remarks - // Example: Destination address is too far from duty location + // Example: Delivery Address is too far from duty location // Read Only: true FinancialReviewRemarks *string `json:"financialReviewRemarks,omitempty"` diff --git a/pkg/gen/ghcmessages/p_p_m_shipment.go b/pkg/gen/ghcmessages/p_p_m_shipment.go index 8392b5f5cc5..117201a70f6 100644 --- a/pkg/gen/ghcmessages/p_p_m_shipment.go +++ b/pkg/gen/ghcmessages/p_p_m_shipment.go @@ -122,6 +122,9 @@ type PPMShipment struct { // Example: false IsActualExpenseReimbursement *bool `json:"isActualExpenseReimbursement"` + // The max amount the government will pay the service member to move their belongings based on the moving date, locations, and shipment weight. + MaxIncentive *int64 `json:"maxIncentive"` + // All expense documentation receipt records of this PPM shipment. MovingExpenses []*MovingExpense `json:"movingExpenses"` diff --git a/pkg/gen/ghcmessages/queue_payment_request.go b/pkg/gen/ghcmessages/queue_payment_request.go index 6f173d37b76..28a4edbb0f7 100644 --- a/pkg/gen/ghcmessages/queue_payment_request.go +++ b/pkg/gen/ghcmessages/queue_payment_request.go @@ -31,6 +31,9 @@ type QueuePaymentRequest struct { // available office users AvailableOfficeUsers AvailableOfficeUsers `json:"availableOfficeUsers,omitempty"` + // counseling office + CounselingOffice *string `json:"counselingOffice,omitempty"` + // customer Customer *Customer `json:"customer,omitempty"` diff --git a/pkg/gen/ghcmessages/search_customer.go b/pkg/gen/ghcmessages/search_customer.go index 6bdab3107de..f35e52f734c 100644 --- a/pkg/gen/ghcmessages/search_customer.go +++ b/pkg/gen/ghcmessages/search_customer.go @@ -22,8 +22,8 @@ type SearchCustomer struct { // branch Branch string `json:"branch,omitempty"` - // dod ID - DodID *string `json:"dodID,omitempty"` + // edipi + Edipi *string `json:"edipi,omitempty"` // emplid Emplid *string `json:"emplid,omitempty"` diff --git a/pkg/gen/ghcmessages/search_move.go b/pkg/gen/ghcmessages/search_move.go index 2a88181a734..c6690995151 100644 --- a/pkg/gen/ghcmessages/search_move.go +++ b/pkg/gen/ghcmessages/search_move.go @@ -30,9 +30,9 @@ type SearchMove struct { // destination g b l o c DestinationGBLOC GBLOC `json:"destinationGBLOC,omitempty"` - // dod ID + // edipi // Example: 1234567890 - DodID *string `json:"dodID,omitempty"` + Edipi *string `json:"edipi,omitempty"` // emplid Emplid *string `json:"emplid,omitempty"` diff --git a/pkg/gen/ghcmessages/shipment_address_update.go b/pkg/gen/ghcmessages/shipment_address_update.go index 5465bcb9c22..d609419c05d 100644 --- a/pkg/gen/ghcmessages/shipment_address_update.go +++ b/pkg/gen/ghcmessages/shipment_address_update.go @@ -14,7 +14,7 @@ import ( "github.com/go-openapi/validate" ) -// ShipmentAddressUpdate This represents a destination address change request made by the Prime that is either auto-approved or requires review if the pricing criteria has changed. If criteria has changed, then it must be approved or rejected by a TOO. +// ShipmentAddressUpdate This represents a delivery address change request made by the Prime that is either auto-approved or requires review if the pricing criteria has changed. If criteria has changed, then it must be approved or rejected by a TOO. // // swagger:model ShipmentAddressUpdate type ShipmentAddressUpdate struct { @@ -38,7 +38,7 @@ type ShipmentAddressUpdate struct { // Required: true NewAddress *Address `json:"newAddress"` - // The distance between the original SIT address and requested new destination address of shipment + // The distance between the original SIT address and requested new delivery address of shipment // Example: 88 // Minimum: 0 NewSitDistanceBetween *int64 `json:"newSitDistanceBetween,omitempty"` @@ -49,7 +49,7 @@ type ShipmentAddressUpdate struct { // Example: This is an office remark OfficeRemarks *string `json:"officeRemarks,omitempty"` - // The distance between the original SIT address and the previous/old destination address of shipment + // The distance between the original SIT address and the previous/old delivery address of shipment // Example: 50 // Minimum: 0 OldSitDistanceBetween *int64 `json:"oldSitDistanceBetween,omitempty"` diff --git a/pkg/gen/internalapi/embedded_spec.go b/pkg/gen/internalapi/embedded_spec.go index 44bfbf10988..0ca76e4ffb7 100644 --- a/pkg/gen/internalapi/embedded_spec.go +++ b/pkg/gen/internalapi/embedded_spec.go @@ -6376,6 +6376,13 @@ func init() { "x-omitempty": false, "example": false }, + "maxIncentive": { + "description": "The max amount the government will pay the service member to move their belongings based on the moving date, locations, and shipment weight.", + "type": "integer", + "format": "cents", + "x-nullable": true, + "x-omitempty": false + }, "movingExpenses": { "description": "All expense documentation receipt records of this PPM shipment.", "type": "array", @@ -15208,6 +15215,13 @@ func init() { "x-omitempty": false, "example": false }, + "maxIncentive": { + "description": "The max amount the government will pay the service member to move their belongings based on the moving date, locations, and shipment weight.", + "type": "integer", + "format": "cents", + "x-nullable": true, + "x-omitempty": false + }, "movingExpenses": { "description": "All expense documentation receipt records of this PPM shipment.", "type": "array", diff --git a/pkg/gen/internalmessages/p_p_m_shipment.go b/pkg/gen/internalmessages/p_p_m_shipment.go index 08347da5a50..15ee628d1df 100644 --- a/pkg/gen/internalmessages/p_p_m_shipment.go +++ b/pkg/gen/internalmessages/p_p_m_shipment.go @@ -122,6 +122,9 @@ type PPMShipment struct { // Example: false IsActualExpenseReimbursement *bool `json:"isActualExpenseReimbursement"` + // The max amount the government will pay the service member to move their belongings based on the moving date, locations, and shipment weight. + MaxIncentive *int64 `json:"maxIncentive"` + // All expense documentation receipt records of this PPM shipment. MovingExpenses []*MovingExpense `json:"movingExpenses"` diff --git a/pkg/gen/primeapi/embedded_spec.go b/pkg/gen/primeapi/embedded_spec.go index 1b18671a661..7bb4dd33e30 100644 --- a/pkg/gen/primeapi/embedded_spec.go +++ b/pkg/gen/primeapi/embedded_spec.go @@ -355,7 +355,7 @@ func init() { }, "/mto-service-items/{mtoServiceItemID}": { "patch": { - "description": "Updates MTOServiceItems after creation. Not all service items or fields may be updated, please see details below.\n\nThis endpoint supports different body definitions. In the modelType field below, select the modelType corresponding\n to the service item you wish to update and the documentation will update with the new definition.\n\n* Addresses: To update a destination service item's SIT destination final address, update the shipment destination address.\nFor approved shipments, please use [updateShipmentDestinationAddress](#mtoShipment/updateShipmentDestinationAddress).\nFor shipments not yet approved, please use [updateMTOShipmentAddress](#mtoShipment/updateMTOShipmentAddress).\n\n* SIT Service Items: Take note that when updating ` + "`" + `sitCustomerContacted` + "`" + `, ` + "`" + `sitDepartureDate` + "`" + `, or ` + "`" + `sitRequestedDelivery` + "`" + `, we want\nthose to be updated on ` + "`" + `DOASIT` + "`" + ` (for origin SIT) and ` + "`" + `DDASIT` + "`" + ` (for destination SIT). If updating those values in other service\nitems, the office users will not have as much attention to those values.\n\nTo create a service item, please use [createMTOServiceItem](#mtoServiceItem/createMTOServiceItem)) endpoint.\n\n* Resubmitting rejected SIT service items: This endpoint will handle the logic of changing the status of rejected SIT service items from\nREJECTED to SUBMITTED. Please provide the ` + "`" + `requestedApprovalsRequestedStatus: true` + "`" + ` when resubmitting as this will give attention to the TOO to\nreview the resubmitted SIT service item. Another note, ` + "`" + `updateReason` + "`" + ` must have a different value than the current ` + "`" + `reason` + "`" + ` value on the service item.\nIf this value is not updated, then an error will be sent back.\n\nThe following SIT service items can be resubmitted following a rejection:\n- DDASIT\n- DDDSIT\n- DDFSIT\n- DOASIT\n- DOPSIT\n- DOFSIT\n- DDSFSC\n- DOSFSC\n\nAt a MINIMUM, the payload for resubmitting a rejected SIT service item must look like this:\n` + "`" + `` + "`" + `` + "`" + `json\n{\n \"reServiceCode\": \"DDFSIT\",\n \"updateReason\": \"A reason that differs from the previous reason\",\n \"modelType\": \"UpdateMTOServiceItemSIT\",\n \"requestApprovalsRequestedStatus\": true\n}\n` + "`" + `` + "`" + `` + "`" + `\n", + "description": "Updates MTOServiceItems after creation. Not all service items or fields may be updated, please see details below.\n\nThis endpoint supports different body definitions. In the modelType field below, select the modelType corresponding\n to the service item you wish to update and the documentation will update with the new definition.\n\n* Addresses: To update a destination service item's SIT destination final address, update the shipment delivery address.\nFor approved shipments, please use [updateShipmentDestinationAddress](#mtoShipment/updateShipmentDestinationAddress).\nFor shipments not yet approved, please use [updateMTOShipmentAddress](#mtoShipment/updateMTOShipmentAddress).\n\n* SIT Service Items: Take note that when updating ` + "`" + `sitCustomerContacted` + "`" + `, ` + "`" + `sitDepartureDate` + "`" + `, or ` + "`" + `sitRequestedDelivery` + "`" + `, we want\nthose to be updated on ` + "`" + `DOASIT` + "`" + ` (for origin SIT) and ` + "`" + `DDASIT` + "`" + ` (for destination SIT). If updating those values in other service\nitems, the office users will not have as much attention to those values.\n\nTo create a service item, please use [createMTOServiceItem](#mtoServiceItem/createMTOServiceItem)) endpoint.\n\n* Resubmitting rejected SIT service items: This endpoint will handle the logic of changing the status of rejected SIT service items from\nREJECTED to SUBMITTED. Please provide the ` + "`" + `requestedApprovalsRequestedStatus: true` + "`" + ` when resubmitting as this will give attention to the TOO to\nreview the resubmitted SIT service item. Another note, ` + "`" + `updateReason` + "`" + ` must have a different value than the current ` + "`" + `reason` + "`" + ` value on the service item.\nIf this value is not updated, then an error will be sent back.\n\nThe following SIT service items can be resubmitted following a rejection:\n- DDASIT\n- DDDSIT\n- DDFSIT\n- DOASIT\n- DOPSIT\n- DOFSIT\n- DDSFSC\n- DOSFSC\n\nAt a MINIMUM, the payload for resubmitting a rejected SIT service item must look like this:\n` + "`" + `` + "`" + `` + "`" + `json\n{\n \"reServiceCode\": \"DDFSIT\",\n \"updateReason\": \"A reason that differs from the previous reason\",\n \"modelType\": \"UpdateMTOServiceItemSIT\",\n \"requestApprovalsRequestedStatus\": true\n}\n` + "`" + `` + "`" + `` + "`" + `\n", "consumes": [ "application/json" ], @@ -567,7 +567,7 @@ func init() { }, "/mto-shipments/{mtoShipmentID}/addresses/{addressID}": { "put": { - "description": "### Functionality\nThis endpoint is used to **update** the pickup, secondary, and destination addresses on an MTO Shipment. mto-shipments/{mtoShipmentID}/shipment-address-updates is for updating a delivery address. The address details completely replace the original, except for the UUID.\nTherefore a complete address should be sent in the request.\nWhen a destination address on a shipment is updated, the destination SIT service items address ID will also be updated so that shipment and service item final destinations match.\n\nThis endpoint **cannot create** an address.\nTo create an address on an MTO shipment, the caller must use [updateMTOShipment](#operation/updateMTOShipment) as the parent shipment has to be updated with the appropriate link to the address.\n\n### Errors\nThe address must be associated with the mtoShipment passed in the url.\nIn other words, it should be listed as pickupAddress, destinationAddress, secondaryPickupAddress or secondaryDeliveryAddress on the mtoShipment provided.\nIf it is not, caller will receive a **Conflict** Error.\n\nThe mtoShipment should be associated with an MTO that is available to prime.\nIf the caller requests an update to an address, and the shipment is not on an available MTO, the caller will receive a **NotFound** Error.\n", + "description": "### Functionality\nThis endpoint is used to **update** the pickup, secondary, and delivery addresses on an MTO Shipment. mto-shipments/{mtoShipmentID}/shipment-address-updates is for updating a delivery address. The address details completely replace the original, except for the UUID.\nTherefore a complete address should be sent in the request.\nWhen a delivery address on a shipment is updated, the destination SIT service items address ID will also be updated so that shipment and service item final destinations match.\n\nThis endpoint **cannot create** an address.\nTo create an address on an MTO shipment, the caller must use [updateMTOShipment](#operation/updateMTOShipment) as the parent shipment has to be updated with the appropriate link to the address.\n\n### Errors\nThe address must be associated with the mtoShipment passed in the url.\nIn other words, it should be listed as pickupAddress, destinationAddress, secondaryPickupAddress or secondaryDeliveryAddress on the mtoShipment provided.\nIf it is not, caller will receive a **Conflict** Error.\n\nThe mtoShipment should be associated with an MTO that is available to prime.\nIf the caller requests an update to an address, and the shipment is not on an available MTO, the caller will receive a **NotFound** Error.\n", "consumes": [ "application/json" ], @@ -858,7 +858,7 @@ func init() { }, "/mto-shipments/{mtoShipmentID}/shipment-address-updates": { "post": { - "description": "### Functionality\nThis endpoint is used so the Prime can request an **update** for the destination address on an MTO Shipment,\nafter the destination address has already been approved.\n\nThis endpoint and operation only supports the following shipment types:\n- HHG\n- NTSR\n\nFor HHG shipments, if automatically approved or TOO approves, this will update the final destination address values for destination SIT service items to be the same as the changed destination address that was approved.\n\nAddress updates will be automatically approved unless they change:\n - The service area\n - Mileage bracket for direct delivery\n - the address and the distance between the old and new address is \u003e 50\n - Domestic Short Haul to Domestic Line Haul or vice versa\n - Shipments that start and end in one ZIP3 use Short Haul pricing\n - Shipments that start and end in different ZIP3s use Line Haul pricing\n\nFor those, changes will require TOO approval.\n", + "description": "### Functionality\nThis endpoint is used so the Prime can request an **update** for the delivery address on an MTO Shipment,\nafter the delivery address has already been approved.\n\nThis endpoint and operation only supports the following shipment types:\n- HHG\n- NTSR\n\nFor HHG shipments, if automatically approved or TOO approves, this will update the final delivery address values for destination SIT service items to be the same as the changed delivery address that was approved.\n\nAddress updates will be automatically approved unless they change:\n - The service area\n - Mileage bracket for direct delivery\n - the address and the distance between the old and new address is \u003e 50\n - Domestic Short Haul to Domestic Line Haul or vice versa\n - Shipments that start and end in one ZIP3 use Short Haul pricing\n - Shipments that start and end in different ZIP3s use Line Haul pricing\n\nFor those, changes will require TOO approval.\n", "consumes": [ "application/json" ], @@ -2260,15 +2260,88 @@ func init() { } ] }, + "MTOServiceItemInternationalCrating": { + "description": "Describes a international crating/uncrating service item subtype of a MTOServiceItem.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItem" + }, + { + "type": "object", + "required": [ + "reServiceCode", + "item", + "crate", + "description" + ], + "properties": { + "crate": { + "description": "The dimensions for the crate the item will be shipped in.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItemDimension" + } + ] + }, + "description": { + "description": "A description of the item being crated.", + "type": "string", + "example": "Decorated horse head to be crated." + }, + "externalCrate": { + "type": "boolean", + "x-nullable": true + }, + "item": { + "description": "The dimensions of the item being crated.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItemDimension" + } + ] + }, + "market": { + "description": "To identify whether the service was provided within (CONUS) or (OCONUS)", + "type": "string", + "enum": [ + "CONUS", + "OCONUS" + ], + "example": "CONUS" + }, + "reServiceCode": { + "description": "A unique code for the service item. Indicates if the service is for crating (ICRT) or uncrating (IUCRT).", + "type": "string", + "enum": [ + "ICRT", + "IUCRT" + ] + }, + "reason": { + "description": "The contractor's explanation for why an item needed to be crated or uncrated. Used by the TOO while deciding to approve or reject the service item.\n", + "type": "string", + "x-nullable": true, + "x-omitempty": false, + "example": "Storage items need to be picked up" + }, + "standaloneCrate": { + "type": "boolean", + "x-nullable": true + } + } + } + ] + }, "MTOServiceItemModelType": { - "description": "Describes all model sub-types for a MTOServiceItem model.\n\nUsing this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DOFSIT, DOASIT - MTOServiceItemOriginSIT\n * DDFSIT, DDASIT - MTOServiceItemDestSIT\n * DOSHUT, DDSHUT - MTOServiceItemShuttle\n * DCRT, DUCRT - MTOServiceItemDomesticCrating\n\nThe documentation will then update with the supported fields.\n", + "description": "Describes all model sub-types for a MTOServiceItem model.\n\nUsing this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DOFSIT, DOASIT - MTOServiceItemOriginSIT\n * DDFSIT, DDASIT - MTOServiceItemDestSIT\n * DOSHUT, DDSHUT - MTOServiceItemShuttle\n * DCRT, DUCRT - MTOServiceItemDomesticCrating\n * ICRT, IUCRT - MTOServiceItemInternationalCrating\n\nThe documentation will then update with the supported fields.\n", "type": "string", "enum": [ "MTOServiceItemBasic", "MTOServiceItemOriginSIT", "MTOServiceItemDestSIT", "MTOServiceItemShuttle", - "MTOServiceItemDomesticCrating" + "MTOServiceItemDomesticCrating", + "MTOServiceItemInternationalCrating" ] }, "MTOServiceItemOriginSIT": { @@ -3026,6 +3099,13 @@ func init() { "x-omitempty": false, "example": false }, + "maxIncentive": { + "description": "The max amount the government will pay the service member to move their belongings based on the moving date, locations, and shipment weight.", + "type": "integer", + "format": "cents", + "x-nullable": true, + "x-omitempty": false + }, "proGearWeight": { "description": "The estimated weight of the pro-gear being moved belonging to the service member in pounds.", "type": "integer", @@ -3352,7 +3432,6 @@ func init() { "ICOLH", "ICOUB", "ICRT", - "ICRTSA", "IDASIT", "IDDSIT", "IDFSIT", @@ -3675,7 +3754,7 @@ func init() { } }, "ShipmentAddressUpdate": { - "description": "This represents a destination address change request made by the Prime that is either auto-approved or requires review if the pricing criteria has changed. If criteria has changed, then it must be approved or rejected by a TOO.\n", + "description": "This represents a delivery address change request made by the Prime that is either auto-approved or requires review if the pricing criteria has changed. If criteria has changed, then it must be approved or rejected by a TOO.\n", "type": "object", "required": [ "id", @@ -3703,7 +3782,7 @@ func init() { "$ref": "#/definitions/Address" }, "newSitDistanceBetween": { - "description": "The distance between the original SIT address and requested new destination address of shipment", + "description": "The distance between the original SIT address and requested new delivery address of shipment", "type": "integer", "example": 88 }, @@ -3715,7 +3794,7 @@ func init() { "example": "This is an office remark" }, "oldSitDistanceBetween": { - "description": "The distance between the original SIT address and the previous/old destination address of shipment", + "description": "The distance between the original SIT address and the previous/old delivery address of shipment", "type": "integer", "example": 50 }, @@ -4204,7 +4283,7 @@ func init() { } }, "UpdateShipmentDestinationAddress": { - "description": "UpdateShipmentDestinationAddress contains the fields required for the prime to request an update for the destination address on an MTO Shipment.", + "description": "UpdateShipmentDestinationAddress contains the fields required for the prime to request an update for the delivery address on an MTO Shipment.", "type": "object", "required": [ "contractorRemarks", @@ -4825,7 +4904,7 @@ func init() { }, "/mto-service-items/{mtoServiceItemID}": { "patch": { - "description": "Updates MTOServiceItems after creation. Not all service items or fields may be updated, please see details below.\n\nThis endpoint supports different body definitions. In the modelType field below, select the modelType corresponding\n to the service item you wish to update and the documentation will update with the new definition.\n\n* Addresses: To update a destination service item's SIT destination final address, update the shipment destination address.\nFor approved shipments, please use [updateShipmentDestinationAddress](#mtoShipment/updateShipmentDestinationAddress).\nFor shipments not yet approved, please use [updateMTOShipmentAddress](#mtoShipment/updateMTOShipmentAddress).\n\n* SIT Service Items: Take note that when updating ` + "`" + `sitCustomerContacted` + "`" + `, ` + "`" + `sitDepartureDate` + "`" + `, or ` + "`" + `sitRequestedDelivery` + "`" + `, we want\nthose to be updated on ` + "`" + `DOASIT` + "`" + ` (for origin SIT) and ` + "`" + `DDASIT` + "`" + ` (for destination SIT). If updating those values in other service\nitems, the office users will not have as much attention to those values.\n\nTo create a service item, please use [createMTOServiceItem](#mtoServiceItem/createMTOServiceItem)) endpoint.\n\n* Resubmitting rejected SIT service items: This endpoint will handle the logic of changing the status of rejected SIT service items from\nREJECTED to SUBMITTED. Please provide the ` + "`" + `requestedApprovalsRequestedStatus: true` + "`" + ` when resubmitting as this will give attention to the TOO to\nreview the resubmitted SIT service item. Another note, ` + "`" + `updateReason` + "`" + ` must have a different value than the current ` + "`" + `reason` + "`" + ` value on the service item.\nIf this value is not updated, then an error will be sent back.\n\nThe following SIT service items can be resubmitted following a rejection:\n- DDASIT\n- DDDSIT\n- DDFSIT\n- DOASIT\n- DOPSIT\n- DOFSIT\n- DDSFSC\n- DOSFSC\n\nAt a MINIMUM, the payload for resubmitting a rejected SIT service item must look like this:\n` + "`" + `` + "`" + `` + "`" + `json\n{\n \"reServiceCode\": \"DDFSIT\",\n \"updateReason\": \"A reason that differs from the previous reason\",\n \"modelType\": \"UpdateMTOServiceItemSIT\",\n \"requestApprovalsRequestedStatus\": true\n}\n` + "`" + `` + "`" + `` + "`" + `\n", + "description": "Updates MTOServiceItems after creation. Not all service items or fields may be updated, please see details below.\n\nThis endpoint supports different body definitions. In the modelType field below, select the modelType corresponding\n to the service item you wish to update and the documentation will update with the new definition.\n\n* Addresses: To update a destination service item's SIT destination final address, update the shipment delivery address.\nFor approved shipments, please use [updateShipmentDestinationAddress](#mtoShipment/updateShipmentDestinationAddress).\nFor shipments not yet approved, please use [updateMTOShipmentAddress](#mtoShipment/updateMTOShipmentAddress).\n\n* SIT Service Items: Take note that when updating ` + "`" + `sitCustomerContacted` + "`" + `, ` + "`" + `sitDepartureDate` + "`" + `, or ` + "`" + `sitRequestedDelivery` + "`" + `, we want\nthose to be updated on ` + "`" + `DOASIT` + "`" + ` (for origin SIT) and ` + "`" + `DDASIT` + "`" + ` (for destination SIT). If updating those values in other service\nitems, the office users will not have as much attention to those values.\n\nTo create a service item, please use [createMTOServiceItem](#mtoServiceItem/createMTOServiceItem)) endpoint.\n\n* Resubmitting rejected SIT service items: This endpoint will handle the logic of changing the status of rejected SIT service items from\nREJECTED to SUBMITTED. Please provide the ` + "`" + `requestedApprovalsRequestedStatus: true` + "`" + ` when resubmitting as this will give attention to the TOO to\nreview the resubmitted SIT service item. Another note, ` + "`" + `updateReason` + "`" + ` must have a different value than the current ` + "`" + `reason` + "`" + ` value on the service item.\nIf this value is not updated, then an error will be sent back.\n\nThe following SIT service items can be resubmitted following a rejection:\n- DDASIT\n- DDDSIT\n- DDFSIT\n- DOASIT\n- DOPSIT\n- DOFSIT\n- DDSFSC\n- DOSFSC\n\nAt a MINIMUM, the payload for resubmitting a rejected SIT service item must look like this:\n` + "`" + `` + "`" + `` + "`" + `json\n{\n \"reServiceCode\": \"DDFSIT\",\n \"updateReason\": \"A reason that differs from the previous reason\",\n \"modelType\": \"UpdateMTOServiceItemSIT\",\n \"requestApprovalsRequestedStatus\": true\n}\n` + "`" + `` + "`" + `` + "`" + `\n", "consumes": [ "application/json" ], @@ -5101,7 +5180,7 @@ func init() { }, "/mto-shipments/{mtoShipmentID}/addresses/{addressID}": { "put": { - "description": "### Functionality\nThis endpoint is used to **update** the pickup, secondary, and destination addresses on an MTO Shipment. mto-shipments/{mtoShipmentID}/shipment-address-updates is for updating a delivery address. The address details completely replace the original, except for the UUID.\nTherefore a complete address should be sent in the request.\nWhen a destination address on a shipment is updated, the destination SIT service items address ID will also be updated so that shipment and service item final destinations match.\n\nThis endpoint **cannot create** an address.\nTo create an address on an MTO shipment, the caller must use [updateMTOShipment](#operation/updateMTOShipment) as the parent shipment has to be updated with the appropriate link to the address.\n\n### Errors\nThe address must be associated with the mtoShipment passed in the url.\nIn other words, it should be listed as pickupAddress, destinationAddress, secondaryPickupAddress or secondaryDeliveryAddress on the mtoShipment provided.\nIf it is not, caller will receive a **Conflict** Error.\n\nThe mtoShipment should be associated with an MTO that is available to prime.\nIf the caller requests an update to an address, and the shipment is not on an available MTO, the caller will receive a **NotFound** Error.\n", + "description": "### Functionality\nThis endpoint is used to **update** the pickup, secondary, and delivery addresses on an MTO Shipment. mto-shipments/{mtoShipmentID}/shipment-address-updates is for updating a delivery address. The address details completely replace the original, except for the UUID.\nTherefore a complete address should be sent in the request.\nWhen a delivery address on a shipment is updated, the destination SIT service items address ID will also be updated so that shipment and service item final destinations match.\n\nThis endpoint **cannot create** an address.\nTo create an address on an MTO shipment, the caller must use [updateMTOShipment](#operation/updateMTOShipment) as the parent shipment has to be updated with the appropriate link to the address.\n\n### Errors\nThe address must be associated with the mtoShipment passed in the url.\nIn other words, it should be listed as pickupAddress, destinationAddress, secondaryPickupAddress or secondaryDeliveryAddress on the mtoShipment provided.\nIf it is not, caller will receive a **Conflict** Error.\n\nThe mtoShipment should be associated with an MTO that is available to prime.\nIf the caller requests an update to an address, and the shipment is not on an available MTO, the caller will receive a **NotFound** Error.\n", "consumes": [ "application/json" ], @@ -5494,7 +5573,7 @@ func init() { }, "/mto-shipments/{mtoShipmentID}/shipment-address-updates": { "post": { - "description": "### Functionality\nThis endpoint is used so the Prime can request an **update** for the destination address on an MTO Shipment,\nafter the destination address has already been approved.\n\nThis endpoint and operation only supports the following shipment types:\n- HHG\n- NTSR\n\nFor HHG shipments, if automatically approved or TOO approves, this will update the final destination address values for destination SIT service items to be the same as the changed destination address that was approved.\n\nAddress updates will be automatically approved unless they change:\n - The service area\n - Mileage bracket for direct delivery\n - the address and the distance between the old and new address is \u003e 50\n - Domestic Short Haul to Domestic Line Haul or vice versa\n - Shipments that start and end in one ZIP3 use Short Haul pricing\n - Shipments that start and end in different ZIP3s use Line Haul pricing\n\nFor those, changes will require TOO approval.\n", + "description": "### Functionality\nThis endpoint is used so the Prime can request an **update** for the delivery address on an MTO Shipment,\nafter the delivery address has already been approved.\n\nThis endpoint and operation only supports the following shipment types:\n- HHG\n- NTSR\n\nFor HHG shipments, if automatically approved or TOO approves, this will update the final delivery address values for destination SIT service items to be the same as the changed delivery address that was approved.\n\nAddress updates will be automatically approved unless they change:\n - The service area\n - Mileage bracket for direct delivery\n - the address and the distance between the old and new address is \u003e 50\n - Domestic Short Haul to Domestic Line Haul or vice versa\n - Shipments that start and end in one ZIP3 use Short Haul pricing\n - Shipments that start and end in different ZIP3s use Line Haul pricing\n\nFor those, changes will require TOO approval.\n", "consumes": [ "application/json" ], @@ -7005,15 +7084,88 @@ func init() { } ] }, + "MTOServiceItemInternationalCrating": { + "description": "Describes a international crating/uncrating service item subtype of a MTOServiceItem.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItem" + }, + { + "type": "object", + "required": [ + "reServiceCode", + "item", + "crate", + "description" + ], + "properties": { + "crate": { + "description": "The dimensions for the crate the item will be shipped in.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItemDimension" + } + ] + }, + "description": { + "description": "A description of the item being crated.", + "type": "string", + "example": "Decorated horse head to be crated." + }, + "externalCrate": { + "type": "boolean", + "x-nullable": true + }, + "item": { + "description": "The dimensions of the item being crated.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItemDimension" + } + ] + }, + "market": { + "description": "To identify whether the service was provided within (CONUS) or (OCONUS)", + "type": "string", + "enum": [ + "CONUS", + "OCONUS" + ], + "example": "CONUS" + }, + "reServiceCode": { + "description": "A unique code for the service item. Indicates if the service is for crating (ICRT) or uncrating (IUCRT).", + "type": "string", + "enum": [ + "ICRT", + "IUCRT" + ] + }, + "reason": { + "description": "The contractor's explanation for why an item needed to be crated or uncrated. Used by the TOO while deciding to approve or reject the service item.\n", + "type": "string", + "x-nullable": true, + "x-omitempty": false, + "example": "Storage items need to be picked up" + }, + "standaloneCrate": { + "type": "boolean", + "x-nullable": true + } + } + } + ] + }, "MTOServiceItemModelType": { - "description": "Describes all model sub-types for a MTOServiceItem model.\n\nUsing this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DOFSIT, DOASIT - MTOServiceItemOriginSIT\n * DDFSIT, DDASIT - MTOServiceItemDestSIT\n * DOSHUT, DDSHUT - MTOServiceItemShuttle\n * DCRT, DUCRT - MTOServiceItemDomesticCrating\n\nThe documentation will then update with the supported fields.\n", + "description": "Describes all model sub-types for a MTOServiceItem model.\n\nUsing this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DOFSIT, DOASIT - MTOServiceItemOriginSIT\n * DDFSIT, DDASIT - MTOServiceItemDestSIT\n * DOSHUT, DDSHUT - MTOServiceItemShuttle\n * DCRT, DUCRT - MTOServiceItemDomesticCrating\n * ICRT, IUCRT - MTOServiceItemInternationalCrating\n\nThe documentation will then update with the supported fields.\n", "type": "string", "enum": [ "MTOServiceItemBasic", "MTOServiceItemOriginSIT", "MTOServiceItemDestSIT", "MTOServiceItemShuttle", - "MTOServiceItemDomesticCrating" + "MTOServiceItemDomesticCrating", + "MTOServiceItemInternationalCrating" ] }, "MTOServiceItemOriginSIT": { @@ -7771,6 +7923,13 @@ func init() { "x-omitempty": false, "example": false }, + "maxIncentive": { + "description": "The max amount the government will pay the service member to move their belongings based on the moving date, locations, and shipment weight.", + "type": "integer", + "format": "cents", + "x-nullable": true, + "x-omitempty": false + }, "proGearWeight": { "description": "The estimated weight of the pro-gear being moved belonging to the service member in pounds.", "type": "integer", @@ -8097,7 +8256,6 @@ func init() { "ICOLH", "ICOUB", "ICRT", - "ICRTSA", "IDASIT", "IDDSIT", "IDFSIT", @@ -8423,7 +8581,7 @@ func init() { } }, "ShipmentAddressUpdate": { - "description": "This represents a destination address change request made by the Prime that is either auto-approved or requires review if the pricing criteria has changed. If criteria has changed, then it must be approved or rejected by a TOO.\n", + "description": "This represents a delivery address change request made by the Prime that is either auto-approved or requires review if the pricing criteria has changed. If criteria has changed, then it must be approved or rejected by a TOO.\n", "type": "object", "required": [ "id", @@ -8451,7 +8609,7 @@ func init() { "$ref": "#/definitions/Address" }, "newSitDistanceBetween": { - "description": "The distance between the original SIT address and requested new destination address of shipment", + "description": "The distance between the original SIT address and requested new delivery address of shipment", "type": "integer", "minimum": 0, "example": 88 @@ -8464,7 +8622,7 @@ func init() { "example": "This is an office remark" }, "oldSitDistanceBetween": { - "description": "The distance between the original SIT address and the previous/old destination address of shipment", + "description": "The distance between the original SIT address and the previous/old delivery address of shipment", "type": "integer", "minimum": 0, "example": 50 @@ -8954,7 +9112,7 @@ func init() { } }, "UpdateShipmentDestinationAddress": { - "description": "UpdateShipmentDestinationAddress contains the fields required for the prime to request an update for the destination address on an MTO Shipment.", + "description": "UpdateShipmentDestinationAddress contains the fields required for the prime to request an update for the delivery address on an MTO Shipment.", "type": "object", "required": [ "contractorRemarks", diff --git a/pkg/gen/primeapi/primeoperations/mto_service_item/update_m_t_o_service_item.go b/pkg/gen/primeapi/primeoperations/mto_service_item/update_m_t_o_service_item.go index f8b307b79f0..f7f4c334317 100644 --- a/pkg/gen/primeapi/primeoperations/mto_service_item/update_m_t_o_service_item.go +++ b/pkg/gen/primeapi/primeoperations/mto_service_item/update_m_t_o_service_item.go @@ -40,7 +40,7 @@ This endpoint supports different body definitions. In the modelType field below, to the service item you wish to update and the documentation will update with the new definition. -* Addresses: To update a destination service item's SIT destination final address, update the shipment destination address. +* Addresses: To update a destination service item's SIT destination final address, update the shipment delivery address. For approved shipments, please use [updateShipmentDestinationAddress](#mtoShipment/updateShipmentDestinationAddress). For shipments not yet approved, please use [updateMTOShipmentAddress](#mtoShipment/updateMTOShipmentAddress). diff --git a/pkg/gen/primeapi/primeoperations/mto_shipment/update_m_t_o_shipment_address.go b/pkg/gen/primeapi/primeoperations/mto_shipment/update_m_t_o_shipment_address.go index d0c377c5163..b5d1e738c34 100644 --- a/pkg/gen/primeapi/primeoperations/mto_shipment/update_m_t_o_shipment_address.go +++ b/pkg/gen/primeapi/primeoperations/mto_shipment/update_m_t_o_shipment_address.go @@ -35,9 +35,9 @@ func NewUpdateMTOShipmentAddress(ctx *middleware.Context, handler UpdateMTOShipm updateMTOShipmentAddress ### Functionality -This endpoint is used to **update** the pickup, secondary, and destination addresses on an MTO Shipment. mto-shipments/{mtoShipmentID}/shipment-address-updates is for updating a delivery address. The address details completely replace the original, except for the UUID. +This endpoint is used to **update** the pickup, secondary, and delivery addresses on an MTO Shipment. mto-shipments/{mtoShipmentID}/shipment-address-updates is for updating a delivery address. The address details completely replace the original, except for the UUID. Therefore a complete address should be sent in the request. -When a destination address on a shipment is updated, the destination SIT service items address ID will also be updated so that shipment and service item final destinations match. +When a delivery address on a shipment is updated, the destination SIT service items address ID will also be updated so that shipment and service item final destinations match. This endpoint **cannot create** an address. To create an address on an MTO shipment, the caller must use [updateMTOShipment](#operation/updateMTOShipment) as the parent shipment has to be updated with the appropriate link to the address. diff --git a/pkg/gen/primeapi/primeoperations/mto_shipment/update_shipment_destination_address.go b/pkg/gen/primeapi/primeoperations/mto_shipment/update_shipment_destination_address.go index 7f6b0337bbf..05d4da3e964 100644 --- a/pkg/gen/primeapi/primeoperations/mto_shipment/update_shipment_destination_address.go +++ b/pkg/gen/primeapi/primeoperations/mto_shipment/update_shipment_destination_address.go @@ -35,14 +35,14 @@ func NewUpdateShipmentDestinationAddress(ctx *middleware.Context, handler Update updateShipmentDestinationAddress ### Functionality -This endpoint is used so the Prime can request an **update** for the destination address on an MTO Shipment, -after the destination address has already been approved. +This endpoint is used so the Prime can request an **update** for the delivery address on an MTO Shipment, +after the delivery address has already been approved. This endpoint and operation only supports the following shipment types: - HHG - NTSR -For HHG shipments, if automatically approved or TOO approves, this will update the final destination address values for destination SIT service items to be the same as the changed destination address that was approved. +For HHG shipments, if automatically approved or TOO approves, this will update the final delivery address values for destination SIT service items to be the same as the changed delivery address that was approved. Address updates will be automatically approved unless they change: - The service area diff --git a/pkg/gen/primeclient/mto_service_item/mto_service_item_client.go b/pkg/gen/primeclient/mto_service_item/mto_service_item_client.go index 97e446ab844..76bd5b73e25 100644 --- a/pkg/gen/primeclient/mto_service_item/mto_service_item_client.go +++ b/pkg/gen/primeclient/mto_service_item/mto_service_item_client.go @@ -223,7 +223,7 @@ This endpoint supports different body definitions. In the modelType field below, to the service item you wish to update and the documentation will update with the new definition. -* Addresses: To update a destination service item's SIT destination final address, update the shipment destination address. +* Addresses: To update a destination service item's SIT destination final address, update the shipment delivery address. For approved shipments, please use [updateShipmentDestinationAddress](#mtoShipment/updateShipmentDestinationAddress). For shipments not yet approved, please use [updateMTOShipmentAddress](#mtoShipment/updateMTOShipmentAddress). diff --git a/pkg/gen/primeclient/mto_shipment/mto_shipment_client.go b/pkg/gen/primeclient/mto_shipment/mto_shipment_client.go index 47d681c22e7..2051172f1d1 100644 --- a/pkg/gen/primeclient/mto_shipment/mto_shipment_client.go +++ b/pkg/gen/primeclient/mto_shipment/mto_shipment_client.go @@ -319,9 +319,9 @@ func (a *Client) UpdateMTOShipment(params *UpdateMTOShipmentParams, opts ...Clie ### Functionality -This endpoint is used to **update** the pickup, secondary, and destination addresses on an MTO Shipment. mto-shipments/{mtoShipmentID}/shipment-address-updates is for updating a delivery address. The address details completely replace the original, except for the UUID. +This endpoint is used to **update** the pickup, secondary, and delivery addresses on an MTO Shipment. mto-shipments/{mtoShipmentID}/shipment-address-updates is for updating a delivery address. The address details completely replace the original, except for the UUID. Therefore a complete address should be sent in the request. -When a destination address on a shipment is updated, the destination SIT service items address ID will also be updated so that shipment and service item final destinations match. +When a delivery address on a shipment is updated, the destination SIT service items address ID will also be updated so that shipment and service item final destinations match. This endpoint **cannot create** an address. To create an address on an MTO shipment, the caller must use [updateMTOShipment](#operation/updateMTOShipment) as the parent shipment has to be updated with the appropriate link to the address. @@ -464,14 +464,14 @@ func (a *Client) UpdateReweigh(params *UpdateReweighParams, opts ...ClientOption ### Functionality -This endpoint is used so the Prime can request an **update** for the destination address on an MTO Shipment, -after the destination address has already been approved. +This endpoint is used so the Prime can request an **update** for the delivery address on an MTO Shipment, +after the delivery address has already been approved. This endpoint and operation only supports the following shipment types: - HHG - NTSR -For HHG shipments, if automatically approved or TOO approves, this will update the final destination address values for destination SIT service items to be the same as the changed destination address that was approved. +For HHG shipments, if automatically approved or TOO approves, this will update the final delivery address values for destination SIT service items to be the same as the changed delivery address that was approved. Address updates will be automatically approved unless they change: - The service area diff --git a/pkg/gen/primemessages/m_t_o_service_item.go b/pkg/gen/primemessages/m_t_o_service_item.go index 76cc5d55483..375c7eca7e0 100644 --- a/pkg/gen/primemessages/m_t_o_service_item.go +++ b/pkg/gen/primemessages/m_t_o_service_item.go @@ -273,6 +273,12 @@ func unmarshalMTOServiceItem(data []byte, consumer runtime.Consumer) (MTOService return nil, err } return &result, nil + case "MTOServiceItemInternationalCrating": + var result MTOServiceItemInternationalCrating + if err := consumer.Consume(buf2, &result); err != nil { + return nil, err + } + return &result, nil case "MTOServiceItemOriginSIT": var result MTOServiceItemOriginSIT if err := consumer.Consume(buf2, &result); err != nil { diff --git a/pkg/gen/primemessages/m_t_o_service_item_international_crating.go b/pkg/gen/primemessages/m_t_o_service_item_international_crating.go new file mode 100644 index 00000000000..eaac651416d --- /dev/null +++ b/pkg/gen/primemessages/m_t_o_service_item_international_crating.go @@ -0,0 +1,773 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package primemessages + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "bytes" + "context" + "encoding/json" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// MTOServiceItemInternationalCrating Describes a international crating/uncrating service item subtype of a MTOServiceItem. +// +// swagger:model MTOServiceItemInternationalCrating +type MTOServiceItemInternationalCrating struct { + eTagField string + + idField strfmt.UUID + + lockedPriceCentsField *int64 + + moveTaskOrderIdField *strfmt.UUID + + mtoShipmentIdField strfmt.UUID + + reServiceNameField string + + rejectionReasonField *string + + serviceRequestDocumentsField ServiceRequestDocuments + + statusField MTOServiceItemStatus + + // The dimensions for the crate the item will be shipped in. + // Required: true + Crate struct { + MTOServiceItemDimension + } `json:"crate"` + + // A description of the item being crated. + // Example: Decorated horse head to be crated. + // Required: true + Description *string `json:"description"` + + // external crate + ExternalCrate *bool `json:"externalCrate,omitempty"` + + // The dimensions of the item being crated. + // Required: true + Item struct { + MTOServiceItemDimension + } `json:"item"` + + // To identify whether the service was provided within (CONUS) or (OCONUS) + // Example: CONUS + // Enum: [CONUS OCONUS] + Market string `json:"market,omitempty"` + + // A unique code for the service item. Indicates if the service is for crating (ICRT) or uncrating (IUCRT). + // Required: true + // Enum: [ICRT IUCRT] + ReServiceCode *string `json:"reServiceCode"` + + // The contractor's explanation for why an item needed to be crated or uncrated. Used by the TOO while deciding to approve or reject the service item. + // + // Example: Storage items need to be picked up + Reason *string `json:"reason"` + + // standalone crate + StandaloneCrate *bool `json:"standaloneCrate,omitempty"` +} + +// ETag gets the e tag of this subtype +func (m *MTOServiceItemInternationalCrating) ETag() string { + return m.eTagField +} + +// SetETag sets the e tag of this subtype +func (m *MTOServiceItemInternationalCrating) SetETag(val string) { + m.eTagField = val +} + +// ID gets the id of this subtype +func (m *MTOServiceItemInternationalCrating) ID() strfmt.UUID { + return m.idField +} + +// SetID sets the id of this subtype +func (m *MTOServiceItemInternationalCrating) SetID(val strfmt.UUID) { + m.idField = val +} + +// LockedPriceCents gets the locked price cents of this subtype +func (m *MTOServiceItemInternationalCrating) LockedPriceCents() *int64 { + return m.lockedPriceCentsField +} + +// SetLockedPriceCents sets the locked price cents of this subtype +func (m *MTOServiceItemInternationalCrating) SetLockedPriceCents(val *int64) { + m.lockedPriceCentsField = val +} + +// ModelType gets the model type of this subtype +func (m *MTOServiceItemInternationalCrating) ModelType() MTOServiceItemModelType { + return "MTOServiceItemInternationalCrating" +} + +// SetModelType sets the model type of this subtype +func (m *MTOServiceItemInternationalCrating) SetModelType(val MTOServiceItemModelType) { +} + +// MoveTaskOrderID gets the move task order ID of this subtype +func (m *MTOServiceItemInternationalCrating) MoveTaskOrderID() *strfmt.UUID { + return m.moveTaskOrderIdField +} + +// SetMoveTaskOrderID sets the move task order ID of this subtype +func (m *MTOServiceItemInternationalCrating) SetMoveTaskOrderID(val *strfmt.UUID) { + m.moveTaskOrderIdField = val +} + +// MtoShipmentID gets the mto shipment ID of this subtype +func (m *MTOServiceItemInternationalCrating) MtoShipmentID() strfmt.UUID { + return m.mtoShipmentIdField +} + +// SetMtoShipmentID sets the mto shipment ID of this subtype +func (m *MTOServiceItemInternationalCrating) SetMtoShipmentID(val strfmt.UUID) { + m.mtoShipmentIdField = val +} + +// ReServiceName gets the re service name of this subtype +func (m *MTOServiceItemInternationalCrating) ReServiceName() string { + return m.reServiceNameField +} + +// SetReServiceName sets the re service name of this subtype +func (m *MTOServiceItemInternationalCrating) SetReServiceName(val string) { + m.reServiceNameField = val +} + +// RejectionReason gets the rejection reason of this subtype +func (m *MTOServiceItemInternationalCrating) RejectionReason() *string { + return m.rejectionReasonField +} + +// SetRejectionReason sets the rejection reason of this subtype +func (m *MTOServiceItemInternationalCrating) SetRejectionReason(val *string) { + m.rejectionReasonField = val +} + +// ServiceRequestDocuments gets the service request documents of this subtype +func (m *MTOServiceItemInternationalCrating) ServiceRequestDocuments() ServiceRequestDocuments { + return m.serviceRequestDocumentsField +} + +// SetServiceRequestDocuments sets the service request documents of this subtype +func (m *MTOServiceItemInternationalCrating) SetServiceRequestDocuments(val ServiceRequestDocuments) { + m.serviceRequestDocumentsField = val +} + +// Status gets the status of this subtype +func (m *MTOServiceItemInternationalCrating) Status() MTOServiceItemStatus { + return m.statusField +} + +// SetStatus sets the status of this subtype +func (m *MTOServiceItemInternationalCrating) SetStatus(val MTOServiceItemStatus) { + m.statusField = val +} + +// UnmarshalJSON unmarshals this object with a polymorphic type from a JSON structure +func (m *MTOServiceItemInternationalCrating) UnmarshalJSON(raw []byte) error { + var data struct { + + // The dimensions for the crate the item will be shipped in. + // Required: true + Crate struct { + MTOServiceItemDimension + } `json:"crate"` + + // A description of the item being crated. + // Example: Decorated horse head to be crated. + // Required: true + Description *string `json:"description"` + + // external crate + ExternalCrate *bool `json:"externalCrate,omitempty"` + + // The dimensions of the item being crated. + // Required: true + Item struct { + MTOServiceItemDimension + } `json:"item"` + + // To identify whether the service was provided within (CONUS) or (OCONUS) + // Example: CONUS + // Enum: [CONUS OCONUS] + Market string `json:"market,omitempty"` + + // A unique code for the service item. Indicates if the service is for crating (ICRT) or uncrating (IUCRT). + // Required: true + // Enum: [ICRT IUCRT] + ReServiceCode *string `json:"reServiceCode"` + + // The contractor's explanation for why an item needed to be crated or uncrated. Used by the TOO while deciding to approve or reject the service item. + // + // Example: Storage items need to be picked up + Reason *string `json:"reason"` + + // standalone crate + StandaloneCrate *bool `json:"standaloneCrate,omitempty"` + } + buf := bytes.NewBuffer(raw) + dec := json.NewDecoder(buf) + dec.UseNumber() + + if err := dec.Decode(&data); err != nil { + return err + } + + var base struct { + /* Just the base type fields. Used for unmashalling polymorphic types.*/ + + ETag string `json:"eTag,omitempty"` + + ID strfmt.UUID `json:"id,omitempty"` + + LockedPriceCents *int64 `json:"lockedPriceCents,omitempty"` + + ModelType MTOServiceItemModelType `json:"modelType"` + + MoveTaskOrderID *strfmt.UUID `json:"moveTaskOrderID"` + + MtoShipmentID strfmt.UUID `json:"mtoShipmentID,omitempty"` + + ReServiceName string `json:"reServiceName,omitempty"` + + RejectionReason *string `json:"rejectionReason,omitempty"` + + ServiceRequestDocuments ServiceRequestDocuments `json:"serviceRequestDocuments,omitempty"` + + Status MTOServiceItemStatus `json:"status,omitempty"` + } + buf = bytes.NewBuffer(raw) + dec = json.NewDecoder(buf) + dec.UseNumber() + + if err := dec.Decode(&base); err != nil { + return err + } + + var result MTOServiceItemInternationalCrating + + result.eTagField = base.ETag + + result.idField = base.ID + + result.lockedPriceCentsField = base.LockedPriceCents + + if base.ModelType != result.ModelType() { + /* Not the type we're looking for. */ + return errors.New(422, "invalid modelType value: %q", base.ModelType) + } + result.moveTaskOrderIdField = base.MoveTaskOrderID + + result.mtoShipmentIdField = base.MtoShipmentID + + result.reServiceNameField = base.ReServiceName + + result.rejectionReasonField = base.RejectionReason + + result.serviceRequestDocumentsField = base.ServiceRequestDocuments + + result.statusField = base.Status + + result.Crate = data.Crate + result.Description = data.Description + result.ExternalCrate = data.ExternalCrate + result.Item = data.Item + result.Market = data.Market + result.ReServiceCode = data.ReServiceCode + result.Reason = data.Reason + result.StandaloneCrate = data.StandaloneCrate + + *m = result + + return nil +} + +// MarshalJSON marshals this object with a polymorphic type to a JSON structure +func (m MTOServiceItemInternationalCrating) MarshalJSON() ([]byte, error) { + var b1, b2, b3 []byte + var err error + b1, err = json.Marshal(struct { + + // The dimensions for the crate the item will be shipped in. + // Required: true + Crate struct { + MTOServiceItemDimension + } `json:"crate"` + + // A description of the item being crated. + // Example: Decorated horse head to be crated. + // Required: true + Description *string `json:"description"` + + // external crate + ExternalCrate *bool `json:"externalCrate,omitempty"` + + // The dimensions of the item being crated. + // Required: true + Item struct { + MTOServiceItemDimension + } `json:"item"` + + // To identify whether the service was provided within (CONUS) or (OCONUS) + // Example: CONUS + // Enum: [CONUS OCONUS] + Market string `json:"market,omitempty"` + + // A unique code for the service item. Indicates if the service is for crating (ICRT) or uncrating (IUCRT). + // Required: true + // Enum: [ICRT IUCRT] + ReServiceCode *string `json:"reServiceCode"` + + // The contractor's explanation for why an item needed to be crated or uncrated. Used by the TOO while deciding to approve or reject the service item. + // + // Example: Storage items need to be picked up + Reason *string `json:"reason"` + + // standalone crate + StandaloneCrate *bool `json:"standaloneCrate,omitempty"` + }{ + + Crate: m.Crate, + + Description: m.Description, + + ExternalCrate: m.ExternalCrate, + + Item: m.Item, + + Market: m.Market, + + ReServiceCode: m.ReServiceCode, + + Reason: m.Reason, + + StandaloneCrate: m.StandaloneCrate, + }) + if err != nil { + return nil, err + } + b2, err = json.Marshal(struct { + ETag string `json:"eTag,omitempty"` + + ID strfmt.UUID `json:"id,omitempty"` + + LockedPriceCents *int64 `json:"lockedPriceCents,omitempty"` + + ModelType MTOServiceItemModelType `json:"modelType"` + + MoveTaskOrderID *strfmt.UUID `json:"moveTaskOrderID"` + + MtoShipmentID strfmt.UUID `json:"mtoShipmentID,omitempty"` + + ReServiceName string `json:"reServiceName,omitempty"` + + RejectionReason *string `json:"rejectionReason,omitempty"` + + ServiceRequestDocuments ServiceRequestDocuments `json:"serviceRequestDocuments,omitempty"` + + Status MTOServiceItemStatus `json:"status,omitempty"` + }{ + + ETag: m.ETag(), + + ID: m.ID(), + + LockedPriceCents: m.LockedPriceCents(), + + ModelType: m.ModelType(), + + MoveTaskOrderID: m.MoveTaskOrderID(), + + MtoShipmentID: m.MtoShipmentID(), + + ReServiceName: m.ReServiceName(), + + RejectionReason: m.RejectionReason(), + + ServiceRequestDocuments: m.ServiceRequestDocuments(), + + Status: m.Status(), + }) + if err != nil { + return nil, err + } + + return swag.ConcatJSON(b1, b2, b3), nil +} + +// Validate validates this m t o service item international crating +func (m *MTOServiceItemInternationalCrating) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateID(formats); err != nil { + res = append(res, err) + } + + if err := m.validateMoveTaskOrderID(formats); err != nil { + res = append(res, err) + } + + if err := m.validateMtoShipmentID(formats); err != nil { + res = append(res, err) + } + + if err := m.validateServiceRequestDocuments(formats); err != nil { + res = append(res, err) + } + + if err := m.validateStatus(formats); err != nil { + res = append(res, err) + } + + if err := m.validateCrate(formats); err != nil { + res = append(res, err) + } + + if err := m.validateDescription(formats); err != nil { + res = append(res, err) + } + + if err := m.validateItem(formats); err != nil { + res = append(res, err) + } + + if err := m.validateMarket(formats); err != nil { + res = append(res, err) + } + + if err := m.validateReServiceCode(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *MTOServiceItemInternationalCrating) validateID(formats strfmt.Registry) error { + + if swag.IsZero(m.ID()) { // not required + return nil + } + + if err := validate.FormatOf("id", "body", "uuid", m.ID().String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalCrating) validateMoveTaskOrderID(formats strfmt.Registry) error { + + if err := validate.Required("moveTaskOrderID", "body", m.MoveTaskOrderID()); err != nil { + return err + } + + if err := validate.FormatOf("moveTaskOrderID", "body", "uuid", m.MoveTaskOrderID().String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalCrating) validateMtoShipmentID(formats strfmt.Registry) error { + + if swag.IsZero(m.MtoShipmentID()) { // not required + return nil + } + + if err := validate.FormatOf("mtoShipmentID", "body", "uuid", m.MtoShipmentID().String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalCrating) validateServiceRequestDocuments(formats strfmt.Registry) error { + + if swag.IsZero(m.ServiceRequestDocuments()) { // not required + return nil + } + + if err := m.ServiceRequestDocuments().Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("serviceRequestDocuments") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("serviceRequestDocuments") + } + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalCrating) validateStatus(formats strfmt.Registry) error { + + if swag.IsZero(m.Status()) { // not required + return nil + } + + if err := m.Status().Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("status") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("status") + } + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalCrating) validateCrate(formats strfmt.Registry) error { + + return nil +} + +func (m *MTOServiceItemInternationalCrating) validateDescription(formats strfmt.Registry) error { + + if err := validate.Required("description", "body", m.Description); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalCrating) validateItem(formats strfmt.Registry) error { + + return nil +} + +var mTOServiceItemInternationalCratingTypeMarketPropEnum []interface{} + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["CONUS","OCONUS"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + mTOServiceItemInternationalCratingTypeMarketPropEnum = append(mTOServiceItemInternationalCratingTypeMarketPropEnum, v) + } +} + +// property enum +func (m *MTOServiceItemInternationalCrating) validateMarketEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, mTOServiceItemInternationalCratingTypeMarketPropEnum, true); err != nil { + return err + } + return nil +} + +func (m *MTOServiceItemInternationalCrating) validateMarket(formats strfmt.Registry) error { + + if swag.IsZero(m.Market) { // not required + return nil + } + + // value enum + if err := m.validateMarketEnum("market", "body", m.Market); err != nil { + return err + } + + return nil +} + +var mTOServiceItemInternationalCratingTypeReServiceCodePropEnum []interface{} + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["ICRT","IUCRT"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + mTOServiceItemInternationalCratingTypeReServiceCodePropEnum = append(mTOServiceItemInternationalCratingTypeReServiceCodePropEnum, v) + } +} + +// property enum +func (m *MTOServiceItemInternationalCrating) validateReServiceCodeEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, mTOServiceItemInternationalCratingTypeReServiceCodePropEnum, true); err != nil { + return err + } + return nil +} + +func (m *MTOServiceItemInternationalCrating) validateReServiceCode(formats strfmt.Registry) error { + + if err := validate.Required("reServiceCode", "body", m.ReServiceCode); err != nil { + return err + } + + // value enum + if err := m.validateReServiceCodeEnum("reServiceCode", "body", *m.ReServiceCode); err != nil { + return err + } + + return nil +} + +// ContextValidate validate this m t o service item international crating based on the context it is used +func (m *MTOServiceItemInternationalCrating) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateETag(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateID(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateReServiceName(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateRejectionReason(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateServiceRequestDocuments(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateStatus(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateCrate(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateItem(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *MTOServiceItemInternationalCrating) contextValidateETag(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "eTag", "body", string(m.ETag())); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalCrating) contextValidateID(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "id", "body", strfmt.UUID(m.ID())); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalCrating) contextValidateModelType(ctx context.Context, formats strfmt.Registry) error { + + if err := m.ModelType().ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("modelType") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("modelType") + } + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalCrating) contextValidateReServiceName(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "reServiceName", "body", string(m.ReServiceName())); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalCrating) contextValidateRejectionReason(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "rejectionReason", "body", m.RejectionReason()); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalCrating) contextValidateServiceRequestDocuments(ctx context.Context, formats strfmt.Registry) error { + + if err := m.ServiceRequestDocuments().ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("serviceRequestDocuments") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("serviceRequestDocuments") + } + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalCrating) contextValidateStatus(ctx context.Context, formats strfmt.Registry) error { + + if swag.IsZero(m.Status()) { // not required + return nil + } + + if err := m.Status().ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("status") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("status") + } + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalCrating) contextValidateCrate(ctx context.Context, formats strfmt.Registry) error { + + return nil +} + +func (m *MTOServiceItemInternationalCrating) contextValidateItem(ctx context.Context, formats strfmt.Registry) error { + + return nil +} + +// MarshalBinary interface implementation +func (m *MTOServiceItemInternationalCrating) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *MTOServiceItemInternationalCrating) UnmarshalBinary(b []byte) error { + var res MTOServiceItemInternationalCrating + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/pkg/gen/primemessages/m_t_o_service_item_model_type.go b/pkg/gen/primemessages/m_t_o_service_item_model_type.go index cb393f2160d..e7feb9758f6 100644 --- a/pkg/gen/primemessages/m_t_o_service_item_model_type.go +++ b/pkg/gen/primemessages/m_t_o_service_item_model_type.go @@ -21,6 +21,7 @@ import ( // - DDFSIT, DDASIT - MTOServiceItemDestSIT // - DOSHUT, DDSHUT - MTOServiceItemShuttle // - DCRT, DUCRT - MTOServiceItemDomesticCrating +// - ICRT, IUCRT - MTOServiceItemInternationalCrating // // The documentation will then update with the supported fields. // @@ -52,6 +53,9 @@ const ( // MTOServiceItemModelTypeMTOServiceItemDomesticCrating captures enum value "MTOServiceItemDomesticCrating" MTOServiceItemModelTypeMTOServiceItemDomesticCrating MTOServiceItemModelType = "MTOServiceItemDomesticCrating" + + // MTOServiceItemModelTypeMTOServiceItemInternationalCrating captures enum value "MTOServiceItemInternationalCrating" + MTOServiceItemModelTypeMTOServiceItemInternationalCrating MTOServiceItemModelType = "MTOServiceItemInternationalCrating" ) // for schema @@ -59,7 +63,7 @@ var mTOServiceItemModelTypeEnum []interface{} func init() { var res []MTOServiceItemModelType - if err := json.Unmarshal([]byte(`["MTOServiceItemBasic","MTOServiceItemOriginSIT","MTOServiceItemDestSIT","MTOServiceItemShuttle","MTOServiceItemDomesticCrating"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["MTOServiceItemBasic","MTOServiceItemOriginSIT","MTOServiceItemDestSIT","MTOServiceItemShuttle","MTOServiceItemDomesticCrating","MTOServiceItemInternationalCrating"]`), &res); err != nil { panic(err) } for _, v := range res { diff --git a/pkg/gen/primemessages/p_p_m_shipment.go b/pkg/gen/primemessages/p_p_m_shipment.go index 60846c17243..245ad0bbdf8 100644 --- a/pkg/gen/primemessages/p_p_m_shipment.go +++ b/pkg/gen/primemessages/p_p_m_shipment.go @@ -98,6 +98,9 @@ type PPMShipment struct { // Example: false IsActualExpenseReimbursement *bool `json:"isActualExpenseReimbursement"` + // The max amount the government will pay the service member to move their belongings based on the moving date, locations, and shipment weight. + MaxIncentive *int64 `json:"maxIncentive"` + // The estimated weight of the pro-gear being moved belonging to the service member in pounds. ProGearWeight *int64 `json:"proGearWeight"` diff --git a/pkg/gen/primemessages/re_service_code.go b/pkg/gen/primemessages/re_service_code.go index 708ae688e7e..33db5909d41 100644 --- a/pkg/gen/primemessages/re_service_code.go +++ b/pkg/gen/primemessages/re_service_code.go @@ -114,9 +114,6 @@ const ( // ReServiceCodeICRT captures enum value "ICRT" ReServiceCodeICRT ReServiceCode = "ICRT" - // ReServiceCodeICRTSA captures enum value "ICRTSA" - ReServiceCodeICRTSA ReServiceCode = "ICRTSA" - // ReServiceCodeIDASIT captures enum value "IDASIT" ReServiceCodeIDASIT ReServiceCode = "IDASIT" @@ -186,7 +183,7 @@ var reServiceCodeEnum []interface{} func init() { var res []ReServiceCode - if err := json.Unmarshal([]byte(`["CS","DBHF","DBTF","DCRT","DDASIT","DDDSIT","DDFSIT","DDP","DDSHUT","DLH","DMHF","DNPK","DOASIT","DOFSIT","DOP","DOPSIT","DOSHUT","DPK","DSH","DUCRT","DUPK","FSC","IBHF","IBTF","ICOLH","ICOUB","ICRT","ICRTSA","IDASIT","IDDSIT","IDFSIT","IDSHUT","IHPK","IHUPK","INPK","IOASIT","IOCLH","IOCUB","IOFSIT","IOOLH","IOOUB","IOPSIT","IOSHUT","IUBPK","IUBUPK","IUCRT","MS","NSTH","NSTUB"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["CS","DBHF","DBTF","DCRT","DDASIT","DDDSIT","DDFSIT","DDP","DDSHUT","DLH","DMHF","DNPK","DOASIT","DOFSIT","DOP","DOPSIT","DOSHUT","DPK","DSH","DUCRT","DUPK","FSC","IBHF","IBTF","ICOLH","ICOUB","ICRT","IDASIT","IDDSIT","IDFSIT","IDSHUT","IHPK","IHUPK","INPK","IOASIT","IOCLH","IOCUB","IOFSIT","IOOLH","IOOUB","IOPSIT","IOSHUT","IUBPK","IUBUPK","IUCRT","MS","NSTH","NSTUB"]`), &res); err != nil { panic(err) } for _, v := range res { diff --git a/pkg/gen/primemessages/shipment_address_update.go b/pkg/gen/primemessages/shipment_address_update.go index 647192fe679..0e5b4258565 100644 --- a/pkg/gen/primemessages/shipment_address_update.go +++ b/pkg/gen/primemessages/shipment_address_update.go @@ -14,7 +14,7 @@ import ( "github.com/go-openapi/validate" ) -// ShipmentAddressUpdate This represents a destination address change request made by the Prime that is either auto-approved or requires review if the pricing criteria has changed. If criteria has changed, then it must be approved or rejected by a TOO. +// ShipmentAddressUpdate This represents a delivery address change request made by the Prime that is either auto-approved or requires review if the pricing criteria has changed. If criteria has changed, then it must be approved or rejected by a TOO. // // swagger:model ShipmentAddressUpdate type ShipmentAddressUpdate struct { @@ -38,7 +38,7 @@ type ShipmentAddressUpdate struct { // Required: true NewAddress *Address `json:"newAddress"` - // The distance between the original SIT address and requested new destination address of shipment + // The distance between the original SIT address and requested new delivery address of shipment // Example: 88 // Minimum: 0 NewSitDistanceBetween *int64 `json:"newSitDistanceBetween,omitempty"` @@ -49,7 +49,7 @@ type ShipmentAddressUpdate struct { // Example: This is an office remark OfficeRemarks *string `json:"officeRemarks,omitempty"` - // The distance between the original SIT address and the previous/old destination address of shipment + // The distance between the original SIT address and the previous/old delivery address of shipment // Example: 50 // Minimum: 0 OldSitDistanceBetween *int64 `json:"oldSitDistanceBetween,omitempty"` diff --git a/pkg/gen/primemessages/update_shipment_destination_address.go b/pkg/gen/primemessages/update_shipment_destination_address.go index c6e82f5275e..6089fccc289 100644 --- a/pkg/gen/primemessages/update_shipment_destination_address.go +++ b/pkg/gen/primemessages/update_shipment_destination_address.go @@ -14,7 +14,7 @@ import ( "github.com/go-openapi/validate" ) -// UpdateShipmentDestinationAddress UpdateShipmentDestinationAddress contains the fields required for the prime to request an update for the destination address on an MTO Shipment. +// UpdateShipmentDestinationAddress UpdateShipmentDestinationAddress contains the fields required for the prime to request an update for the delivery address on an MTO Shipment. // // swagger:model UpdateShipmentDestinationAddress type UpdateShipmentDestinationAddress struct { diff --git a/pkg/gen/primev2api/embedded_spec.go b/pkg/gen/primev2api/embedded_spec.go index 56718d64852..a9141e068c8 100644 --- a/pkg/gen/primev2api/embedded_spec.go +++ b/pkg/gen/primev2api/embedded_spec.go @@ -1391,15 +1391,88 @@ func init() { } ] }, + "MTOServiceItemInternationalCrating": { + "description": "Describes a international crating/uncrating service item subtype of a MTOServiceItem.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItem" + }, + { + "type": "object", + "required": [ + "reServiceCode", + "item", + "crate", + "description" + ], + "properties": { + "crate": { + "description": "The dimensions for the crate the item will be shipped in.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItemDimension" + } + ] + }, + "description": { + "description": "A description of the item being crated.", + "type": "string", + "example": "Decorated horse head to be crated." + }, + "externalCrate": { + "type": "boolean", + "x-nullable": true + }, + "item": { + "description": "The dimensions of the item being crated.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItemDimension" + } + ] + }, + "market": { + "description": "To identify whether the service was provided within (CONUS) or (OCONUS)", + "type": "string", + "enum": [ + "CONUS", + "OCONUS" + ], + "example": "CONUS" + }, + "reServiceCode": { + "description": "A unique code for the service item. Indicates if the service is for crating (ICRT) or uncrating (IUCRT).", + "type": "string", + "enum": [ + "ICRT", + "IUCRT" + ] + }, + "reason": { + "description": "The contractor's explanation for why an item needed to be crated or uncrated. Used by the TOO while deciding to approve or reject the service item.\n", + "type": "string", + "x-nullable": true, + "x-omitempty": false, + "example": "Storage items need to be picked up" + }, + "standaloneCrate": { + "type": "boolean", + "x-nullable": true + } + } + } + ] + }, "MTOServiceItemModelType": { - "description": "Describes all model sub-types for a MTOServiceItem model.\n\nUsing this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DOFSIT, DOASIT - MTOServiceItemOriginSIT\n * DDFSIT, DDASIT - MTOServiceItemDestSIT\n * DOSHUT, DDSHUT - MTOServiceItemShuttle\n * DCRT, DUCRT - MTOServiceItemDomesticCrating\n\nThe documentation will then update with the supported fields.\n", + "description": "Describes all model sub-types for a MTOServiceItem model.\n\nUsing this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DOFSIT, DOASIT - MTOServiceItemOriginSIT\n * DDFSIT, DDASIT - MTOServiceItemDestSIT\n * DOSHUT, DDSHUT - MTOServiceItemShuttle\n * DCRT, DUCRT - MTOServiceItemDomesticCrating\n * ICRT, IUCRT - MTOServiceItemInternationalCrating\n\nThe documentation will then update with the supported fields.\n", "type": "string", "enum": [ "MTOServiceItemBasic", "MTOServiceItemOriginSIT", "MTOServiceItemDestSIT", "MTOServiceItemShuttle", - "MTOServiceItemDomesticCrating" + "MTOServiceItemDomesticCrating", + "MTOServiceItemInternationalCrating" ] }, "MTOServiceItemOriginSIT": { @@ -2177,6 +2250,13 @@ func init() { "x-omitempty": false, "example": false }, + "maxIncentive": { + "description": "The max amount the government will pay the service member to move their belongings based on the moving date, locations, and shipment weight.", + "type": "integer", + "format": "cents", + "x-nullable": true, + "x-omitempty": false + }, "proGearWeight": { "description": "The estimated weight of the pro-gear being moved belonging to the service member in pounds.", "type": "integer", @@ -2503,7 +2583,6 @@ func init() { "ICOLH", "ICOUB", "ICRT", - "ICRTSA", "IDASIT", "IDDSIT", "IDFSIT", @@ -2795,7 +2874,7 @@ func init() { } }, "ShipmentAddressUpdate": { - "description": "This represents a destination address change request made by the Prime that is either auto-approved or requires review if the pricing criteria has changed. If criteria has changed, then it must be approved or rejected by a TOO.\n", + "description": "This represents a delivery address change request made by the Prime that is either auto-approved or requires review if the pricing criteria has changed. If criteria has changed, then it must be approved or rejected by a TOO.\n", "type": "object", "required": [ "id", @@ -2823,7 +2902,7 @@ func init() { "$ref": "#/definitions/Address" }, "newSitDistanceBetween": { - "description": "The distance between the original SIT address and requested new destination address of shipment", + "description": "The distance between the original SIT address and requested new delivery address of shipment", "type": "integer", "example": 88 }, @@ -2835,7 +2914,7 @@ func init() { "example": "This is an office remark" }, "oldSitDistanceBetween": { - "description": "The distance between the original SIT address and the previous/old destination address of shipment", + "description": "The distance between the original SIT address and the previous/old delivery address of shipment", "type": "integer", "example": 50 }, @@ -3316,7 +3395,7 @@ func init() { } }, "UpdateShipmentDestinationAddress": { - "description": "UpdateShipmentDestinationAddress contains the fields required for the prime to request an update for the destination address on an MTO Shipment.", + "description": "UpdateShipmentDestinationAddress contains the fields required for the prime to request an update for the delivery address on an MTO Shipment.", "type": "object", "required": [ "contractorRemarks", @@ -4903,15 +4982,88 @@ func init() { } ] }, + "MTOServiceItemInternationalCrating": { + "description": "Describes a international crating/uncrating service item subtype of a MTOServiceItem.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItem" + }, + { + "type": "object", + "required": [ + "reServiceCode", + "item", + "crate", + "description" + ], + "properties": { + "crate": { + "description": "The dimensions for the crate the item will be shipped in.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItemDimension" + } + ] + }, + "description": { + "description": "A description of the item being crated.", + "type": "string", + "example": "Decorated horse head to be crated." + }, + "externalCrate": { + "type": "boolean", + "x-nullable": true + }, + "item": { + "description": "The dimensions of the item being crated.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItemDimension" + } + ] + }, + "market": { + "description": "To identify whether the service was provided within (CONUS) or (OCONUS)", + "type": "string", + "enum": [ + "CONUS", + "OCONUS" + ], + "example": "CONUS" + }, + "reServiceCode": { + "description": "A unique code for the service item. Indicates if the service is for crating (ICRT) or uncrating (IUCRT).", + "type": "string", + "enum": [ + "ICRT", + "IUCRT" + ] + }, + "reason": { + "description": "The contractor's explanation for why an item needed to be crated or uncrated. Used by the TOO while deciding to approve or reject the service item.\n", + "type": "string", + "x-nullable": true, + "x-omitempty": false, + "example": "Storage items need to be picked up" + }, + "standaloneCrate": { + "type": "boolean", + "x-nullable": true + } + } + } + ] + }, "MTOServiceItemModelType": { - "description": "Describes all model sub-types for a MTOServiceItem model.\n\nUsing this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DOFSIT, DOASIT - MTOServiceItemOriginSIT\n * DDFSIT, DDASIT - MTOServiceItemDestSIT\n * DOSHUT, DDSHUT - MTOServiceItemShuttle\n * DCRT, DUCRT - MTOServiceItemDomesticCrating\n\nThe documentation will then update with the supported fields.\n", + "description": "Describes all model sub-types for a MTOServiceItem model.\n\nUsing this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DOFSIT, DOASIT - MTOServiceItemOriginSIT\n * DDFSIT, DDASIT - MTOServiceItemDestSIT\n * DOSHUT, DDSHUT - MTOServiceItemShuttle\n * DCRT, DUCRT - MTOServiceItemDomesticCrating\n * ICRT, IUCRT - MTOServiceItemInternationalCrating\n\nThe documentation will then update with the supported fields.\n", "type": "string", "enum": [ "MTOServiceItemBasic", "MTOServiceItemOriginSIT", "MTOServiceItemDestSIT", "MTOServiceItemShuttle", - "MTOServiceItemDomesticCrating" + "MTOServiceItemDomesticCrating", + "MTOServiceItemInternationalCrating" ] }, "MTOServiceItemOriginSIT": { @@ -5689,6 +5841,13 @@ func init() { "x-omitempty": false, "example": false }, + "maxIncentive": { + "description": "The max amount the government will pay the service member to move their belongings based on the moving date, locations, and shipment weight.", + "type": "integer", + "format": "cents", + "x-nullable": true, + "x-omitempty": false + }, "proGearWeight": { "description": "The estimated weight of the pro-gear being moved belonging to the service member in pounds.", "type": "integer", @@ -6015,7 +6174,6 @@ func init() { "ICOLH", "ICOUB", "ICRT", - "ICRTSA", "IDASIT", "IDDSIT", "IDFSIT", @@ -6307,7 +6465,7 @@ func init() { } }, "ShipmentAddressUpdate": { - "description": "This represents a destination address change request made by the Prime that is either auto-approved or requires review if the pricing criteria has changed. If criteria has changed, then it must be approved or rejected by a TOO.\n", + "description": "This represents a delivery address change request made by the Prime that is either auto-approved or requires review if the pricing criteria has changed. If criteria has changed, then it must be approved or rejected by a TOO.\n", "type": "object", "required": [ "id", @@ -6335,7 +6493,7 @@ func init() { "$ref": "#/definitions/Address" }, "newSitDistanceBetween": { - "description": "The distance between the original SIT address and requested new destination address of shipment", + "description": "The distance between the original SIT address and requested new delivery address of shipment", "type": "integer", "minimum": 0, "example": 88 @@ -6348,7 +6506,7 @@ func init() { "example": "This is an office remark" }, "oldSitDistanceBetween": { - "description": "The distance between the original SIT address and the previous/old destination address of shipment", + "description": "The distance between the original SIT address and the previous/old delivery address of shipment", "type": "integer", "minimum": 0, "example": 50 @@ -6830,7 +6988,7 @@ func init() { } }, "UpdateShipmentDestinationAddress": { - "description": "UpdateShipmentDestinationAddress contains the fields required for the prime to request an update for the destination address on an MTO Shipment.", + "description": "UpdateShipmentDestinationAddress contains the fields required for the prime to request an update for the delivery address on an MTO Shipment.", "type": "object", "required": [ "contractorRemarks", diff --git a/pkg/gen/primev2messages/m_t_o_service_item.go b/pkg/gen/primev2messages/m_t_o_service_item.go index e2bbae39284..58ae8130bb3 100644 --- a/pkg/gen/primev2messages/m_t_o_service_item.go +++ b/pkg/gen/primev2messages/m_t_o_service_item.go @@ -273,6 +273,12 @@ func unmarshalMTOServiceItem(data []byte, consumer runtime.Consumer) (MTOService return nil, err } return &result, nil + case "MTOServiceItemInternationalCrating": + var result MTOServiceItemInternationalCrating + if err := consumer.Consume(buf2, &result); err != nil { + return nil, err + } + return &result, nil case "MTOServiceItemOriginSIT": var result MTOServiceItemOriginSIT if err := consumer.Consume(buf2, &result); err != nil { diff --git a/pkg/gen/primev2messages/m_t_o_service_item_international_crating.go b/pkg/gen/primev2messages/m_t_o_service_item_international_crating.go new file mode 100644 index 00000000000..f644a7a869f --- /dev/null +++ b/pkg/gen/primev2messages/m_t_o_service_item_international_crating.go @@ -0,0 +1,773 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package primev2messages + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "bytes" + "context" + "encoding/json" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// MTOServiceItemInternationalCrating Describes a international crating/uncrating service item subtype of a MTOServiceItem. +// +// swagger:model MTOServiceItemInternationalCrating +type MTOServiceItemInternationalCrating struct { + eTagField string + + idField strfmt.UUID + + lockedPriceCentsField *int64 + + moveTaskOrderIdField *strfmt.UUID + + mtoShipmentIdField strfmt.UUID + + reServiceNameField string + + rejectionReasonField *string + + serviceRequestDocumentsField ServiceRequestDocuments + + statusField MTOServiceItemStatus + + // The dimensions for the crate the item will be shipped in. + // Required: true + Crate struct { + MTOServiceItemDimension + } `json:"crate"` + + // A description of the item being crated. + // Example: Decorated horse head to be crated. + // Required: true + Description *string `json:"description"` + + // external crate + ExternalCrate *bool `json:"externalCrate,omitempty"` + + // The dimensions of the item being crated. + // Required: true + Item struct { + MTOServiceItemDimension + } `json:"item"` + + // To identify whether the service was provided within (CONUS) or (OCONUS) + // Example: CONUS + // Enum: [CONUS OCONUS] + Market string `json:"market,omitempty"` + + // A unique code for the service item. Indicates if the service is for crating (ICRT) or uncrating (IUCRT). + // Required: true + // Enum: [ICRT IUCRT] + ReServiceCode *string `json:"reServiceCode"` + + // The contractor's explanation for why an item needed to be crated or uncrated. Used by the TOO while deciding to approve or reject the service item. + // + // Example: Storage items need to be picked up + Reason *string `json:"reason"` + + // standalone crate + StandaloneCrate *bool `json:"standaloneCrate,omitempty"` +} + +// ETag gets the e tag of this subtype +func (m *MTOServiceItemInternationalCrating) ETag() string { + return m.eTagField +} + +// SetETag sets the e tag of this subtype +func (m *MTOServiceItemInternationalCrating) SetETag(val string) { + m.eTagField = val +} + +// ID gets the id of this subtype +func (m *MTOServiceItemInternationalCrating) ID() strfmt.UUID { + return m.idField +} + +// SetID sets the id of this subtype +func (m *MTOServiceItemInternationalCrating) SetID(val strfmt.UUID) { + m.idField = val +} + +// LockedPriceCents gets the locked price cents of this subtype +func (m *MTOServiceItemInternationalCrating) LockedPriceCents() *int64 { + return m.lockedPriceCentsField +} + +// SetLockedPriceCents sets the locked price cents of this subtype +func (m *MTOServiceItemInternationalCrating) SetLockedPriceCents(val *int64) { + m.lockedPriceCentsField = val +} + +// ModelType gets the model type of this subtype +func (m *MTOServiceItemInternationalCrating) ModelType() MTOServiceItemModelType { + return "MTOServiceItemInternationalCrating" +} + +// SetModelType sets the model type of this subtype +func (m *MTOServiceItemInternationalCrating) SetModelType(val MTOServiceItemModelType) { +} + +// MoveTaskOrderID gets the move task order ID of this subtype +func (m *MTOServiceItemInternationalCrating) MoveTaskOrderID() *strfmt.UUID { + return m.moveTaskOrderIdField +} + +// SetMoveTaskOrderID sets the move task order ID of this subtype +func (m *MTOServiceItemInternationalCrating) SetMoveTaskOrderID(val *strfmt.UUID) { + m.moveTaskOrderIdField = val +} + +// MtoShipmentID gets the mto shipment ID of this subtype +func (m *MTOServiceItemInternationalCrating) MtoShipmentID() strfmt.UUID { + return m.mtoShipmentIdField +} + +// SetMtoShipmentID sets the mto shipment ID of this subtype +func (m *MTOServiceItemInternationalCrating) SetMtoShipmentID(val strfmt.UUID) { + m.mtoShipmentIdField = val +} + +// ReServiceName gets the re service name of this subtype +func (m *MTOServiceItemInternationalCrating) ReServiceName() string { + return m.reServiceNameField +} + +// SetReServiceName sets the re service name of this subtype +func (m *MTOServiceItemInternationalCrating) SetReServiceName(val string) { + m.reServiceNameField = val +} + +// RejectionReason gets the rejection reason of this subtype +func (m *MTOServiceItemInternationalCrating) RejectionReason() *string { + return m.rejectionReasonField +} + +// SetRejectionReason sets the rejection reason of this subtype +func (m *MTOServiceItemInternationalCrating) SetRejectionReason(val *string) { + m.rejectionReasonField = val +} + +// ServiceRequestDocuments gets the service request documents of this subtype +func (m *MTOServiceItemInternationalCrating) ServiceRequestDocuments() ServiceRequestDocuments { + return m.serviceRequestDocumentsField +} + +// SetServiceRequestDocuments sets the service request documents of this subtype +func (m *MTOServiceItemInternationalCrating) SetServiceRequestDocuments(val ServiceRequestDocuments) { + m.serviceRequestDocumentsField = val +} + +// Status gets the status of this subtype +func (m *MTOServiceItemInternationalCrating) Status() MTOServiceItemStatus { + return m.statusField +} + +// SetStatus sets the status of this subtype +func (m *MTOServiceItemInternationalCrating) SetStatus(val MTOServiceItemStatus) { + m.statusField = val +} + +// UnmarshalJSON unmarshals this object with a polymorphic type from a JSON structure +func (m *MTOServiceItemInternationalCrating) UnmarshalJSON(raw []byte) error { + var data struct { + + // The dimensions for the crate the item will be shipped in. + // Required: true + Crate struct { + MTOServiceItemDimension + } `json:"crate"` + + // A description of the item being crated. + // Example: Decorated horse head to be crated. + // Required: true + Description *string `json:"description"` + + // external crate + ExternalCrate *bool `json:"externalCrate,omitempty"` + + // The dimensions of the item being crated. + // Required: true + Item struct { + MTOServiceItemDimension + } `json:"item"` + + // To identify whether the service was provided within (CONUS) or (OCONUS) + // Example: CONUS + // Enum: [CONUS OCONUS] + Market string `json:"market,omitempty"` + + // A unique code for the service item. Indicates if the service is for crating (ICRT) or uncrating (IUCRT). + // Required: true + // Enum: [ICRT IUCRT] + ReServiceCode *string `json:"reServiceCode"` + + // The contractor's explanation for why an item needed to be crated or uncrated. Used by the TOO while deciding to approve or reject the service item. + // + // Example: Storage items need to be picked up + Reason *string `json:"reason"` + + // standalone crate + StandaloneCrate *bool `json:"standaloneCrate,omitempty"` + } + buf := bytes.NewBuffer(raw) + dec := json.NewDecoder(buf) + dec.UseNumber() + + if err := dec.Decode(&data); err != nil { + return err + } + + var base struct { + /* Just the base type fields. Used for unmashalling polymorphic types.*/ + + ETag string `json:"eTag,omitempty"` + + ID strfmt.UUID `json:"id,omitempty"` + + LockedPriceCents *int64 `json:"lockedPriceCents,omitempty"` + + ModelType MTOServiceItemModelType `json:"modelType"` + + MoveTaskOrderID *strfmt.UUID `json:"moveTaskOrderID"` + + MtoShipmentID strfmt.UUID `json:"mtoShipmentID,omitempty"` + + ReServiceName string `json:"reServiceName,omitempty"` + + RejectionReason *string `json:"rejectionReason,omitempty"` + + ServiceRequestDocuments ServiceRequestDocuments `json:"serviceRequestDocuments,omitempty"` + + Status MTOServiceItemStatus `json:"status,omitempty"` + } + buf = bytes.NewBuffer(raw) + dec = json.NewDecoder(buf) + dec.UseNumber() + + if err := dec.Decode(&base); err != nil { + return err + } + + var result MTOServiceItemInternationalCrating + + result.eTagField = base.ETag + + result.idField = base.ID + + result.lockedPriceCentsField = base.LockedPriceCents + + if base.ModelType != result.ModelType() { + /* Not the type we're looking for. */ + return errors.New(422, "invalid modelType value: %q", base.ModelType) + } + result.moveTaskOrderIdField = base.MoveTaskOrderID + + result.mtoShipmentIdField = base.MtoShipmentID + + result.reServiceNameField = base.ReServiceName + + result.rejectionReasonField = base.RejectionReason + + result.serviceRequestDocumentsField = base.ServiceRequestDocuments + + result.statusField = base.Status + + result.Crate = data.Crate + result.Description = data.Description + result.ExternalCrate = data.ExternalCrate + result.Item = data.Item + result.Market = data.Market + result.ReServiceCode = data.ReServiceCode + result.Reason = data.Reason + result.StandaloneCrate = data.StandaloneCrate + + *m = result + + return nil +} + +// MarshalJSON marshals this object with a polymorphic type to a JSON structure +func (m MTOServiceItemInternationalCrating) MarshalJSON() ([]byte, error) { + var b1, b2, b3 []byte + var err error + b1, err = json.Marshal(struct { + + // The dimensions for the crate the item will be shipped in. + // Required: true + Crate struct { + MTOServiceItemDimension + } `json:"crate"` + + // A description of the item being crated. + // Example: Decorated horse head to be crated. + // Required: true + Description *string `json:"description"` + + // external crate + ExternalCrate *bool `json:"externalCrate,omitempty"` + + // The dimensions of the item being crated. + // Required: true + Item struct { + MTOServiceItemDimension + } `json:"item"` + + // To identify whether the service was provided within (CONUS) or (OCONUS) + // Example: CONUS + // Enum: [CONUS OCONUS] + Market string `json:"market,omitempty"` + + // A unique code for the service item. Indicates if the service is for crating (ICRT) or uncrating (IUCRT). + // Required: true + // Enum: [ICRT IUCRT] + ReServiceCode *string `json:"reServiceCode"` + + // The contractor's explanation for why an item needed to be crated or uncrated. Used by the TOO while deciding to approve or reject the service item. + // + // Example: Storage items need to be picked up + Reason *string `json:"reason"` + + // standalone crate + StandaloneCrate *bool `json:"standaloneCrate,omitempty"` + }{ + + Crate: m.Crate, + + Description: m.Description, + + ExternalCrate: m.ExternalCrate, + + Item: m.Item, + + Market: m.Market, + + ReServiceCode: m.ReServiceCode, + + Reason: m.Reason, + + StandaloneCrate: m.StandaloneCrate, + }) + if err != nil { + return nil, err + } + b2, err = json.Marshal(struct { + ETag string `json:"eTag,omitempty"` + + ID strfmt.UUID `json:"id,omitempty"` + + LockedPriceCents *int64 `json:"lockedPriceCents,omitempty"` + + ModelType MTOServiceItemModelType `json:"modelType"` + + MoveTaskOrderID *strfmt.UUID `json:"moveTaskOrderID"` + + MtoShipmentID strfmt.UUID `json:"mtoShipmentID,omitempty"` + + ReServiceName string `json:"reServiceName,omitempty"` + + RejectionReason *string `json:"rejectionReason,omitempty"` + + ServiceRequestDocuments ServiceRequestDocuments `json:"serviceRequestDocuments,omitempty"` + + Status MTOServiceItemStatus `json:"status,omitempty"` + }{ + + ETag: m.ETag(), + + ID: m.ID(), + + LockedPriceCents: m.LockedPriceCents(), + + ModelType: m.ModelType(), + + MoveTaskOrderID: m.MoveTaskOrderID(), + + MtoShipmentID: m.MtoShipmentID(), + + ReServiceName: m.ReServiceName(), + + RejectionReason: m.RejectionReason(), + + ServiceRequestDocuments: m.ServiceRequestDocuments(), + + Status: m.Status(), + }) + if err != nil { + return nil, err + } + + return swag.ConcatJSON(b1, b2, b3), nil +} + +// Validate validates this m t o service item international crating +func (m *MTOServiceItemInternationalCrating) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateID(formats); err != nil { + res = append(res, err) + } + + if err := m.validateMoveTaskOrderID(formats); err != nil { + res = append(res, err) + } + + if err := m.validateMtoShipmentID(formats); err != nil { + res = append(res, err) + } + + if err := m.validateServiceRequestDocuments(formats); err != nil { + res = append(res, err) + } + + if err := m.validateStatus(formats); err != nil { + res = append(res, err) + } + + if err := m.validateCrate(formats); err != nil { + res = append(res, err) + } + + if err := m.validateDescription(formats); err != nil { + res = append(res, err) + } + + if err := m.validateItem(formats); err != nil { + res = append(res, err) + } + + if err := m.validateMarket(formats); err != nil { + res = append(res, err) + } + + if err := m.validateReServiceCode(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *MTOServiceItemInternationalCrating) validateID(formats strfmt.Registry) error { + + if swag.IsZero(m.ID()) { // not required + return nil + } + + if err := validate.FormatOf("id", "body", "uuid", m.ID().String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalCrating) validateMoveTaskOrderID(formats strfmt.Registry) error { + + if err := validate.Required("moveTaskOrderID", "body", m.MoveTaskOrderID()); err != nil { + return err + } + + if err := validate.FormatOf("moveTaskOrderID", "body", "uuid", m.MoveTaskOrderID().String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalCrating) validateMtoShipmentID(formats strfmt.Registry) error { + + if swag.IsZero(m.MtoShipmentID()) { // not required + return nil + } + + if err := validate.FormatOf("mtoShipmentID", "body", "uuid", m.MtoShipmentID().String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalCrating) validateServiceRequestDocuments(formats strfmt.Registry) error { + + if swag.IsZero(m.ServiceRequestDocuments()) { // not required + return nil + } + + if err := m.ServiceRequestDocuments().Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("serviceRequestDocuments") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("serviceRequestDocuments") + } + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalCrating) validateStatus(formats strfmt.Registry) error { + + if swag.IsZero(m.Status()) { // not required + return nil + } + + if err := m.Status().Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("status") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("status") + } + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalCrating) validateCrate(formats strfmt.Registry) error { + + return nil +} + +func (m *MTOServiceItemInternationalCrating) validateDescription(formats strfmt.Registry) error { + + if err := validate.Required("description", "body", m.Description); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalCrating) validateItem(formats strfmt.Registry) error { + + return nil +} + +var mTOServiceItemInternationalCratingTypeMarketPropEnum []interface{} + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["CONUS","OCONUS"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + mTOServiceItemInternationalCratingTypeMarketPropEnum = append(mTOServiceItemInternationalCratingTypeMarketPropEnum, v) + } +} + +// property enum +func (m *MTOServiceItemInternationalCrating) validateMarketEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, mTOServiceItemInternationalCratingTypeMarketPropEnum, true); err != nil { + return err + } + return nil +} + +func (m *MTOServiceItemInternationalCrating) validateMarket(formats strfmt.Registry) error { + + if swag.IsZero(m.Market) { // not required + return nil + } + + // value enum + if err := m.validateMarketEnum("market", "body", m.Market); err != nil { + return err + } + + return nil +} + +var mTOServiceItemInternationalCratingTypeReServiceCodePropEnum []interface{} + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["ICRT","IUCRT"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + mTOServiceItemInternationalCratingTypeReServiceCodePropEnum = append(mTOServiceItemInternationalCratingTypeReServiceCodePropEnum, v) + } +} + +// property enum +func (m *MTOServiceItemInternationalCrating) validateReServiceCodeEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, mTOServiceItemInternationalCratingTypeReServiceCodePropEnum, true); err != nil { + return err + } + return nil +} + +func (m *MTOServiceItemInternationalCrating) validateReServiceCode(formats strfmt.Registry) error { + + if err := validate.Required("reServiceCode", "body", m.ReServiceCode); err != nil { + return err + } + + // value enum + if err := m.validateReServiceCodeEnum("reServiceCode", "body", *m.ReServiceCode); err != nil { + return err + } + + return nil +} + +// ContextValidate validate this m t o service item international crating based on the context it is used +func (m *MTOServiceItemInternationalCrating) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateETag(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateID(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateReServiceName(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateRejectionReason(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateServiceRequestDocuments(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateStatus(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateCrate(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateItem(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *MTOServiceItemInternationalCrating) contextValidateETag(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "eTag", "body", string(m.ETag())); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalCrating) contextValidateID(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "id", "body", strfmt.UUID(m.ID())); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalCrating) contextValidateModelType(ctx context.Context, formats strfmt.Registry) error { + + if err := m.ModelType().ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("modelType") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("modelType") + } + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalCrating) contextValidateReServiceName(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "reServiceName", "body", string(m.ReServiceName())); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalCrating) contextValidateRejectionReason(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "rejectionReason", "body", m.RejectionReason()); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalCrating) contextValidateServiceRequestDocuments(ctx context.Context, formats strfmt.Registry) error { + + if err := m.ServiceRequestDocuments().ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("serviceRequestDocuments") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("serviceRequestDocuments") + } + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalCrating) contextValidateStatus(ctx context.Context, formats strfmt.Registry) error { + + if swag.IsZero(m.Status()) { // not required + return nil + } + + if err := m.Status().ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("status") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("status") + } + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalCrating) contextValidateCrate(ctx context.Context, formats strfmt.Registry) error { + + return nil +} + +func (m *MTOServiceItemInternationalCrating) contextValidateItem(ctx context.Context, formats strfmt.Registry) error { + + return nil +} + +// MarshalBinary interface implementation +func (m *MTOServiceItemInternationalCrating) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *MTOServiceItemInternationalCrating) UnmarshalBinary(b []byte) error { + var res MTOServiceItemInternationalCrating + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/pkg/gen/primev2messages/m_t_o_service_item_model_type.go b/pkg/gen/primev2messages/m_t_o_service_item_model_type.go index 47af5c6d25d..312a7cc7616 100644 --- a/pkg/gen/primev2messages/m_t_o_service_item_model_type.go +++ b/pkg/gen/primev2messages/m_t_o_service_item_model_type.go @@ -21,6 +21,7 @@ import ( // - DDFSIT, DDASIT - MTOServiceItemDestSIT // - DOSHUT, DDSHUT - MTOServiceItemShuttle // - DCRT, DUCRT - MTOServiceItemDomesticCrating +// - ICRT, IUCRT - MTOServiceItemInternationalCrating // // The documentation will then update with the supported fields. // @@ -52,6 +53,9 @@ const ( // MTOServiceItemModelTypeMTOServiceItemDomesticCrating captures enum value "MTOServiceItemDomesticCrating" MTOServiceItemModelTypeMTOServiceItemDomesticCrating MTOServiceItemModelType = "MTOServiceItemDomesticCrating" + + // MTOServiceItemModelTypeMTOServiceItemInternationalCrating captures enum value "MTOServiceItemInternationalCrating" + MTOServiceItemModelTypeMTOServiceItemInternationalCrating MTOServiceItemModelType = "MTOServiceItemInternationalCrating" ) // for schema @@ -59,7 +63,7 @@ var mTOServiceItemModelTypeEnum []interface{} func init() { var res []MTOServiceItemModelType - if err := json.Unmarshal([]byte(`["MTOServiceItemBasic","MTOServiceItemOriginSIT","MTOServiceItemDestSIT","MTOServiceItemShuttle","MTOServiceItemDomesticCrating"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["MTOServiceItemBasic","MTOServiceItemOriginSIT","MTOServiceItemDestSIT","MTOServiceItemShuttle","MTOServiceItemDomesticCrating","MTOServiceItemInternationalCrating"]`), &res); err != nil { panic(err) } for _, v := range res { diff --git a/pkg/gen/primev2messages/p_p_m_shipment.go b/pkg/gen/primev2messages/p_p_m_shipment.go index 5363936544f..7cb11f73406 100644 --- a/pkg/gen/primev2messages/p_p_m_shipment.go +++ b/pkg/gen/primev2messages/p_p_m_shipment.go @@ -98,6 +98,9 @@ type PPMShipment struct { // Example: false IsActualExpenseReimbursement *bool `json:"isActualExpenseReimbursement"` + // The max amount the government will pay the service member to move their belongings based on the moving date, locations, and shipment weight. + MaxIncentive *int64 `json:"maxIncentive"` + // The estimated weight of the pro-gear being moved belonging to the service member in pounds. ProGearWeight *int64 `json:"proGearWeight"` diff --git a/pkg/gen/primev2messages/re_service_code.go b/pkg/gen/primev2messages/re_service_code.go index 4d0613d6b94..e173f0bc308 100644 --- a/pkg/gen/primev2messages/re_service_code.go +++ b/pkg/gen/primev2messages/re_service_code.go @@ -114,9 +114,6 @@ const ( // ReServiceCodeICRT captures enum value "ICRT" ReServiceCodeICRT ReServiceCode = "ICRT" - // ReServiceCodeICRTSA captures enum value "ICRTSA" - ReServiceCodeICRTSA ReServiceCode = "ICRTSA" - // ReServiceCodeIDASIT captures enum value "IDASIT" ReServiceCodeIDASIT ReServiceCode = "IDASIT" @@ -186,7 +183,7 @@ var reServiceCodeEnum []interface{} func init() { var res []ReServiceCode - if err := json.Unmarshal([]byte(`["CS","DBHF","DBTF","DCRT","DDASIT","DDDSIT","DDFSIT","DDP","DDSHUT","DLH","DMHF","DNPK","DOASIT","DOFSIT","DOP","DOPSIT","DOSHUT","DPK","DSH","DUCRT","DUPK","FSC","IBHF","IBTF","ICOLH","ICOUB","ICRT","ICRTSA","IDASIT","IDDSIT","IDFSIT","IDSHUT","IHPK","IHUPK","INPK","IOASIT","IOCLH","IOCUB","IOFSIT","IOOLH","IOOUB","IOPSIT","IOSHUT","IUBPK","IUBUPK","IUCRT","MS","NSTH","NSTUB"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["CS","DBHF","DBTF","DCRT","DDASIT","DDDSIT","DDFSIT","DDP","DDSHUT","DLH","DMHF","DNPK","DOASIT","DOFSIT","DOP","DOPSIT","DOSHUT","DPK","DSH","DUCRT","DUPK","FSC","IBHF","IBTF","ICOLH","ICOUB","ICRT","IDASIT","IDDSIT","IDFSIT","IDSHUT","IHPK","IHUPK","INPK","IOASIT","IOCLH","IOCUB","IOFSIT","IOOLH","IOOUB","IOPSIT","IOSHUT","IUBPK","IUBUPK","IUCRT","MS","NSTH","NSTUB"]`), &res); err != nil { panic(err) } for _, v := range res { diff --git a/pkg/gen/primev2messages/shipment_address_update.go b/pkg/gen/primev2messages/shipment_address_update.go index 9c42f92d5ca..af7df83363b 100644 --- a/pkg/gen/primev2messages/shipment_address_update.go +++ b/pkg/gen/primev2messages/shipment_address_update.go @@ -14,7 +14,7 @@ import ( "github.com/go-openapi/validate" ) -// ShipmentAddressUpdate This represents a destination address change request made by the Prime that is either auto-approved or requires review if the pricing criteria has changed. If criteria has changed, then it must be approved or rejected by a TOO. +// ShipmentAddressUpdate This represents a delivery address change request made by the Prime that is either auto-approved or requires review if the pricing criteria has changed. If criteria has changed, then it must be approved or rejected by a TOO. // // swagger:model ShipmentAddressUpdate type ShipmentAddressUpdate struct { @@ -38,7 +38,7 @@ type ShipmentAddressUpdate struct { // Required: true NewAddress *Address `json:"newAddress"` - // The distance between the original SIT address and requested new destination address of shipment + // The distance between the original SIT address and requested new delivery address of shipment // Example: 88 // Minimum: 0 NewSitDistanceBetween *int64 `json:"newSitDistanceBetween,omitempty"` @@ -49,7 +49,7 @@ type ShipmentAddressUpdate struct { // Example: This is an office remark OfficeRemarks *string `json:"officeRemarks,omitempty"` - // The distance between the original SIT address and the previous/old destination address of shipment + // The distance between the original SIT address and the previous/old delivery address of shipment // Example: 50 // Minimum: 0 OldSitDistanceBetween *int64 `json:"oldSitDistanceBetween,omitempty"` diff --git a/pkg/gen/primev2messages/update_shipment_destination_address.go b/pkg/gen/primev2messages/update_shipment_destination_address.go index 5c4e9f085d2..aa41a7747d8 100644 --- a/pkg/gen/primev2messages/update_shipment_destination_address.go +++ b/pkg/gen/primev2messages/update_shipment_destination_address.go @@ -14,7 +14,7 @@ import ( "github.com/go-openapi/validate" ) -// UpdateShipmentDestinationAddress UpdateShipmentDestinationAddress contains the fields required for the prime to request an update for the destination address on an MTO Shipment. +// UpdateShipmentDestinationAddress UpdateShipmentDestinationAddress contains the fields required for the prime to request an update for the delivery address on an MTO Shipment. // // swagger:model UpdateShipmentDestinationAddress type UpdateShipmentDestinationAddress struct { diff --git a/pkg/gen/primev3api/embedded_spec.go b/pkg/gen/primev3api/embedded_spec.go index 2a42e3367de..21011b7b868 100644 --- a/pkg/gen/primev3api/embedded_spec.go +++ b/pkg/gen/primev3api/embedded_spec.go @@ -739,7 +739,7 @@ func init() { "example": "handle with care" }, "destinationAddress": { - "description": "Where the movers should deliver this shipment.", + "description": "primary location the movers should deliver this shipment.", "allOf": [ { "$ref": "#/definitions/Address" @@ -773,7 +773,7 @@ func init() { } }, "pickupAddress": { - "description": "The address where the movers should pick up this shipment.", + "description": "The primary address where the movers should pick up this shipment.", "allOf": [ { "$ref": "#/definitions/Address" @@ -801,7 +801,7 @@ func init() { "x-nullable": true }, "secondaryDestinationAddress": { - "description": "The second address where the movers should deliver this shipment.", + "description": "second location where the movers should deliver this shipment.", "allOf": [ { "$ref": "#/definitions/Address" @@ -820,7 +820,7 @@ func init() { "$ref": "#/definitions/MTOShipmentType" }, "tertiaryDestinationAddress": { - "description": "The third address where the movers should deliver this shipment.", + "description": "third location where the movers should deliver this shipment.", "allOf": [ { "$ref": "#/definitions/Address" @@ -938,7 +938,7 @@ func init() { ] }, "secondaryPickupAddress": { - "description": "An optional secondary pickup location address near the origin where additional goods exist.", + "description": "An optional secondary Pickup Address address near the origin where additional goods exist.", "allOf": [ { "$ref": "#/definitions/Address" @@ -991,7 +991,7 @@ func init() { ] }, "tertiaryPickupAddress": { - "description": "An optional tertiary pickup location address near the origin where additional goods exist.", + "description": "An optional tertiary Pickup Address address near the origin where additional goods exist.", "allOf": [ { "$ref": "#/definitions/Address" @@ -1553,15 +1553,88 @@ func init() { } ] }, + "MTOServiceItemInternationalCrating": { + "description": "Describes a international crating/uncrating service item subtype of a MTOServiceItem.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItem" + }, + { + "type": "object", + "required": [ + "reServiceCode", + "item", + "crate", + "description" + ], + "properties": { + "crate": { + "description": "The dimensions for the crate the item will be shipped in.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItemDimension" + } + ] + }, + "description": { + "description": "A description of the item being crated.", + "type": "string", + "example": "Decorated horse head to be crated." + }, + "externalCrate": { + "type": "boolean", + "x-nullable": true + }, + "item": { + "description": "The dimensions of the item being crated.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItemDimension" + } + ] + }, + "market": { + "description": "To identify whether the service was provided within (CONUS) or (OCONUS)", + "type": "string", + "enum": [ + "CONUS", + "OCONUS" + ], + "example": "CONUS" + }, + "reServiceCode": { + "description": "A unique code for the service item. Indicates if the service is for crating (ICRT) or uncrating (IUCRT).", + "type": "string", + "enum": [ + "ICRT", + "IUCRT" + ] + }, + "reason": { + "description": "The contractor's explanation for why an item needed to be crated or uncrated. Used by the TOO while deciding to approve or reject the service item.\n", + "type": "string", + "x-nullable": true, + "x-omitempty": false, + "example": "Storage items need to be picked up" + }, + "standaloneCrate": { + "type": "boolean", + "x-nullable": true + } + } + } + ] + }, "MTOServiceItemModelType": { - "description": "Describes all model sub-types for a MTOServiceItem model.\n\nUsing this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DOFSIT, DOASIT - MTOServiceItemOriginSIT\n * DDFSIT, DDASIT - MTOServiceItemDestSIT\n * DOSHUT, DDSHUT - MTOServiceItemShuttle\n * DCRT, DUCRT - MTOServiceItemDomesticCrating\n\nThe documentation will then update with the supported fields.\n", + "description": "Describes all model sub-types for a MTOServiceItem model.\n\nUsing this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DOFSIT, DOASIT - MTOServiceItemOriginSIT\n * DDFSIT, DDASIT - MTOServiceItemDestSIT\n * DOSHUT, DDSHUT - MTOServiceItemShuttle\n * DCRT, DUCRT - MTOServiceItemDomesticCrating\n * ICRT, IUCRT - MTOServiceItemInternationalCrating\n\nThe documentation will then update with the supported fields.\n", "type": "string", "enum": [ "MTOServiceItemBasic", "MTOServiceItemOriginSIT", "MTOServiceItemDestSIT", "MTOServiceItemShuttle", - "MTOServiceItemDomesticCrating" + "MTOServiceItemDomesticCrating", + "MTOServiceItemInternationalCrating" ] }, "MTOServiceItemOriginSIT": { @@ -2597,6 +2670,13 @@ func init() { "x-omitempty": false, "example": false }, + "maxIncentive": { + "description": "The max amount the government will pay the service member to move their belongings based on the moving date, locations, and shipment weight.", + "type": "integer", + "format": "cents", + "x-nullable": true, + "x-omitempty": false + }, "pickupAddress": { "$ref": "#/definitions/Address" }, @@ -2938,7 +3018,6 @@ func init() { "ICOLH", "ICOUB", "ICRT", - "ICRTSA", "IDASIT", "IDDSIT", "IDFSIT", @@ -3230,7 +3309,7 @@ func init() { } }, "ShipmentAddressUpdate": { - "description": "This represents a destination address change request made by the Prime that is either auto-approved or requires review if the pricing criteria has changed. If criteria has changed, then it must be approved or rejected by a TOO.\n", + "description": "This represents a delivery address change request made by the Prime that is either auto-approved or requires review if the pricing criteria has changed. If criteria has changed, then it must be approved or rejected by a TOO.\n", "type": "object", "required": [ "id", @@ -3258,7 +3337,7 @@ func init() { "$ref": "#/definitions/Address" }, "newSitDistanceBetween": { - "description": "The distance between the original SIT address and requested new destination address of shipment", + "description": "The distance between the original SIT address and requested new delivery address of shipment", "type": "integer", "example": 88 }, @@ -3270,7 +3349,7 @@ func init() { "example": "This is an office remark" }, "oldSitDistanceBetween": { - "description": "The distance between the original SIT address and the previous/old destination address of shipment", + "description": "The distance between the original SIT address and the previous/old delivery address of shipment", "type": "integer", "example": 50 }, @@ -3751,7 +3830,7 @@ func init() { ] }, "secondaryPickupAddress": { - "description": "An optional secondary pickup location near the origin where additional goods exist.\n", + "description": "An optional secondary Pickup Address near the origin where additional goods exist.\n", "allOf": [ { "$ref": "#/definitions/Address" @@ -3805,7 +3884,7 @@ func init() { ] }, "tertiaryPickupAddress": { - "description": "An optional third pickup location near the origin where additional goods exist.\n", + "description": "An optional third Pickup Address near the origin where additional goods exist.\n", "allOf": [ { "$ref": "#/definitions/Address" @@ -3837,7 +3916,7 @@ func init() { } }, "UpdateShipmentDestinationAddress": { - "description": "UpdateShipmentDestinationAddress contains the fields required for the prime to request an update for the destination address on an MTO Shipment.", + "description": "UpdateShipmentDestinationAddress contains the fields required for the prime to request an update for the delivery address on an MTO Shipment.", "type": "object", "required": [ "contractorRemarks", @@ -4772,7 +4851,7 @@ func init() { "example": "handle with care" }, "destinationAddress": { - "description": "Where the movers should deliver this shipment.", + "description": "primary location the movers should deliver this shipment.", "allOf": [ { "$ref": "#/definitions/Address" @@ -4806,7 +4885,7 @@ func init() { } }, "pickupAddress": { - "description": "The address where the movers should pick up this shipment.", + "description": "The primary address where the movers should pick up this shipment.", "allOf": [ { "$ref": "#/definitions/Address" @@ -4834,7 +4913,7 @@ func init() { "x-nullable": true }, "secondaryDestinationAddress": { - "description": "The second address where the movers should deliver this shipment.", + "description": "second location where the movers should deliver this shipment.", "allOf": [ { "$ref": "#/definitions/Address" @@ -4853,7 +4932,7 @@ func init() { "$ref": "#/definitions/MTOShipmentType" }, "tertiaryDestinationAddress": { - "description": "The third address where the movers should deliver this shipment.", + "description": "third location where the movers should deliver this shipment.", "allOf": [ { "$ref": "#/definitions/Address" @@ -4971,7 +5050,7 @@ func init() { ] }, "secondaryPickupAddress": { - "description": "An optional secondary pickup location address near the origin where additional goods exist.", + "description": "An optional secondary Pickup Address address near the origin where additional goods exist.", "allOf": [ { "$ref": "#/definitions/Address" @@ -5024,7 +5103,7 @@ func init() { ] }, "tertiaryPickupAddress": { - "description": "An optional tertiary pickup location address near the origin where additional goods exist.", + "description": "An optional tertiary Pickup Address address near the origin where additional goods exist.", "allOf": [ { "$ref": "#/definitions/Address" @@ -5586,15 +5665,88 @@ func init() { } ] }, + "MTOServiceItemInternationalCrating": { + "description": "Describes a international crating/uncrating service item subtype of a MTOServiceItem.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItem" + }, + { + "type": "object", + "required": [ + "reServiceCode", + "item", + "crate", + "description" + ], + "properties": { + "crate": { + "description": "The dimensions for the crate the item will be shipped in.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItemDimension" + } + ] + }, + "description": { + "description": "A description of the item being crated.", + "type": "string", + "example": "Decorated horse head to be crated." + }, + "externalCrate": { + "type": "boolean", + "x-nullable": true + }, + "item": { + "description": "The dimensions of the item being crated.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItemDimension" + } + ] + }, + "market": { + "description": "To identify whether the service was provided within (CONUS) or (OCONUS)", + "type": "string", + "enum": [ + "CONUS", + "OCONUS" + ], + "example": "CONUS" + }, + "reServiceCode": { + "description": "A unique code for the service item. Indicates if the service is for crating (ICRT) or uncrating (IUCRT).", + "type": "string", + "enum": [ + "ICRT", + "IUCRT" + ] + }, + "reason": { + "description": "The contractor's explanation for why an item needed to be crated or uncrated. Used by the TOO while deciding to approve or reject the service item.\n", + "type": "string", + "x-nullable": true, + "x-omitempty": false, + "example": "Storage items need to be picked up" + }, + "standaloneCrate": { + "type": "boolean", + "x-nullable": true + } + } + } + ] + }, "MTOServiceItemModelType": { - "description": "Describes all model sub-types for a MTOServiceItem model.\n\nUsing this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DOFSIT, DOASIT - MTOServiceItemOriginSIT\n * DDFSIT, DDASIT - MTOServiceItemDestSIT\n * DOSHUT, DDSHUT - MTOServiceItemShuttle\n * DCRT, DUCRT - MTOServiceItemDomesticCrating\n\nThe documentation will then update with the supported fields.\n", + "description": "Describes all model sub-types for a MTOServiceItem model.\n\nUsing this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DOFSIT, DOASIT - MTOServiceItemOriginSIT\n * DDFSIT, DDASIT - MTOServiceItemDestSIT\n * DOSHUT, DDSHUT - MTOServiceItemShuttle\n * DCRT, DUCRT - MTOServiceItemDomesticCrating\n * ICRT, IUCRT - MTOServiceItemInternationalCrating\n\nThe documentation will then update with the supported fields.\n", "type": "string", "enum": [ "MTOServiceItemBasic", "MTOServiceItemOriginSIT", "MTOServiceItemDestSIT", "MTOServiceItemShuttle", - "MTOServiceItemDomesticCrating" + "MTOServiceItemDomesticCrating", + "MTOServiceItemInternationalCrating" ] }, "MTOServiceItemOriginSIT": { @@ -6630,6 +6782,13 @@ func init() { "x-omitempty": false, "example": false }, + "maxIncentive": { + "description": "The max amount the government will pay the service member to move their belongings based on the moving date, locations, and shipment weight.", + "type": "integer", + "format": "cents", + "x-nullable": true, + "x-omitempty": false + }, "pickupAddress": { "$ref": "#/definitions/Address" }, @@ -6971,7 +7130,6 @@ func init() { "ICOLH", "ICOUB", "ICRT", - "ICRTSA", "IDASIT", "IDDSIT", "IDFSIT", @@ -7263,7 +7421,7 @@ func init() { } }, "ShipmentAddressUpdate": { - "description": "This represents a destination address change request made by the Prime that is either auto-approved or requires review if the pricing criteria has changed. If criteria has changed, then it must be approved or rejected by a TOO.\n", + "description": "This represents a delivery address change request made by the Prime that is either auto-approved or requires review if the pricing criteria has changed. If criteria has changed, then it must be approved or rejected by a TOO.\n", "type": "object", "required": [ "id", @@ -7291,7 +7449,7 @@ func init() { "$ref": "#/definitions/Address" }, "newSitDistanceBetween": { - "description": "The distance between the original SIT address and requested new destination address of shipment", + "description": "The distance between the original SIT address and requested new delivery address of shipment", "type": "integer", "minimum": 0, "example": 88 @@ -7304,7 +7462,7 @@ func init() { "example": "This is an office remark" }, "oldSitDistanceBetween": { - "description": "The distance between the original SIT address and the previous/old destination address of shipment", + "description": "The distance between the original SIT address and the previous/old delivery address of shipment", "type": "integer", "minimum": 0, "example": 50 @@ -7786,7 +7944,7 @@ func init() { ] }, "secondaryPickupAddress": { - "description": "An optional secondary pickup location near the origin where additional goods exist.\n", + "description": "An optional secondary Pickup Address near the origin where additional goods exist.\n", "allOf": [ { "$ref": "#/definitions/Address" @@ -7840,7 +7998,7 @@ func init() { ] }, "tertiaryPickupAddress": { - "description": "An optional third pickup location near the origin where additional goods exist.\n", + "description": "An optional third Pickup Address near the origin where additional goods exist.\n", "allOf": [ { "$ref": "#/definitions/Address" @@ -7872,7 +8030,7 @@ func init() { } }, "UpdateShipmentDestinationAddress": { - "description": "UpdateShipmentDestinationAddress contains the fields required for the prime to request an update for the destination address on an MTO Shipment.", + "description": "UpdateShipmentDestinationAddress contains the fields required for the prime to request an update for the delivery address on an MTO Shipment.", "type": "object", "required": [ "contractorRemarks", diff --git a/pkg/gen/primev3messages/create_m_t_o_shipment.go b/pkg/gen/primev3messages/create_m_t_o_shipment.go index d3be412344b..c8496c4f60e 100644 --- a/pkg/gen/primev3messages/create_m_t_o_shipment.go +++ b/pkg/gen/primev3messages/create_m_t_o_shipment.go @@ -45,7 +45,7 @@ type CreateMTOShipment struct { // Example: handle with care CustomerRemarks *string `json:"customerRemarks,omitempty"` - // Where the movers should deliver this shipment. + // primary location the movers should deliver this shipment. DestinationAddress struct { Address } `json:"destinationAddress,omitempty"` @@ -71,7 +71,7 @@ type CreateMTOShipment struct { mtoServiceItemsField []MTOServiceItem - // The address where the movers should pick up this shipment. + // The primary address where the movers should pick up this shipment. PickupAddress struct { Address } `json:"pickupAddress,omitempty"` @@ -94,7 +94,7 @@ type CreateMTOShipment struct { // Format: date RequestedPickupDate *strfmt.Date `json:"requestedPickupDate,omitempty"` - // The second address where the movers should deliver this shipment. + // second location where the movers should deliver this shipment. SecondaryDestinationAddress struct { Address } `json:"secondaryDestinationAddress,omitempty"` @@ -108,7 +108,7 @@ type CreateMTOShipment struct { // Required: true ShipmentType *MTOShipmentType `json:"shipmentType"` - // The third address where the movers should deliver this shipment. + // third location where the movers should deliver this shipment. TertiaryDestinationAddress struct { Address } `json:"tertiaryDestinationAddress,omitempty"` diff --git a/pkg/gen/primev3messages/create_p_p_m_shipment.go b/pkg/gen/primev3messages/create_p_p_m_shipment.go index bc0b3c5c83a..61a5a3ed5ac 100644 --- a/pkg/gen/primev3messages/create_p_p_m_shipment.go +++ b/pkg/gen/primev3messages/create_p_p_m_shipment.go @@ -59,7 +59,7 @@ type CreatePPMShipment struct { Address } `json:"secondaryDestinationAddress,omitempty"` - // An optional secondary pickup location address near the origin where additional goods exist. + // An optional secondary Pickup Address address near the origin where additional goods exist. SecondaryPickupAddress struct { Address } `json:"secondaryPickupAddress,omitempty"` @@ -94,7 +94,7 @@ type CreatePPMShipment struct { Address } `json:"tertiaryDestinationAddress,omitempty"` - // An optional tertiary pickup location address near the origin where additional goods exist. + // An optional tertiary Pickup Address address near the origin where additional goods exist. TertiaryPickupAddress struct { Address } `json:"tertiaryPickupAddress,omitempty"` diff --git a/pkg/gen/primev3messages/m_t_o_service_item.go b/pkg/gen/primev3messages/m_t_o_service_item.go index 271d087d86e..9559114c004 100644 --- a/pkg/gen/primev3messages/m_t_o_service_item.go +++ b/pkg/gen/primev3messages/m_t_o_service_item.go @@ -273,6 +273,12 @@ func unmarshalMTOServiceItem(data []byte, consumer runtime.Consumer) (MTOService return nil, err } return &result, nil + case "MTOServiceItemInternationalCrating": + var result MTOServiceItemInternationalCrating + if err := consumer.Consume(buf2, &result); err != nil { + return nil, err + } + return &result, nil case "MTOServiceItemOriginSIT": var result MTOServiceItemOriginSIT if err := consumer.Consume(buf2, &result); err != nil { diff --git a/pkg/gen/primev3messages/m_t_o_service_item_international_crating.go b/pkg/gen/primev3messages/m_t_o_service_item_international_crating.go new file mode 100644 index 00000000000..55a759a0682 --- /dev/null +++ b/pkg/gen/primev3messages/m_t_o_service_item_international_crating.go @@ -0,0 +1,773 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package primev3messages + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "bytes" + "context" + "encoding/json" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// MTOServiceItemInternationalCrating Describes a international crating/uncrating service item subtype of a MTOServiceItem. +// +// swagger:model MTOServiceItemInternationalCrating +type MTOServiceItemInternationalCrating struct { + eTagField string + + idField strfmt.UUID + + lockedPriceCentsField *int64 + + moveTaskOrderIdField *strfmt.UUID + + mtoShipmentIdField strfmt.UUID + + reServiceNameField string + + rejectionReasonField *string + + serviceRequestDocumentsField ServiceRequestDocuments + + statusField MTOServiceItemStatus + + // The dimensions for the crate the item will be shipped in. + // Required: true + Crate struct { + MTOServiceItemDimension + } `json:"crate"` + + // A description of the item being crated. + // Example: Decorated horse head to be crated. + // Required: true + Description *string `json:"description"` + + // external crate + ExternalCrate *bool `json:"externalCrate,omitempty"` + + // The dimensions of the item being crated. + // Required: true + Item struct { + MTOServiceItemDimension + } `json:"item"` + + // To identify whether the service was provided within (CONUS) or (OCONUS) + // Example: CONUS + // Enum: [CONUS OCONUS] + Market string `json:"market,omitempty"` + + // A unique code for the service item. Indicates if the service is for crating (ICRT) or uncrating (IUCRT). + // Required: true + // Enum: [ICRT IUCRT] + ReServiceCode *string `json:"reServiceCode"` + + // The contractor's explanation for why an item needed to be crated or uncrated. Used by the TOO while deciding to approve or reject the service item. + // + // Example: Storage items need to be picked up + Reason *string `json:"reason"` + + // standalone crate + StandaloneCrate *bool `json:"standaloneCrate,omitempty"` +} + +// ETag gets the e tag of this subtype +func (m *MTOServiceItemInternationalCrating) ETag() string { + return m.eTagField +} + +// SetETag sets the e tag of this subtype +func (m *MTOServiceItemInternationalCrating) SetETag(val string) { + m.eTagField = val +} + +// ID gets the id of this subtype +func (m *MTOServiceItemInternationalCrating) ID() strfmt.UUID { + return m.idField +} + +// SetID sets the id of this subtype +func (m *MTOServiceItemInternationalCrating) SetID(val strfmt.UUID) { + m.idField = val +} + +// LockedPriceCents gets the locked price cents of this subtype +func (m *MTOServiceItemInternationalCrating) LockedPriceCents() *int64 { + return m.lockedPriceCentsField +} + +// SetLockedPriceCents sets the locked price cents of this subtype +func (m *MTOServiceItemInternationalCrating) SetLockedPriceCents(val *int64) { + m.lockedPriceCentsField = val +} + +// ModelType gets the model type of this subtype +func (m *MTOServiceItemInternationalCrating) ModelType() MTOServiceItemModelType { + return "MTOServiceItemInternationalCrating" +} + +// SetModelType sets the model type of this subtype +func (m *MTOServiceItemInternationalCrating) SetModelType(val MTOServiceItemModelType) { +} + +// MoveTaskOrderID gets the move task order ID of this subtype +func (m *MTOServiceItemInternationalCrating) MoveTaskOrderID() *strfmt.UUID { + return m.moveTaskOrderIdField +} + +// SetMoveTaskOrderID sets the move task order ID of this subtype +func (m *MTOServiceItemInternationalCrating) SetMoveTaskOrderID(val *strfmt.UUID) { + m.moveTaskOrderIdField = val +} + +// MtoShipmentID gets the mto shipment ID of this subtype +func (m *MTOServiceItemInternationalCrating) MtoShipmentID() strfmt.UUID { + return m.mtoShipmentIdField +} + +// SetMtoShipmentID sets the mto shipment ID of this subtype +func (m *MTOServiceItemInternationalCrating) SetMtoShipmentID(val strfmt.UUID) { + m.mtoShipmentIdField = val +} + +// ReServiceName gets the re service name of this subtype +func (m *MTOServiceItemInternationalCrating) ReServiceName() string { + return m.reServiceNameField +} + +// SetReServiceName sets the re service name of this subtype +func (m *MTOServiceItemInternationalCrating) SetReServiceName(val string) { + m.reServiceNameField = val +} + +// RejectionReason gets the rejection reason of this subtype +func (m *MTOServiceItemInternationalCrating) RejectionReason() *string { + return m.rejectionReasonField +} + +// SetRejectionReason sets the rejection reason of this subtype +func (m *MTOServiceItemInternationalCrating) SetRejectionReason(val *string) { + m.rejectionReasonField = val +} + +// ServiceRequestDocuments gets the service request documents of this subtype +func (m *MTOServiceItemInternationalCrating) ServiceRequestDocuments() ServiceRequestDocuments { + return m.serviceRequestDocumentsField +} + +// SetServiceRequestDocuments sets the service request documents of this subtype +func (m *MTOServiceItemInternationalCrating) SetServiceRequestDocuments(val ServiceRequestDocuments) { + m.serviceRequestDocumentsField = val +} + +// Status gets the status of this subtype +func (m *MTOServiceItemInternationalCrating) Status() MTOServiceItemStatus { + return m.statusField +} + +// SetStatus sets the status of this subtype +func (m *MTOServiceItemInternationalCrating) SetStatus(val MTOServiceItemStatus) { + m.statusField = val +} + +// UnmarshalJSON unmarshals this object with a polymorphic type from a JSON structure +func (m *MTOServiceItemInternationalCrating) UnmarshalJSON(raw []byte) error { + var data struct { + + // The dimensions for the crate the item will be shipped in. + // Required: true + Crate struct { + MTOServiceItemDimension + } `json:"crate"` + + // A description of the item being crated. + // Example: Decorated horse head to be crated. + // Required: true + Description *string `json:"description"` + + // external crate + ExternalCrate *bool `json:"externalCrate,omitempty"` + + // The dimensions of the item being crated. + // Required: true + Item struct { + MTOServiceItemDimension + } `json:"item"` + + // To identify whether the service was provided within (CONUS) or (OCONUS) + // Example: CONUS + // Enum: [CONUS OCONUS] + Market string `json:"market,omitempty"` + + // A unique code for the service item. Indicates if the service is for crating (ICRT) or uncrating (IUCRT). + // Required: true + // Enum: [ICRT IUCRT] + ReServiceCode *string `json:"reServiceCode"` + + // The contractor's explanation for why an item needed to be crated or uncrated. Used by the TOO while deciding to approve or reject the service item. + // + // Example: Storage items need to be picked up + Reason *string `json:"reason"` + + // standalone crate + StandaloneCrate *bool `json:"standaloneCrate,omitempty"` + } + buf := bytes.NewBuffer(raw) + dec := json.NewDecoder(buf) + dec.UseNumber() + + if err := dec.Decode(&data); err != nil { + return err + } + + var base struct { + /* Just the base type fields. Used for unmashalling polymorphic types.*/ + + ETag string `json:"eTag,omitempty"` + + ID strfmt.UUID `json:"id,omitempty"` + + LockedPriceCents *int64 `json:"lockedPriceCents,omitempty"` + + ModelType MTOServiceItemModelType `json:"modelType"` + + MoveTaskOrderID *strfmt.UUID `json:"moveTaskOrderID"` + + MtoShipmentID strfmt.UUID `json:"mtoShipmentID,omitempty"` + + ReServiceName string `json:"reServiceName,omitempty"` + + RejectionReason *string `json:"rejectionReason,omitempty"` + + ServiceRequestDocuments ServiceRequestDocuments `json:"serviceRequestDocuments,omitempty"` + + Status MTOServiceItemStatus `json:"status,omitempty"` + } + buf = bytes.NewBuffer(raw) + dec = json.NewDecoder(buf) + dec.UseNumber() + + if err := dec.Decode(&base); err != nil { + return err + } + + var result MTOServiceItemInternationalCrating + + result.eTagField = base.ETag + + result.idField = base.ID + + result.lockedPriceCentsField = base.LockedPriceCents + + if base.ModelType != result.ModelType() { + /* Not the type we're looking for. */ + return errors.New(422, "invalid modelType value: %q", base.ModelType) + } + result.moveTaskOrderIdField = base.MoveTaskOrderID + + result.mtoShipmentIdField = base.MtoShipmentID + + result.reServiceNameField = base.ReServiceName + + result.rejectionReasonField = base.RejectionReason + + result.serviceRequestDocumentsField = base.ServiceRequestDocuments + + result.statusField = base.Status + + result.Crate = data.Crate + result.Description = data.Description + result.ExternalCrate = data.ExternalCrate + result.Item = data.Item + result.Market = data.Market + result.ReServiceCode = data.ReServiceCode + result.Reason = data.Reason + result.StandaloneCrate = data.StandaloneCrate + + *m = result + + return nil +} + +// MarshalJSON marshals this object with a polymorphic type to a JSON structure +func (m MTOServiceItemInternationalCrating) MarshalJSON() ([]byte, error) { + var b1, b2, b3 []byte + var err error + b1, err = json.Marshal(struct { + + // The dimensions for the crate the item will be shipped in. + // Required: true + Crate struct { + MTOServiceItemDimension + } `json:"crate"` + + // A description of the item being crated. + // Example: Decorated horse head to be crated. + // Required: true + Description *string `json:"description"` + + // external crate + ExternalCrate *bool `json:"externalCrate,omitempty"` + + // The dimensions of the item being crated. + // Required: true + Item struct { + MTOServiceItemDimension + } `json:"item"` + + // To identify whether the service was provided within (CONUS) or (OCONUS) + // Example: CONUS + // Enum: [CONUS OCONUS] + Market string `json:"market,omitempty"` + + // A unique code for the service item. Indicates if the service is for crating (ICRT) or uncrating (IUCRT). + // Required: true + // Enum: [ICRT IUCRT] + ReServiceCode *string `json:"reServiceCode"` + + // The contractor's explanation for why an item needed to be crated or uncrated. Used by the TOO while deciding to approve or reject the service item. + // + // Example: Storage items need to be picked up + Reason *string `json:"reason"` + + // standalone crate + StandaloneCrate *bool `json:"standaloneCrate,omitempty"` + }{ + + Crate: m.Crate, + + Description: m.Description, + + ExternalCrate: m.ExternalCrate, + + Item: m.Item, + + Market: m.Market, + + ReServiceCode: m.ReServiceCode, + + Reason: m.Reason, + + StandaloneCrate: m.StandaloneCrate, + }) + if err != nil { + return nil, err + } + b2, err = json.Marshal(struct { + ETag string `json:"eTag,omitempty"` + + ID strfmt.UUID `json:"id,omitempty"` + + LockedPriceCents *int64 `json:"lockedPriceCents,omitempty"` + + ModelType MTOServiceItemModelType `json:"modelType"` + + MoveTaskOrderID *strfmt.UUID `json:"moveTaskOrderID"` + + MtoShipmentID strfmt.UUID `json:"mtoShipmentID,omitempty"` + + ReServiceName string `json:"reServiceName,omitempty"` + + RejectionReason *string `json:"rejectionReason,omitempty"` + + ServiceRequestDocuments ServiceRequestDocuments `json:"serviceRequestDocuments,omitempty"` + + Status MTOServiceItemStatus `json:"status,omitempty"` + }{ + + ETag: m.ETag(), + + ID: m.ID(), + + LockedPriceCents: m.LockedPriceCents(), + + ModelType: m.ModelType(), + + MoveTaskOrderID: m.MoveTaskOrderID(), + + MtoShipmentID: m.MtoShipmentID(), + + ReServiceName: m.ReServiceName(), + + RejectionReason: m.RejectionReason(), + + ServiceRequestDocuments: m.ServiceRequestDocuments(), + + Status: m.Status(), + }) + if err != nil { + return nil, err + } + + return swag.ConcatJSON(b1, b2, b3), nil +} + +// Validate validates this m t o service item international crating +func (m *MTOServiceItemInternationalCrating) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateID(formats); err != nil { + res = append(res, err) + } + + if err := m.validateMoveTaskOrderID(formats); err != nil { + res = append(res, err) + } + + if err := m.validateMtoShipmentID(formats); err != nil { + res = append(res, err) + } + + if err := m.validateServiceRequestDocuments(formats); err != nil { + res = append(res, err) + } + + if err := m.validateStatus(formats); err != nil { + res = append(res, err) + } + + if err := m.validateCrate(formats); err != nil { + res = append(res, err) + } + + if err := m.validateDescription(formats); err != nil { + res = append(res, err) + } + + if err := m.validateItem(formats); err != nil { + res = append(res, err) + } + + if err := m.validateMarket(formats); err != nil { + res = append(res, err) + } + + if err := m.validateReServiceCode(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *MTOServiceItemInternationalCrating) validateID(formats strfmt.Registry) error { + + if swag.IsZero(m.ID()) { // not required + return nil + } + + if err := validate.FormatOf("id", "body", "uuid", m.ID().String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalCrating) validateMoveTaskOrderID(formats strfmt.Registry) error { + + if err := validate.Required("moveTaskOrderID", "body", m.MoveTaskOrderID()); err != nil { + return err + } + + if err := validate.FormatOf("moveTaskOrderID", "body", "uuid", m.MoveTaskOrderID().String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalCrating) validateMtoShipmentID(formats strfmt.Registry) error { + + if swag.IsZero(m.MtoShipmentID()) { // not required + return nil + } + + if err := validate.FormatOf("mtoShipmentID", "body", "uuid", m.MtoShipmentID().String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalCrating) validateServiceRequestDocuments(formats strfmt.Registry) error { + + if swag.IsZero(m.ServiceRequestDocuments()) { // not required + return nil + } + + if err := m.ServiceRequestDocuments().Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("serviceRequestDocuments") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("serviceRequestDocuments") + } + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalCrating) validateStatus(formats strfmt.Registry) error { + + if swag.IsZero(m.Status()) { // not required + return nil + } + + if err := m.Status().Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("status") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("status") + } + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalCrating) validateCrate(formats strfmt.Registry) error { + + return nil +} + +func (m *MTOServiceItemInternationalCrating) validateDescription(formats strfmt.Registry) error { + + if err := validate.Required("description", "body", m.Description); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalCrating) validateItem(formats strfmt.Registry) error { + + return nil +} + +var mTOServiceItemInternationalCratingTypeMarketPropEnum []interface{} + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["CONUS","OCONUS"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + mTOServiceItemInternationalCratingTypeMarketPropEnum = append(mTOServiceItemInternationalCratingTypeMarketPropEnum, v) + } +} + +// property enum +func (m *MTOServiceItemInternationalCrating) validateMarketEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, mTOServiceItemInternationalCratingTypeMarketPropEnum, true); err != nil { + return err + } + return nil +} + +func (m *MTOServiceItemInternationalCrating) validateMarket(formats strfmt.Registry) error { + + if swag.IsZero(m.Market) { // not required + return nil + } + + // value enum + if err := m.validateMarketEnum("market", "body", m.Market); err != nil { + return err + } + + return nil +} + +var mTOServiceItemInternationalCratingTypeReServiceCodePropEnum []interface{} + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["ICRT","IUCRT"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + mTOServiceItemInternationalCratingTypeReServiceCodePropEnum = append(mTOServiceItemInternationalCratingTypeReServiceCodePropEnum, v) + } +} + +// property enum +func (m *MTOServiceItemInternationalCrating) validateReServiceCodeEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, mTOServiceItemInternationalCratingTypeReServiceCodePropEnum, true); err != nil { + return err + } + return nil +} + +func (m *MTOServiceItemInternationalCrating) validateReServiceCode(formats strfmt.Registry) error { + + if err := validate.Required("reServiceCode", "body", m.ReServiceCode); err != nil { + return err + } + + // value enum + if err := m.validateReServiceCodeEnum("reServiceCode", "body", *m.ReServiceCode); err != nil { + return err + } + + return nil +} + +// ContextValidate validate this m t o service item international crating based on the context it is used +func (m *MTOServiceItemInternationalCrating) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateETag(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateID(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateReServiceName(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateRejectionReason(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateServiceRequestDocuments(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateStatus(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateCrate(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateItem(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *MTOServiceItemInternationalCrating) contextValidateETag(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "eTag", "body", string(m.ETag())); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalCrating) contextValidateID(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "id", "body", strfmt.UUID(m.ID())); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalCrating) contextValidateModelType(ctx context.Context, formats strfmt.Registry) error { + + if err := m.ModelType().ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("modelType") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("modelType") + } + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalCrating) contextValidateReServiceName(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "reServiceName", "body", string(m.ReServiceName())); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalCrating) contextValidateRejectionReason(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "rejectionReason", "body", m.RejectionReason()); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalCrating) contextValidateServiceRequestDocuments(ctx context.Context, formats strfmt.Registry) error { + + if err := m.ServiceRequestDocuments().ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("serviceRequestDocuments") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("serviceRequestDocuments") + } + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalCrating) contextValidateStatus(ctx context.Context, formats strfmt.Registry) error { + + if swag.IsZero(m.Status()) { // not required + return nil + } + + if err := m.Status().ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("status") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("status") + } + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalCrating) contextValidateCrate(ctx context.Context, formats strfmt.Registry) error { + + return nil +} + +func (m *MTOServiceItemInternationalCrating) contextValidateItem(ctx context.Context, formats strfmt.Registry) error { + + return nil +} + +// MarshalBinary interface implementation +func (m *MTOServiceItemInternationalCrating) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *MTOServiceItemInternationalCrating) UnmarshalBinary(b []byte) error { + var res MTOServiceItemInternationalCrating + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/pkg/gen/primev3messages/m_t_o_service_item_model_type.go b/pkg/gen/primev3messages/m_t_o_service_item_model_type.go index 1ba55597506..fd5e854a7ab 100644 --- a/pkg/gen/primev3messages/m_t_o_service_item_model_type.go +++ b/pkg/gen/primev3messages/m_t_o_service_item_model_type.go @@ -21,6 +21,7 @@ import ( // - DDFSIT, DDASIT - MTOServiceItemDestSIT // - DOSHUT, DDSHUT - MTOServiceItemShuttle // - DCRT, DUCRT - MTOServiceItemDomesticCrating +// - ICRT, IUCRT - MTOServiceItemInternationalCrating // // The documentation will then update with the supported fields. // @@ -52,6 +53,9 @@ const ( // MTOServiceItemModelTypeMTOServiceItemDomesticCrating captures enum value "MTOServiceItemDomesticCrating" MTOServiceItemModelTypeMTOServiceItemDomesticCrating MTOServiceItemModelType = "MTOServiceItemDomesticCrating" + + // MTOServiceItemModelTypeMTOServiceItemInternationalCrating captures enum value "MTOServiceItemInternationalCrating" + MTOServiceItemModelTypeMTOServiceItemInternationalCrating MTOServiceItemModelType = "MTOServiceItemInternationalCrating" ) // for schema @@ -59,7 +63,7 @@ var mTOServiceItemModelTypeEnum []interface{} func init() { var res []MTOServiceItemModelType - if err := json.Unmarshal([]byte(`["MTOServiceItemBasic","MTOServiceItemOriginSIT","MTOServiceItemDestSIT","MTOServiceItemShuttle","MTOServiceItemDomesticCrating"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["MTOServiceItemBasic","MTOServiceItemOriginSIT","MTOServiceItemDestSIT","MTOServiceItemShuttle","MTOServiceItemDomesticCrating","MTOServiceItemInternationalCrating"]`), &res); err != nil { panic(err) } for _, v := range res { diff --git a/pkg/gen/primev3messages/p_p_m_shipment.go b/pkg/gen/primev3messages/p_p_m_shipment.go index 9f12985aeef..89d6cd51c5f 100644 --- a/pkg/gen/primev3messages/p_p_m_shipment.go +++ b/pkg/gen/primev3messages/p_p_m_shipment.go @@ -114,6 +114,9 @@ type PPMShipment struct { // Example: false IsActualExpenseReimbursement *bool `json:"isActualExpenseReimbursement"` + // The max amount the government will pay the service member to move their belongings based on the moving date, locations, and shipment weight. + MaxIncentive *int64 `json:"maxIncentive"` + // pickup address // Required: true PickupAddress *Address `json:"pickupAddress"` diff --git a/pkg/gen/primev3messages/re_service_code.go b/pkg/gen/primev3messages/re_service_code.go index 1f328d6ee96..2028866f693 100644 --- a/pkg/gen/primev3messages/re_service_code.go +++ b/pkg/gen/primev3messages/re_service_code.go @@ -114,9 +114,6 @@ const ( // ReServiceCodeICRT captures enum value "ICRT" ReServiceCodeICRT ReServiceCode = "ICRT" - // ReServiceCodeICRTSA captures enum value "ICRTSA" - ReServiceCodeICRTSA ReServiceCode = "ICRTSA" - // ReServiceCodeIDASIT captures enum value "IDASIT" ReServiceCodeIDASIT ReServiceCode = "IDASIT" @@ -186,7 +183,7 @@ var reServiceCodeEnum []interface{} func init() { var res []ReServiceCode - if err := json.Unmarshal([]byte(`["CS","DBHF","DBTF","DCRT","DDASIT","DDDSIT","DDFSIT","DDP","DDSHUT","DLH","DMHF","DNPK","DOASIT","DOFSIT","DOP","DOPSIT","DOSHUT","DPK","DSH","DUCRT","DUPK","FSC","IBHF","IBTF","ICOLH","ICOUB","ICRT","ICRTSA","IDASIT","IDDSIT","IDFSIT","IDSHUT","IHPK","IHUPK","INPK","IOASIT","IOCLH","IOCUB","IOFSIT","IOOLH","IOOUB","IOPSIT","IOSHUT","IUBPK","IUBUPK","IUCRT","MS","NSTH","NSTUB"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["CS","DBHF","DBTF","DCRT","DDASIT","DDDSIT","DDFSIT","DDP","DDSHUT","DLH","DMHF","DNPK","DOASIT","DOFSIT","DOP","DOPSIT","DOSHUT","DPK","DSH","DUCRT","DUPK","FSC","IBHF","IBTF","ICOLH","ICOUB","ICRT","IDASIT","IDDSIT","IDFSIT","IDSHUT","IHPK","IHUPK","INPK","IOASIT","IOCLH","IOCUB","IOFSIT","IOOLH","IOOUB","IOPSIT","IOSHUT","IUBPK","IUBUPK","IUCRT","MS","NSTH","NSTUB"]`), &res); err != nil { panic(err) } for _, v := range res { diff --git a/pkg/gen/primev3messages/shipment_address_update.go b/pkg/gen/primev3messages/shipment_address_update.go index 0ffe7bdd055..f18dd49e3ee 100644 --- a/pkg/gen/primev3messages/shipment_address_update.go +++ b/pkg/gen/primev3messages/shipment_address_update.go @@ -14,7 +14,7 @@ import ( "github.com/go-openapi/validate" ) -// ShipmentAddressUpdate This represents a destination address change request made by the Prime that is either auto-approved or requires review if the pricing criteria has changed. If criteria has changed, then it must be approved or rejected by a TOO. +// ShipmentAddressUpdate This represents a delivery address change request made by the Prime that is either auto-approved or requires review if the pricing criteria has changed. If criteria has changed, then it must be approved or rejected by a TOO. // // swagger:model ShipmentAddressUpdate type ShipmentAddressUpdate struct { @@ -38,7 +38,7 @@ type ShipmentAddressUpdate struct { // Required: true NewAddress *Address `json:"newAddress"` - // The distance between the original SIT address and requested new destination address of shipment + // The distance between the original SIT address and requested new delivery address of shipment // Example: 88 // Minimum: 0 NewSitDistanceBetween *int64 `json:"newSitDistanceBetween,omitempty"` @@ -49,7 +49,7 @@ type ShipmentAddressUpdate struct { // Example: This is an office remark OfficeRemarks *string `json:"officeRemarks,omitempty"` - // The distance between the original SIT address and the previous/old destination address of shipment + // The distance between the original SIT address and the previous/old delivery address of shipment // Example: 50 // Minimum: 0 OldSitDistanceBetween *int64 `json:"oldSitDistanceBetween,omitempty"` diff --git a/pkg/gen/primev3messages/update_p_p_m_shipment.go b/pkg/gen/primev3messages/update_p_p_m_shipment.go index ae462620c83..097575b0240 100644 --- a/pkg/gen/primev3messages/update_p_p_m_shipment.go +++ b/pkg/gen/primev3messages/update_p_p_m_shipment.go @@ -69,7 +69,7 @@ type UpdatePPMShipment struct { Address } `json:"secondaryDestinationAddress,omitempty"` - // An optional secondary pickup location near the origin where additional goods exist. + // An optional secondary Pickup Address near the origin where additional goods exist. // SecondaryPickupAddress struct { Address @@ -105,7 +105,7 @@ type UpdatePPMShipment struct { Address } `json:"tertiaryDestinationAddress,omitempty"` - // An optional third pickup location near the origin where additional goods exist. + // An optional third Pickup Address near the origin where additional goods exist. // TertiaryPickupAddress struct { Address diff --git a/pkg/gen/primev3messages/update_shipment_destination_address.go b/pkg/gen/primev3messages/update_shipment_destination_address.go index 97415bc99c9..c4d7dd58866 100644 --- a/pkg/gen/primev3messages/update_shipment_destination_address.go +++ b/pkg/gen/primev3messages/update_shipment_destination_address.go @@ -14,7 +14,7 @@ import ( "github.com/go-openapi/validate" ) -// UpdateShipmentDestinationAddress UpdateShipmentDestinationAddress contains the fields required for the prime to request an update for the destination address on an MTO Shipment. +// UpdateShipmentDestinationAddress UpdateShipmentDestinationAddress contains the fields required for the prime to request an update for the delivery address on an MTO Shipment. // // swagger:model UpdateShipmentDestinationAddress type UpdateShipmentDestinationAddress struct { diff --git a/pkg/gen/supportapi/embedded_spec.go b/pkg/gen/supportapi/embedded_spec.go index 70ea8c1dbbc..73ec4c2a19f 100644 --- a/pkg/gen/supportapi/embedded_spec.go +++ b/pkg/gen/supportapi/embedded_spec.go @@ -1526,14 +1526,15 @@ func init() { ] }, "MTOServiceItemModelType": { - "description": "Describes all model sub-types for a MTOServiceItem model.\n\nUsing this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DOFSIT, DOASIT - MTOServiceItemOriginSIT\n * DDFSIT, DDASIT - MTOServiceItemDestSIT\n * DOSHUT, DDSHUT - MTOServiceItemShuttle\n * DCRT, DUCRT - MTOServiceItemDomesticCrating\n\nThe documentation will then update with the supported fields.\n", + "description": "Describes all model sub-types for a MTOServiceItem model.\n\nUsing this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DOFSIT, DOASIT - MTOServiceItemOriginSIT\n * DDFSIT, DDASIT - MTOServiceItemDestSIT\n * DOSHUT, DDSHUT - MTOServiceItemShuttle\n * DCRT, DUCRT - MTOServiceItemDomesticCrating\n * ICRT, IUCRT - MTOServiceItemInternationalCrating\n\nThe documentation will then update with the supported fields.\n", "type": "string", "enum": [ "MTOServiceItemBasic", "MTOServiceItemOriginSIT", "MTOServiceItemDestSIT", "MTOServiceItemShuttle", - "MTOServiceItemDomesticCrating" + "MTOServiceItemDomesticCrating", + "MTOServiceItemInternationalCrating" ] }, "MTOServiceItemOriginSIT": { @@ -2301,7 +2302,6 @@ func init() { "ICOLH", "ICOUB", "ICRT", - "ICRTSA", "IDASIT", "IDDSIT", "IDFSIT", @@ -4383,14 +4383,15 @@ func init() { ] }, "MTOServiceItemModelType": { - "description": "Describes all model sub-types for a MTOServiceItem model.\n\nUsing this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DOFSIT, DOASIT - MTOServiceItemOriginSIT\n * DDFSIT, DDASIT - MTOServiceItemDestSIT\n * DOSHUT, DDSHUT - MTOServiceItemShuttle\n * DCRT, DUCRT - MTOServiceItemDomesticCrating\n\nThe documentation will then update with the supported fields.\n", + "description": "Describes all model sub-types for a MTOServiceItem model.\n\nUsing this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DOFSIT, DOASIT - MTOServiceItemOriginSIT\n * DDFSIT, DDASIT - MTOServiceItemDestSIT\n * DOSHUT, DDSHUT - MTOServiceItemShuttle\n * DCRT, DUCRT - MTOServiceItemDomesticCrating\n * ICRT, IUCRT - MTOServiceItemInternationalCrating\n\nThe documentation will then update with the supported fields.\n", "type": "string", "enum": [ "MTOServiceItemBasic", "MTOServiceItemOriginSIT", "MTOServiceItemDestSIT", "MTOServiceItemShuttle", - "MTOServiceItemDomesticCrating" + "MTOServiceItemDomesticCrating", + "MTOServiceItemInternationalCrating" ] }, "MTOServiceItemOriginSIT": { @@ -5158,7 +5159,6 @@ func init() { "ICOLH", "ICOUB", "ICRT", - "ICRTSA", "IDASIT", "IDDSIT", "IDFSIT", diff --git a/pkg/gen/supportmessages/m_t_o_service_item_model_type.go b/pkg/gen/supportmessages/m_t_o_service_item_model_type.go index 0739ec7e52e..9a949e919da 100644 --- a/pkg/gen/supportmessages/m_t_o_service_item_model_type.go +++ b/pkg/gen/supportmessages/m_t_o_service_item_model_type.go @@ -21,6 +21,7 @@ import ( // - DDFSIT, DDASIT - MTOServiceItemDestSIT // - DOSHUT, DDSHUT - MTOServiceItemShuttle // - DCRT, DUCRT - MTOServiceItemDomesticCrating +// - ICRT, IUCRT - MTOServiceItemInternationalCrating // // The documentation will then update with the supported fields. // @@ -52,6 +53,9 @@ const ( // MTOServiceItemModelTypeMTOServiceItemDomesticCrating captures enum value "MTOServiceItemDomesticCrating" MTOServiceItemModelTypeMTOServiceItemDomesticCrating MTOServiceItemModelType = "MTOServiceItemDomesticCrating" + + // MTOServiceItemModelTypeMTOServiceItemInternationalCrating captures enum value "MTOServiceItemInternationalCrating" + MTOServiceItemModelTypeMTOServiceItemInternationalCrating MTOServiceItemModelType = "MTOServiceItemInternationalCrating" ) // for schema @@ -59,7 +63,7 @@ var mTOServiceItemModelTypeEnum []interface{} func init() { var res []MTOServiceItemModelType - if err := json.Unmarshal([]byte(`["MTOServiceItemBasic","MTOServiceItemOriginSIT","MTOServiceItemDestSIT","MTOServiceItemShuttle","MTOServiceItemDomesticCrating"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["MTOServiceItemBasic","MTOServiceItemOriginSIT","MTOServiceItemDestSIT","MTOServiceItemShuttle","MTOServiceItemDomesticCrating","MTOServiceItemInternationalCrating"]`), &res); err != nil { panic(err) } for _, v := range res { diff --git a/pkg/gen/supportmessages/re_service_code.go b/pkg/gen/supportmessages/re_service_code.go index 2955c52d89b..ae293c133d2 100644 --- a/pkg/gen/supportmessages/re_service_code.go +++ b/pkg/gen/supportmessages/re_service_code.go @@ -114,9 +114,6 @@ const ( // ReServiceCodeICRT captures enum value "ICRT" ReServiceCodeICRT ReServiceCode = "ICRT" - // ReServiceCodeICRTSA captures enum value "ICRTSA" - ReServiceCodeICRTSA ReServiceCode = "ICRTSA" - // ReServiceCodeIDASIT captures enum value "IDASIT" ReServiceCodeIDASIT ReServiceCode = "IDASIT" @@ -186,7 +183,7 @@ var reServiceCodeEnum []interface{} func init() { var res []ReServiceCode - if err := json.Unmarshal([]byte(`["CS","DBHF","DBTF","DCRT","DDASIT","DDDSIT","DDFSIT","DDP","DDSHUT","DLH","DMHF","DNPK","DOASIT","DOFSIT","DOP","DOPSIT","DOSHUT","DPK","DSH","DUCRT","DUPK","FSC","IBHF","IBTF","ICOLH","ICOUB","ICRT","ICRTSA","IDASIT","IDDSIT","IDFSIT","IDSHUT","IHPK","IHUPK","INPK","IOASIT","IOCLH","IOCUB","IOFSIT","IOOLH","IOOUB","IOPSIT","IOSHUT","IUBPK","IUBUPK","IUCRT","MS","NSTH","NSTUB"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["CS","DBHF","DBTF","DCRT","DDASIT","DDDSIT","DDFSIT","DDP","DDSHUT","DLH","DMHF","DNPK","DOASIT","DOFSIT","DOP","DOPSIT","DOSHUT","DPK","DSH","DUCRT","DUPK","FSC","IBHF","IBTF","ICOLH","ICOUB","ICRT","IDASIT","IDDSIT","IDFSIT","IDSHUT","IHPK","IHUPK","INPK","IOASIT","IOCLH","IOCUB","IOFSIT","IOOLH","IOOUB","IOPSIT","IOSHUT","IUBPK","IUBUPK","IUCRT","MS","NSTH","NSTUB"]`), &res); err != nil { panic(err) } for _, v := range res { diff --git a/pkg/handlers/authentication/auth.go b/pkg/handlers/authentication/auth.go index 226cbe88e1a..a01f499de5e 100644 --- a/pkg/handlers/authentication/auth.go +++ b/pkg/handlers/authentication/auth.go @@ -48,11 +48,12 @@ func (ar AuthorizationResult) String() string { } // IsLoggedInMiddleware handles requests to is_logged_in endpoint by returning true if someone is logged in -func IsLoggedInMiddleware(_ *zap.Logger) http.HandlerFunc { +func IsLoggedInMiddleware(_ *zap.Logger, maintenanceFlag bool) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { logger := logging.FromContext(r.Context()) data := map[string]interface{}{ - "isLoggedIn": false, + "isLoggedIn": false, + "underMaintenance": maintenanceFlag, } session := auth.SessionFromRequestContext(r) diff --git a/pkg/handlers/authentication/auth_test.go b/pkg/handlers/authentication/auth_test.go index 6b1614273c4..759c393cb60 100644 --- a/pkg/handlers/authentication/auth_test.go +++ b/pkg/handlers/authentication/auth_test.go @@ -533,7 +533,7 @@ func (suite *AuthSuite) TestIsLoggedInWhenNoUserLoggedIn() { rr := httptest.NewRecorder() sessionManager := scs.New() - handler := sessionManager.LoadAndSave(IsLoggedInMiddleware(suite.Logger())) + handler := sessionManager.LoadAndSave(IsLoggedInMiddleware(suite.Logger(), false)) handler.ServeHTTP(rr, req) @@ -541,7 +541,24 @@ func (suite *AuthSuite) TestIsLoggedInWhenNoUserLoggedIn() { suite.Equal(http.StatusOK, rr.Code, "handler returned the wrong status code") // expects to return that no one is logged in - expected := "{\"isLoggedIn\":false}\n" + expected := "{\"isLoggedIn\":false,\"underMaintenance\":false}\n" + suite.Equal(expected, rr.Body.String(), "handler returned wrong body") +} + +func (suite *AuthSuite) TestUnderMaintenanceFlag() { + req := httptest.NewRequest("GET", "/is_logged_in", nil) + + rr := httptest.NewRecorder() + sessionManager := scs.New() + handler := sessionManager.LoadAndSave(IsLoggedInMiddleware(suite.Logger(), true)) + + handler.ServeHTTP(rr, req) + + // expects to return 200 OK + suite.Equal(http.StatusOK, rr.Code, "handler returned the wrong status code") + + // expects to return that no one is logged in + expected := "{\"isLoggedIn\":false,\"underMaintenance\":true}\n" suite.Equal(expected, rr.Body.String(), "handler returned wrong body") } @@ -563,7 +580,7 @@ func (suite *AuthSuite) TestIsLoggedInWhenUserLoggedIn() { req = req.WithContext(ctx) rr := httptest.NewRecorder() - handler := sessionManager.LoadAndSave(IsLoggedInMiddleware(suite.Logger())) + handler := sessionManager.LoadAndSave(IsLoggedInMiddleware(suite.Logger(), false)) handler.ServeHTTP(rr, req) @@ -571,7 +588,7 @@ func (suite *AuthSuite) TestIsLoggedInWhenUserLoggedIn() { suite.Equal(http.StatusOK, rr.Code, "handler returned the wrong status code") // expects to return that no one is logged in - expected := "{\"isLoggedIn\":true}\n" + expected := "{\"isLoggedIn\":true,\"underMaintenance\":false}\n" suite.Equal(expected, rr.Body.String(), "handler returned wrong body") } diff --git a/pkg/handlers/ghcapi/api.go b/pkg/handlers/ghcapi/api.go index b76e8c4b0ca..6c6a50aa10e 100644 --- a/pkg/handlers/ghcapi/api.go +++ b/pkg/handlers/ghcapi/api.go @@ -471,6 +471,7 @@ func NewGhcAPIHandler(handlerConfig handlers.HandlerConfig) *ghcops.MymoveAPI { ghcAPI.MoveSearchMovesHandler = SearchMovesHandler{ HandlerConfig: handlerConfig, MoveSearcher: move.NewMoveSearcher(), + MoveUnlocker: movelocker.NewMoveUnlocker(), } ghcAPI.MtoShipmentUpdateMTOShipmentHandler = UpdateShipmentHandler{ @@ -701,5 +702,10 @@ func NewGhcAPIHandler(handlerConfig handlers.HandlerConfig) *ghcops.MymoveAPI { assignedOfficeUserUpdater, } + ghcAPI.MoveCheckForLockedMovesAndUnlockHandler = CheckForLockedMovesAndUnlockHandler{ + HandlerConfig: handlerConfig, + MoveUnlocker: movelocker.NewMoveUnlocker(), + } + return ghcAPI } diff --git a/pkg/handlers/ghcapi/customer.go b/pkg/handlers/ghcapi/customer.go index 372605d68bb..9ae913e4522 100644 --- a/pkg/handlers/ghcapi/customer.go +++ b/pkg/handlers/ghcapi/customer.go @@ -77,7 +77,7 @@ func (h SearchCustomersHandler) Handle(params customercodeop.SearchCustomersPara return h.AuditableAppContextFromRequestWithErrors(params.HTTPRequest, func(appCtx appcontext.AppContext) (middleware.Responder, error) { searchCustomersParams := services.SearchCustomersParams{ - DodID: params.Body.DodID, + Edipi: params.Body.Edipi, CustomerName: params.Body.CustomerName, Page: params.Body.Page, PerPage: params.Body.PerPage, diff --git a/pkg/handlers/ghcapi/customer_test.go b/pkg/handlers/ghcapi/customer_test.go index 3a6a3305172..5f8bf0cf6a6 100644 --- a/pkg/handlers/ghcapi/customer_test.go +++ b/pkg/handlers/ghcapi/customer_test.go @@ -398,7 +398,7 @@ func (suite *HandlerSuite) TestSearchCustomersHandler() { mockSearcher.On("SearchCustomers", mock.AnythingOfType("*appcontext.appContext"), mock.MatchedBy(func(params *services.SearchCustomersParams) bool { - return *params.DodID == *customer.Edipi && + return *params.Edipi == *customer.Edipi && params.CustomerName == nil }), ).Return(customers, 1, nil) @@ -406,7 +406,7 @@ func (suite *HandlerSuite) TestSearchCustomersHandler() { params := customerops.SearchCustomersParams{ HTTPRequest: req, Body: customerops.SearchCustomersBody{ - DodID: customer.Edipi, + Edipi: customer.Edipi, }, } @@ -434,7 +434,7 @@ func (suite *HandlerSuite) TestSearchCustomersHandler() { mock.AnythingOfType("*appcontext.appContext"), mock.MatchedBy(func(params *services.SearchCustomersParams) bool { return *params.CustomerName == *customer.FirstName && - params.DodID == nil + params.Edipi == nil }), ).Return(customers, 1, nil) diff --git a/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go b/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go index 6717b7d15d7..992322feecd 100644 --- a/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go +++ b/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go @@ -1835,8 +1835,7 @@ func MTOServiceItemModel(s *models.MTOServiceItem, storer storage.FileStorer) *g serviceRequestDocs[i] = payload } } - - return &ghcmessages.MTOServiceItem{ + payload := &ghcmessages.MTOServiceItem{ ID: handlers.FmtUUID(s.ID), MoveTaskOrderID: handlers.FmtUUID(s.MoveTaskOrderID), MtoShipmentID: handlers.FmtUUIDPtr(s.MTOShipmentID), @@ -1870,8 +1869,27 @@ func MTOServiceItemModel(s *models.MTOServiceItem, storer storage.FileStorer) *g SitDeliveryMiles: handlers.FmtIntPtrToInt64(s.SITDeliveryMiles), EstimatedPrice: handlers.FmtCost(s.PricingEstimate), StandaloneCrate: s.StandaloneCrate, + ExternalCrate: s.ExternalCrate, LockedPriceCents: handlers.FmtCost(s.LockedPriceCents), } + + if s.ReService.Code == models.ReServiceCodeICRT && s.MTOShipment.PickupAddress != nil { + if *s.MTOShipment.PickupAddress.IsOconus { + payload.Market = handlers.FmtString(models.MarketOconus.FullString()) + } else { + payload.Market = handlers.FmtString(models.MarketConus.FullString()) + } + } + + if s.ReService.Code == models.ReServiceCodeIUCRT && s.MTOShipment.DestinationAddress != nil { + if *s.MTOShipment.DestinationAddress.IsOconus { + payload.Market = handlers.FmtString(models.MarketOconus.FullString()) + } else { + payload.Market = handlers.FmtString(models.MarketConus.FullString()) + } + } + + return payload } // SITServiceItemGrouping payload @@ -2116,6 +2134,75 @@ func QueueAvailableOfficeUsers(officeUsers []models.OfficeUser) *ghcmessages.Ava return &availableOfficeUsers } +func queueMoveIsAssignable(move models.Move, assignedToUser *ghcmessages.AssignedOfficeUser, isCloseoutQueue bool, role roles.RoleType, officeUser models.OfficeUser, isSupervisor bool, isHQRole bool, ppmCloseoutGblocs bool) bool { + // default to false + isAssignable := false + + // HQ role is read only + if isHQRole { + isAssignable = false + return isAssignable + } + + // if its unassigned its assignable in all cases + if assignedToUser == nil { + isAssignable = true + } + + // in TOO queues, all moves are assignable for supervisor users + if role == roles.RoleTypeTOO && isSupervisor { + isAssignable = true + } + + // if it is assigned in the SCs queue + // it is only assignable if the user is a supervisor... + if role == roles.RoleTypeServicesCounselor && isSupervisor { + // AND we are in the counseling queue AND the move's counseling office is the supervisor's transportation office + if !isCloseoutQueue && move.CounselingOfficeID != nil && *move.CounselingOfficeID == officeUser.TransportationOfficeID { + isAssignable = true + } + // OR we are in the closeout queue AND the move's closeout office is the supervisor's transportation office + if isCloseoutQueue && move.CloseoutOfficeID != nil && *move.CloseoutOfficeID == officeUser.TransportationOfficeID { + isAssignable = true + } + + // OR theyre a navy, marine, or coast guard supervisor + if ppmCloseoutGblocs { + isAssignable = true + } + } + + return isAssignable +} + +func servicesCounselorAvailableOfficeUsers(move models.Move, officeUsers []models.OfficeUser, role roles.RoleType, officeUser models.OfficeUser, ppmCloseoutGblocs bool, isCloseoutQueue bool) []models.OfficeUser { + if role == roles.RoleTypeServicesCounselor { + // if the office user currently assigned to the move works outside of the logged in users counseling office + // add them to the set + if move.SCAssignedUser != nil && move.SCAssignedUser.TransportationOfficeID != officeUser.TransportationOfficeID { + officeUsers = append(officeUsers, *move.SCAssignedUser) + } + + // if there is no counseling office + // OR if our current user doesn't work at the move's counseling office + // only available user should be themself + if !isCloseoutQueue && (move.CounselingOfficeID == nil) || (move.CounselingOfficeID != nil && *move.CounselingOfficeID != officeUser.TransportationOfficeID) { + officeUsers = models.OfficeUsers{officeUser} + } + + // if its the closeout queue and its not a Navy, Marine, or Coast Guard user + // and the move doesn't have a closeout office + // OR the move's closeout office is not the office users office + // only available user should be themself + if isCloseoutQueue && !ppmCloseoutGblocs && move.CloseoutOfficeID == nil || (move.CloseoutOfficeID != nil && *move.CloseoutOfficeID != officeUser.TransportationOfficeID) { + officeUsers = models.OfficeUsers{officeUser} + + } + } + + return officeUsers +} + // QueueMoves payload func QueueMoves(moves []models.Move, officeUsers []models.OfficeUser, requestedPpmStatus *models.PPMShipmentStatus, role roles.RoleType, officeUser models.OfficeUser, isSupervisor bool, isHQRole bool) *ghcmessages.QueueMoves { queueMoves := make(ghcmessages.QueueMoves, len(moves)) @@ -2184,6 +2271,37 @@ func QueueMoves(moves []models.Move, officeUsers []models.OfficeUser, requestedP } } + // queue assignment logic below + + // determine if there is an assigned user + var assignedToUser *ghcmessages.AssignedOfficeUser + if role == roles.RoleTypeServicesCounselor && move.SCAssignedUser != nil { + assignedToUser = AssignedOfficeUser(move.SCAssignedUser) + } + if role == roles.RoleTypeTOO && move.TOOAssignedUser != nil { + assignedToUser = AssignedOfficeUser(move.TOOAssignedUser) + } + + // these branches have their own closeout specific offices + ppmCloseoutGblocs := closeoutLocation == "NAVY" || closeoutLocation == "TVCB" || closeoutLocation == "USCG" + // requestedPpmStatus also represents if we are viewing the closeout queue + isCloseoutQueue := requestedPpmStatus != nil && *requestedPpmStatus == models.PPMShipmentStatusNeedsCloseout + // determine if the move is assignable + assignable := queueMoveIsAssignable(move, assignedToUser, isCloseoutQueue, role, officeUser, isSupervisor, isHQRole, ppmCloseoutGblocs) + + // only need to attach available office users if move is assignable + var apiAvailableOfficeUsers ghcmessages.AvailableOfficeUsers + if assignable { + // non SC roles don't need the extra logic, just make availableOfficeUsers = officeUsers + availableOfficeUsers := officeUsers + + if role == roles.RoleTypeServicesCounselor { + availableOfficeUsers = servicesCounselorAvailableOfficeUsers(move, availableOfficeUsers, role, officeUser, ppmCloseoutGblocs, isCloseoutQueue) + } + + apiAvailableOfficeUsers = *QueueAvailableOfficeUsers(availableOfficeUsers) + } + queueMoves[i] = &ghcmessages.QueueMove{ Customer: Customer(&customer), Status: ghcmessages.MoveStatus(move.Status), @@ -2207,59 +2325,9 @@ func QueueMoves(moves []models.Move, officeUsers []models.OfficeUser, requestedP PpmStatus: ghcmessages.PPMStatus(ppmStatus), CounselingOffice: &transportationOffice, CounselingOfficeID: handlers.FmtUUID(transportationOfficeId), - } - - if role == roles.RoleTypeServicesCounselor && move.SCAssignedUser != nil { - queueMoves[i].AssignedTo = AssignedOfficeUser(move.SCAssignedUser) - } - if role == roles.RoleTypeTOO && move.TOOAssignedUser != nil { - queueMoves[i].AssignedTo = AssignedOfficeUser(move.TOOAssignedUser) - } - - // scenarios where a move is assinable: - - // if it is unassigned, it is always assignable - isAssignable := false - if queueMoves[i].AssignedTo == nil { - isAssignable = true - } - - // in TOO queues, all moves are assignable for supervisor users - if role == roles.RoleTypeTOO && isSupervisor { - isAssignable = true - } - - // if it is assigned in the SCs queue - // it is only assignable if the user is a supervisor - // and if the move's counseling office is the supervisor's transportation office - if role == roles.RoleTypeServicesCounselor && isSupervisor && move.CounselingOfficeID != nil && *move.CounselingOfficeID == officeUser.TransportationOfficeID { - isAssignable = true - } - - if isHQRole { - isAssignable = false - } - - queueMoves[i].Assignable = isAssignable - - // only need to attach available office users if move is assignable - if queueMoves[i].Assignable { - availableOfficeUsers := officeUsers - if role == roles.RoleTypeServicesCounselor { - // if there is no counseling office - // OR if our current user doesn't work at the move's counseling office - // only available user should be themself - if (move.CounselingOfficeID == nil) || (move.CounselingOfficeID != nil && *move.CounselingOfficeID != officeUser.TransportationOfficeID) { - availableOfficeUsers = models.OfficeUsers{officeUser} - } - - // if the office user currently assigned to move works outside of the logged in users counseling office - // add them to the set - if move.SCAssignedUser != nil && move.SCAssignedUser.TransportationOfficeID != officeUser.TransportationOfficeID { - availableOfficeUsers = append(availableOfficeUsers, *move.SCAssignedUser) - } - } - queueMoves[i].AvailableOfficeUsers = *QueueAvailableOfficeUsers(availableOfficeUsers) + AssignedTo: assignedToUser, + Assignable: assignable, + AvailableOfficeUsers: apiAvailableOfficeUsers, } } return &queueMoves @@ -2359,6 +2427,10 @@ func QueuePaymentRequests(paymentRequests *models.PaymentRequests, officeUsers [ queuePaymentRequests[i].AssignedTo = AssignedOfficeUser(paymentRequest.MoveTaskOrder.TIOAssignedUser) } + if paymentRequest.MoveTaskOrder.CounselingOffice != nil { + queuePaymentRequests[i].CounselingOffice = &paymentRequest.MoveTaskOrder.CounselingOffice.Name + } + isAssignable := false if queuePaymentRequests[i].AssignedTo == nil { isAssignable = true @@ -2475,7 +2547,7 @@ func SearchMoves(appCtx appcontext.AppContext, moves models.Moves) *ghcmessages. searchMoves[i] = &ghcmessages.SearchMove{ FirstName: customer.FirstName, LastName: customer.LastName, - DodID: customer.Edipi, + Edipi: customer.Edipi, Emplid: customer.Emplid, Branch: customer.Affiliation.String(), Status: ghcmessages.MoveStatus(move.Status), @@ -2538,7 +2610,7 @@ func SearchCustomers(customers models.ServiceMemberSearchResults) *ghcmessages.S searchCustomers[i] = &ghcmessages.SearchCustomer{ FirstName: customer.FirstName, LastName: customer.LastName, - DodID: customer.Edipi, + Edipi: customer.Edipi, Emplid: customer.Emplid, Branch: customer.Affiliation.String(), ID: *handlers.FmtUUID(customer.ID), diff --git a/pkg/handlers/ghcapi/internal/payloads/model_to_payload_test.go b/pkg/handlers/ghcapi/internal/payloads/model_to_payload_test.go index 84204a80dd1..c779256f33e 100644 --- a/pkg/handlers/ghcapi/internal/payloads/model_to_payload_test.go +++ b/pkg/handlers/ghcapi/internal/payloads/model_to_payload_test.go @@ -11,6 +11,7 @@ import ( "github.com/transcom/mymove/pkg/gen/ghcmessages" "github.com/transcom/mymove/pkg/handlers" "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/models/roles" "github.com/transcom/mymove/pkg/storage/test" ) @@ -27,6 +28,90 @@ func TestMove(t *testing.T) { } } +func (suite *PayloadsSuite) TestPaymentRequestQueue() { + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) + officeUserTIO := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTIO}) + + gbloc := "LKNQ" + + approvedMove := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) + approvedMove.ShipmentGBLOC = append(approvedMove.ShipmentGBLOC, models.MoveToGBLOC{GBLOC: &gbloc}) + + pr2 := factory.BuildPaymentRequest(suite.DB(), []factory.Customization{ + { + Model: approvedMove, + LinkOnly: true, + }, + { + Model: models.TransportationOffice{ + Gbloc: "LKNQ", + }, + Type: &factory.TransportationOffices.OriginDutyLocation, + }, + { + Model: models.DutyLocation{ + Name: "KJKJKJKJKJK", + }, + Type: &factory.DutyLocations.OriginDutyLocation, + }, + }, nil) + + paymentRequests := models.PaymentRequests{pr2} + transportationOffice := factory.BuildTransportationOffice(suite.DB(), []factory.Customization{ + { + Model: models.TransportationOffice{ + Name: "PPSO", + ProvidesCloseout: true, + }, + }, + }, nil) + var officeUsers models.OfficeUsers + officeUsers = append(officeUsers, officeUser) + var paymentRequestsQueue = QueuePaymentRequests(&paymentRequests, officeUsers, officeUser, false, false) + + suite.Run("Test Payment request is assignable due to not being assigend", func() { + paymentRequestCopy := *paymentRequestsQueue + suite.NotNil(paymentRequestsQueue) + suite.IsType(paymentRequestsQueue, &ghcmessages.QueuePaymentRequests{}) + suite.Nil(paymentRequestCopy[0].AssignedTo) + }) + + suite.Run("Test Payment request has no counseling office", func() { + paymentRequestCopy := *paymentRequestsQueue + suite.NotNil(paymentRequestsQueue) + suite.IsType(paymentRequestsQueue, &ghcmessages.QueuePaymentRequests{}) + suite.Nil(paymentRequestCopy[0].CounselingOffice) + }) + + paymentRequests[0].MoveTaskOrder.TIOAssignedUser = &officeUserTIO + paymentRequests[0].MoveTaskOrder.CounselingOffice = &transportationOffice + + paymentRequestsQueue = QueuePaymentRequests(&paymentRequests, officeUsers, officeUser, false, false) + + suite.Run("Test PaymentRequest has both Counseling Office and TIO AssignedUser ", func() { + PaymentRequestsCopy := *paymentRequestsQueue + + suite.NotNil(PaymentRequests) + suite.IsType(&ghcmessages.QueuePaymentRequests{}, paymentRequestsQueue) + suite.IsType(&ghcmessages.QueuePaymentRequest{}, PaymentRequestsCopy[0]) + suite.Equal(PaymentRequestsCopy[0].AssignedTo.FirstName, officeUserTIO.FirstName) + suite.Equal(PaymentRequestsCopy[0].AssignedTo.LastName, officeUserTIO.LastName) + suite.Equal(*PaymentRequestsCopy[0].CounselingOffice, transportationOffice.Name) + }) + + suite.Run("Test PaymentRequest is assignable due to user Supervisor role", func() { + paymentRequests := QueuePaymentRequests(&paymentRequests, officeUsers, officeUser, true, false) + paymentRequestCopy := *paymentRequests + suite.Equal(paymentRequestCopy[0].Assignable, true) + }) + + suite.Run("Test PaymentRequest is not assignable due to user HQ role", func() { + paymentRequests := QueuePaymentRequests(&paymentRequests, officeUsers, officeUser, false, true) + paymentRequestCopy := *paymentRequests + suite.Equal(paymentRequestCopy[0].Assignable, false) + }) +} + func (suite *PayloadsSuite) TestFetchPPMShipment() { ppmShipmentID, _ := uuid.NewV4() @@ -509,3 +594,167 @@ func (suite *PayloadsSuite) TestGsrAppeal() { suite.False(result.IsSeriousIncident, "Expected IsSeriousIncident to be false") }) } + +func (suite *PayloadsSuite) TestMTOServiceItemModel() { + suite.Run("returns nil when MTOServiceItem is nil", func() { + var serviceItem *models.MTOServiceItem = nil + result := MTOServiceItemModel(serviceItem, suite.storer) + suite.Nil(result, "Expected result to be nil when MTOServiceItem is nil") + }) + + suite.Run("successfully converts MTOServiceItem to payload", func() { + serviceID := uuid.Must(uuid.NewV4()) + moveID := uuid.Must(uuid.NewV4()) + shipID := uuid.Must(uuid.NewV4()) + reServiceID := uuid.Must(uuid.NewV4()) + now := time.Now() + + mockReService := models.ReService{ + ID: reServiceID, + Code: models.ReServiceCodeICRT, + Name: "Some ReService", + } + + mockPickupAddress := models.Address{ + ID: uuid.Must(uuid.NewV4()), + IsOconus: models.BoolPointer(false), + CreatedAt: now, + UpdatedAt: now, + } + + mockMTOShipment := models.MTOShipment{ + ID: shipID, + PickupAddress: &mockPickupAddress, + } + + mockServiceItem := models.MTOServiceItem{ + ID: serviceID, + MoveTaskOrderID: moveID, + MTOShipmentID: &shipID, + MTOShipment: mockMTOShipment, + ReServiceID: reServiceID, + ReService: mockReService, + CreatedAt: now, + UpdatedAt: now, + } + + result := MTOServiceItemModel(&mockServiceItem, suite.storer) + suite.NotNil(result, "Expected result to not be nil when MTOServiceItem is valid") + suite.Equal(handlers.FmtUUID(serviceID), result.ID, "Expected ID to match") + suite.Equal(handlers.FmtUUID(moveID), result.MoveTaskOrderID, "Expected MoveTaskOrderID to match") + suite.Equal(handlers.FmtUUIDPtr(&shipID), result.MtoShipmentID, "Expected MtoShipmentID to match") + suite.Equal(handlers.FmtString(models.MarketConus.FullString()), result.Market, "Expected Market to be CONUS") + }) + + suite.Run("sets Market to OCONUS when PickupAddress.IsOconus is true for ICRT", func() { + reServiceID := uuid.Must(uuid.NewV4()) + + mockReService := models.ReService{ + ID: reServiceID, + Code: models.ReServiceCodeICRT, + Name: "Test ReService", + } + + mockPickupAddress := models.Address{ + ID: uuid.Must(uuid.NewV4()), + IsOconus: models.BoolPointer(true), + } + + mockMTOShipment := models.MTOShipment{ + PickupAddress: &mockPickupAddress, + } + + mockServiceItem := models.MTOServiceItem{ + ReService: mockReService, + MTOShipment: mockMTOShipment, + } + + result := MTOServiceItemModel(&mockServiceItem, suite.storer) + suite.NotNil(result, "Expected result to not be nil for valid MTOServiceItem") + suite.Equal(handlers.FmtString(models.MarketOconus.FullString()), result.Market, "Expected Market to be OCONUS") + }) + + suite.Run("sets Market to CONUS when PickupAddress.IsOconus is false for ICRT", func() { + reServiceID := uuid.Must(uuid.NewV4()) + + mockReService := models.ReService{ + ID: reServiceID, + Code: models.ReServiceCodeICRT, + Name: "Test ReService", + } + + mockPickupAddress := models.Address{ + ID: uuid.Must(uuid.NewV4()), + IsOconus: models.BoolPointer(false), + } + + mockMTOShipment := models.MTOShipment{ + PickupAddress: &mockPickupAddress, + } + + mockServiceItem := models.MTOServiceItem{ + ReService: mockReService, + MTOShipment: mockMTOShipment, + } + + result := MTOServiceItemModel(&mockServiceItem, suite.storer) + suite.NotNil(result, "Expected result to not be nil for valid MTOServiceItem") + suite.Equal(handlers.FmtString(models.MarketConus.FullString()), result.Market, "Expected Market to be CONUS") + }) + + suite.Run("sets Market to CONUS when DestinationAddress.IsOconus is false for IUCRT", func() { + reServiceID := uuid.Must(uuid.NewV4()) + + mockReService := models.ReService{ + ID: reServiceID, + Code: models.ReServiceCodeIUCRT, + Name: "Test ReService", + } + + mockDestinationAddress := models.Address{ + ID: uuid.Must(uuid.NewV4()), + IsOconus: models.BoolPointer(false), + } + + mockMTOShipment := models.MTOShipment{ + DestinationAddress: &mockDestinationAddress, + } + + mockServiceItem := models.MTOServiceItem{ + ReService: mockReService, + MTOShipment: mockMTOShipment, + } + + result := MTOServiceItemModel(&mockServiceItem, suite.storer) + suite.NotNil(result, "Expected result to not be nil for valid MTOServiceItem") + suite.Equal(handlers.FmtString(models.MarketConus.FullString()), result.Market, "Expected Market to be CONUS") + }) + + suite.Run("sets Market to OCONUS when DestinationAddress.IsOconus is true for IUCRT", func() { + reServiceID := uuid.Must(uuid.NewV4()) + + mockReService := models.ReService{ + ID: reServiceID, + Code: models.ReServiceCodeIUCRT, + Name: "Test ReService", + } + + mockDestinationAddress := models.Address{ + ID: uuid.Must(uuid.NewV4()), + IsOconus: models.BoolPointer(true), + } + + mockMTOShipment := models.MTOShipment{ + DestinationAddress: &mockDestinationAddress, + } + + mockServiceItem := models.MTOServiceItem{ + ReService: mockReService, + MTOShipment: mockMTOShipment, + } + + result := MTOServiceItemModel(&mockServiceItem, suite.storer) + suite.NotNil(result, "Expected result to not be nil for valid MTOServiceItem") + suite.Equal(handlers.FmtString(models.MarketOconus.FullString()), result.Market, "Expected Market to be OCONUS") + }) +} diff --git a/pkg/handlers/ghcapi/move.go b/pkg/handlers/ghcapi/move.go index 25de4d094ed..aaf96dde91e 100644 --- a/pkg/handlers/ghcapi/move.go +++ b/pkg/handlers/ghcapi/move.go @@ -84,7 +84,8 @@ func (h GetMoveHandler) Handle(params moveop.GetMoveParams) middleware.Responder if moveOrders.OrdersType == "SAFETY" && !privileges.HasPrivilege(models.PrivilegeTypeSafety) { appCtx.Logger().Error("Invalid permissions") - return moveop.NewGetMoveNotFound(), nil + errMsg := "Page is inaccessible" + return moveop.NewGetMoveNotFound().WithPayload(&ghcmessages.Error{Message: &errMsg}), apperror.NewNotFoundError(uuid.Nil, "Page is inaccessible") } else { payload, err := payloads.Move(move, h.FileStorer()) if err != nil { @@ -98,6 +99,7 @@ func (h GetMoveHandler) Handle(params moveop.GetMoveParams) middleware.Responder type SearchMovesHandler struct { handlers.HandlerConfig services.MoveSearcher + services.MoveUnlocker } func (h SearchMovesHandler) Handle(params moveop.SearchMovesParams) middleware.Responder { @@ -106,7 +108,7 @@ func (h SearchMovesHandler) Handle(params moveop.SearchMovesParams) middleware.R searchMovesParams := services.SearchMovesParams{ Branch: params.Body.Branch, Locator: params.Body.Locator, - DodID: params.Body.DodID, + DodID: params.Body.Edipi, Emplid: params.Body.Emplid, CustomerName: params.Body.CustomerName, PaymentRequestCode: params.Body.PaymentRequestCode, @@ -128,6 +130,22 @@ func (h SearchMovesHandler) Handle(params moveop.SearchMovesParams) middleware.R appCtx.Logger().Error("Error searching for move", zap.Error(err)) return moveop.NewSearchMovesInternalServerError(), err } + + // if the search move office user is accessing the queue, we need to unlock move/moves they have locked + if appCtx.Session().IsOfficeUser() { + officeUserID := appCtx.Session().OfficeUserID + for i, move := range moves { + lockedOfficeUserID := move.LockedByOfficeUserID + if lockedOfficeUserID != nil && *lockedOfficeUserID == officeUserID { + copyOfMove := move + unlockedMove, err := h.UnlockMove(appCtx, ©OfMove, officeUserID) + if err != nil { + return moveop.NewSearchMovesInternalServerError(), err + } + moves[i] = *unlockedMove + } + } + } searchMoves := payloads.SearchMoves(appCtx, moves) payload := &ghcmessages.SearchMovesResult{ Page: searchMovesParams.Page, @@ -316,6 +334,27 @@ func (h MoveCancelerHandler) Handle(params moveop.MoveCancelerParams) middleware }) } +type CheckForLockedMovesAndUnlockHandler struct { + handlers.HandlerConfig + services.MoveUnlocker +} + +func (h CheckForLockedMovesAndUnlockHandler) Handle(params moveop.CheckForLockedMovesAndUnlockParams) middleware.Responder { + return h.AuditableAppContextFromRequestWithErrors(params.HTTPRequest, + func(appCtx appcontext.AppContext) (middleware.Responder, error) { + // if the search move office user is accessing the queue, we need to unlock move/moves they have locked + err := h.CheckForLockedMovesAndUnlock(appCtx, uuid.FromStringOrNil(params.OfficeUserID.String())) + if err != nil { + return moveop.NewCheckForLockedMovesAndUnlockInternalServerError(), err + } + var payload moveop.CheckForLockedMovesAndUnlockOK + payload.SetPayload(&moveop.CheckForLockedMovesAndUnlockOKBody{ + SuccessMessage: "Successfully unlocked all move(s) for current office user"}) + + return &payload, nil + }) +} + type DeleteAssignedOfficeUserHandler struct { handlers.HandlerConfig services.MoveAssignedOfficeUserUpdater diff --git a/pkg/handlers/ghcapi/move_test.go b/pkg/handlers/ghcapi/move_test.go index 999f5393c77..f345c7cfb85 100644 --- a/pkg/handlers/ghcapi/move_test.go +++ b/pkg/handlers/ghcapi/move_test.go @@ -7,9 +7,11 @@ import ( "time" "github.com/go-openapi/strfmt" + "github.com/gofrs/uuid" "github.com/stretchr/testify/mock" "github.com/transcom/mymove/pkg/apperror" + "github.com/transcom/mymove/pkg/auth" "github.com/transcom/mymove/pkg/etag" "github.com/transcom/mymove/pkg/factory" moveops "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/move" @@ -295,9 +297,11 @@ func (suite *HandlerSuite) TestSearchMovesHandler() { mockSearcher := mocks.MoveSearcher{} + mockUnlocker := movelocker.NewMoveUnlocker() handler := SearchMovesHandler{ HandlerConfig: suite.HandlerConfig(), MoveSearcher: &mockSearcher, + MoveUnlocker: mockUnlocker, } mockSearcher.On("SearchMoves", mock.AnythingOfType("*appcontext.appContext"), @@ -310,7 +314,7 @@ func (suite *HandlerSuite) TestSearchMovesHandler() { HTTPRequest: req, Body: moveops.SearchMovesBody{ Locator: &move.Locator, - DodID: nil, + Edipi: nil, }, } @@ -326,7 +330,7 @@ func (suite *HandlerSuite) TestSearchMovesHandler() { payloadMove := *(*payload).SearchMoves[0] suite.Equal(move.ID.String(), payloadMove.ID.String()) - suite.Equal(*move.Orders.ServiceMember.Edipi, *payloadMove.DodID) + suite.Equal(*move.Orders.ServiceMember.Edipi, *payloadMove.Edipi) suite.Equal(move.Orders.NewDutyLocation.Address.PostalCode, payloadMove.DestinationDutyLocationPostalCode) suite.Equal(move.Orders.OriginDutyLocation.Address.PostalCode, payloadMove.OriginDutyLocationPostalCode) suite.Equal(ghcmessages.MoveStatusDRAFT, payloadMove.Status) @@ -363,7 +367,7 @@ func (suite *HandlerSuite) TestSearchMovesHandler() { HTTPRequest: req, Body: moveops.SearchMovesBody{ Locator: nil, - DodID: move.Orders.ServiceMember.Edipi, + Edipi: move.Orders.ServiceMember.Edipi, }, } @@ -882,3 +886,85 @@ func (suite *HandlerSuite) TestUpdateAssignedOfficeUserHandler() { suite.Nil(payload.TIOAssignedUser) }) } + +func (suite *HandlerSuite) TestCheckForLockedMovesAndUnlockHandler() { + var validOfficeUser models.OfficeUser + var move models.Move + + mockLocker := movelocker.NewMoveLocker() + setupLockedMove := func() { + appCtx := suite.AppContextWithSessionForTest(&auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: validOfficeUser.User.Roles, + OfficeUserID: validOfficeUser.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + UserID: validOfficeUser.ID, + }) + + validOfficeUser = factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) + move = factory.BuildMove(suite.DB(), []factory.Customization{ + { + Model: models.OfficeUser{ + ID: validOfficeUser.ID, + }, + }, + }, nil) + + move, err := mockLocker.LockMove(appCtx, &move, validOfficeUser.ID) + + suite.NoError(err) + suite.NotNil(move.LockedByOfficeUserID) + } + + setupTestData := func() (*http.Request, CheckForLockedMovesAndUnlockHandler) { + req := httptest.NewRequest("GET", "/moves/{officeUserID}/CheckForLockedMovesAndUnlock", nil) + + handler := CheckForLockedMovesAndUnlockHandler{ + HandlerConfig: suite.HandlerConfig(), + MoveUnlocker: movelocker.NewMoveUnlocker(), + } + + return req, handler + } + suite.PreloadData(setupLockedMove) + + suite.Run("Successful unlocking of move", func() { + req, handler := setupTestData() + + expectedPayloadMessage := "Successfully unlocked all move(s) for current office user" + + officeUserID := strfmt.UUID(validOfficeUser.ID.String()) + params := moveops.CheckForLockedMovesAndUnlockParams{ + HTTPRequest: req, + OfficeUserID: officeUserID, + } + + handler.Handle(params) + suite.NotNil(move) + + response := handler.Handle(params) + suite.IsType(&moveops.CheckForLockedMovesAndUnlockOK{}, response) + payload := response.(*moveops.CheckForLockedMovesAndUnlockOK).Payload + suite.NoError(payload.Validate(strfmt.Default)) + + actualMessage := payload.SuccessMessage + suite.Equal(expectedPayloadMessage, actualMessage) + }) + + suite.Run("Unsucceful unlocking of move - nil officerUserId", func() { + req, handler := setupTestData() + + invalidOfficeUserID := strfmt.UUID(uuid.Nil.String()) + params := moveops.CheckForLockedMovesAndUnlockParams{ + HTTPRequest: req, + OfficeUserID: invalidOfficeUserID, + } + + handler.Handle(params) + response := handler.Handle(params) + suite.IsType(&moveops.CheckForLockedMovesAndUnlockInternalServerError{}, response) + payload := response.(*moveops.CheckForLockedMovesAndUnlockInternalServerError).Payload + suite.Nil(payload) + }) +} diff --git a/pkg/handlers/ghcapi/mto_service_items.go b/pkg/handlers/ghcapi/mto_service_items.go index 33b34ae1373..60b8db84c04 100644 --- a/pkg/handlers/ghcapi/mto_service_items.go +++ b/pkg/handlers/ghcapi/mto_service_items.go @@ -371,6 +371,12 @@ func (h ListMTOServiceItemsHandler) Handle(params mtoserviceitemop.ListMTOServic if loadErr != nil { return mtoserviceitemop.NewListMTOServiceItemsInternalServerError(), loadErr } + } else if serviceItem.ReService.Code == models.ReServiceCodeICRT || // use address.isOconus to get 'market' value for intl crating + serviceItem.ReService.Code == models.ReServiceCodeIUCRT { + loadErr := appCtx.DB().Load(&serviceItems[i], "MTOShipment.PickupAddress", "MTOShipment.DestinationAddress") + if loadErr != nil { + return mtoserviceitemop.NewListMTOServiceItemsInternalServerError(), loadErr + } } } diff --git a/pkg/handlers/ghcapi/mto_service_items_test.go b/pkg/handlers/ghcapi/mto_service_items_test.go index 9b6ae2bcf2f..16bd37da126 100644 --- a/pkg/handlers/ghcapi/mto_service_items_test.go +++ b/pkg/handlers/ghcapi/mto_service_items_test.go @@ -842,3 +842,187 @@ func (suite *HandlerSuite) TestUpdateServiceItemSitEntryDateHandler() { suite.IsType(payload, &ghcmessages.ValidationError{}) }) } + +func (suite *HandlerSuite) TestListMTOServiceItemsHandlerWithICRTandIUCRT() { + reServiceID, _ := uuid.NewV4() + serviceItemID, _ := uuid.NewV4() + serviceItemID2, _ := uuid.NewV4() + mtoShipmentID, _ := uuid.NewV4() + var mtoID uuid.UUID + + setupTestData := func() (models.User, models.MTOServiceItems) { + mto := factory.BuildMove(suite.DB(), nil, nil) + mtoID = mto.ID + reServiceICRT := factory.FetchReService(suite.DB(), []factory.Customization{ + { + Model: models.ReService{ + ID: reServiceID, + Code: models.ReServiceCodeICRT, + }, + }, + }, nil) + reServiceIUCRT := factory.FetchReService(suite.DB(), []factory.Customization{ + { + Model: models.ReService{ + ID: reServiceID, + Code: models.ReServiceCodeIUCRT, + }, + }, + }, nil) + mtoShipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ID: mtoShipmentID}, + }, + }, nil) + requestUser := factory.BuildUser(nil, nil, nil) + serviceItem := factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: models.MTOServiceItem{ + ID: serviceItemID, + }, + }, + { + Model: mto, + LinkOnly: true, + }, + { + Model: reServiceICRT, + LinkOnly: true, + }, + { + Model: mtoShipment, + LinkOnly: true, + }, + }, nil) + serviceItem2 := factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: models.MTOServiceItem{ + ID: serviceItemID2, + }, + }, + { + Model: mto, + LinkOnly: true, + }, + { + Model: reServiceIUCRT, + LinkOnly: true, + }, + { + Model: mtoShipment, + LinkOnly: true, + }, + }, nil) + + serviceItems := models.MTOServiceItems{serviceItem, serviceItem2} + + return requestUser, serviceItems + } + + suite.Run("200 - successfully loads PickupAddress and DestinationAddress for intl crating", func() { + requestUser, serviceItem := setupTestData() + req := httptest.NewRequest("GET", fmt.Sprintf("/move_task_orders/%s/mto_service_items", mtoID.String()), nil) + req = suite.AuthenticateUserRequest(req, requestUser) + + params := mtoserviceitemop.ListMTOServiceItemsParams{ + HTTPRequest: req, + MoveTaskOrderID: *handlers.FmtUUID(serviceItem[0].MoveTaskOrderID), + } + + // Create the addresses + pickupAddress := factory.BuildAddress(suite.DB(), nil, nil) + destinationAddress := factory.BuildAddress(suite.DB(), nil, nil) + + // Create the MTOShipment with populated PickupAddress and DestinationAddress + mtoShipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + PickupAddressID: &pickupAddress.ID, + DestinationAddressID: &destinationAddress.ID, + }, + }, + }, nil) + + // Create service items with references to the MTOShipment + serviceItems := models.MTOServiceItems{ + { + ID: uuid.Must(uuid.NewV4()), + ReService: models.ReService{Code: models.ReServiceCodeICRT}, + MTOShipmentID: &mtoShipment.ID, + MTOShipment: mtoShipment, + }, + { + ID: uuid.Must(uuid.NewV4()), + ReService: models.ReService{Code: models.ReServiceCodeIUCRT}, + MTOShipmentID: &mtoShipment.ID, + MTOShipment: mtoShipment, + }, + } + + // Mock Load function for PickupAddress and DestinationAddress + mockLoad := func(item interface{}, associations ...string) error { + mtoServiceItem, ok := item.(*models.MTOServiceItem) + if !ok { + return fmt.Errorf("unexpected type for item: %T", item) + } + if len(associations) == 2 && associations[0] == "MTOShipment.PickupAddress" && associations[1] == "MTOShipment.DestinationAddress" { + mtoServiceItem.MTOShipment.PickupAddress = &pickupAddress + mtoServiceItem.MTOShipment.DestinationAddress = &destinationAddress + return nil + } + return fmt.Errorf("unexpected association: %v", associations) + } + + // Inject mockLoad behavior + for i := range serviceItems { + if serviceItems[i].ReService.Code == models.ReServiceCodeICRT || serviceItems[i].ReService.Code == models.ReServiceCodeIUCRT { + err := mockLoad(&serviceItems[i], "MTOShipment.PickupAddress", "MTOShipment.DestinationAddress") + suite.NoError(err, "Expected no error when loading Pickup and Destination Addresses for ICRT/IUCRT codes") + } + } + + // Mock ListFetcher + listFetcherMock := mocks.ListFetcher{} + listFetcherMock.On("FetchRecordList", + mock.AnythingOfType("*appcontext.appContext"), + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything, + ).Run(func(args mock.Arguments) { + arg := args.Get(1).(*models.MTOServiceItems) + *arg = serviceItems + }).Return(nil) + + queryBuilder := query.NewQueryBuilder() + listFetcher := fetch.NewListFetcher(queryBuilder) + fetcher := fetch.NewFetcher(queryBuilder) + counselingPricer := ghcrateengine.NewCounselingServicesPricer() + moveManagementPricer := ghcrateengine.NewManagementServicesPricer() + + // Configure the handler with mocks + handler := ListMTOServiceItemsHandler{ + suite.createS3HandlerConfig(), + listFetcher, + fetcher, + counselingPricer, + moveManagementPricer, + } + + // Run the handler + response := handler.Handle(params) + suite.IsType(&mtoserviceitemop.ListMTOServiceItemsOK{}, response) + okResponse := response.(*mtoserviceitemop.ListMTOServiceItemsOK) + + // Validate the response + suite.Len(okResponse.Payload, len(serviceItems)) + for _, payload := range okResponse.Payload { + if *payload.ReServiceCode == string(models.ReServiceCodeICRT) { + suite.NotNil(payload.Market, "Expected Market to be set for ICRT") + } else if *payload.ReServiceCode == string(models.ReServiceCodeIUCRT) { + suite.NotNil(payload.Market, "Expected Market to be set for IUCRT") + } + } + }) +} diff --git a/pkg/handlers/ghcapi/mto_shipment_test.go b/pkg/handlers/ghcapi/mto_shipment_test.go index 083cf93475e..60974341ee3 100644 --- a/pkg/handlers/ghcapi/mto_shipment_test.go +++ b/pkg/handlers/ghcapi/mto_shipment_test.go @@ -3593,6 +3593,12 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandlerUsingPPM() { mock.AnythingOfType("*models.PPMShipment")). Return(models.CentPointer(unit.Cents(estimatedIncentive)), models.CentPointer(unit.Cents(sitEstimatedCost)), nil).Once() + ppmEstimator.On("MaxIncentive", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("models.PPMShipment"), + mock.AnythingOfType("*models.PPMShipment")). + Return(nil, nil) + // Validate incoming payload suite.NoError(params.Body.Validate(strfmt.Default)) @@ -3747,6 +3753,12 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandlerUsingPPM() { mock.AnythingOfType("*models.PPMShipment")). Return(models.CentPointer(unit.Cents(estimatedIncentive)), nil, nil).Once() + ppmEstimator.On("MaxIncentive", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("models.PPMShipment"), + mock.AnythingOfType("*models.PPMShipment")). + Return(nil, nil) + // Validate incoming payload suite.NoError(params.Body.Validate(strfmt.Default)) @@ -3891,6 +3903,12 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandlerUsingPPM() { mock.AnythingOfType("*models.PPMShipment")). Return(models.CentPointer(unit.Cents(estimatedIncentive)), nil, nil).Once() + ppmEstimator.On("MaxIncentive", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("models.PPMShipment"), + mock.AnythingOfType("*models.PPMShipment")). + Return(nil, nil) + // Validate incoming payload suite.NoError(params.Body.Validate(strfmt.Default)) @@ -4221,6 +4239,12 @@ func (suite *HandlerSuite) TestUpdateShipmentHandler() { mock.AnythingOfType("*models.PPMShipment")). Return(models.CentPointer(unit.Cents(estimatedIncentive)), models.CentPointer(unit.Cents(sitEstimatedCost)), nil).Once() + ppmEstimator.On("MaxIncentive", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("models.PPMShipment"), + mock.AnythingOfType("*models.PPMShipment")). + Return(nil, nil) + ppmEstimator.On("FinalIncentiveWithDefaultChecks", mock.AnythingOfType("*appcontext.appContext"), mock.AnythingOfType("models.PPMShipment"), @@ -4315,6 +4339,12 @@ func (suite *HandlerSuite) TestUpdateShipmentHandler() { mock.AnythingOfType("*models.PPMShipment")). Return(models.CentPointer(unit.Cents(estimatedIncentive)), models.CentPointer(unit.Cents(sitEstimatedCost)), nil).Once() + ppmEstimator.On("MaxIncentive", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("models.PPMShipment"), + mock.AnythingOfType("*models.PPMShipment")). + Return(nil, nil) + ppmEstimator.On("FinalIncentiveWithDefaultChecks", mock.AnythingOfType("*appcontext.appContext"), mock.AnythingOfType("models.PPMShipment"), @@ -4388,6 +4418,12 @@ func (suite *HandlerSuite) TestUpdateShipmentHandler() { mock.AnythingOfType("*models.PPMShipment")). Return(models.CentPointer(unit.Cents(estimatedIncentive)), models.CentPointer(unit.Cents(sitEstimatedCost)), nil).Once() + ppmEstimator.On("MaxIncentive", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("models.PPMShipment"), + mock.AnythingOfType("*models.PPMShipment")). + Return(nil, nil) + ppmEstimator.On("FinalIncentiveWithDefaultChecks", mock.AnythingOfType("*appcontext.appContext"), mock.AnythingOfType("models.PPMShipment"), diff --git a/pkg/handlers/ghcapi/queues.go b/pkg/handlers/ghcapi/queues.go index 1f69143ce16..a2d38582045 100644 --- a/pkg/handlers/ghcapi/queues.go +++ b/pkg/handlers/ghcapi/queues.go @@ -47,7 +47,7 @@ func (h GetMovesQueueHandler) Handle(params queues.GetMovesQueueParams) middlewa ListOrderParams := services.ListOrderParams{ Branch: params.Branch, Locator: params.Locator, - DodID: params.DodID, + Edipi: params.Edipi, Emplid: params.Emplid, CustomerName: params.CustomerName, DestinationDutyLocation: params.DestinationDutyLocation, @@ -61,6 +61,7 @@ func (h GetMovesQueueHandler) Handle(params queues.GetMovesQueueParams) middlewa Order: params.Order, OrderType: params.OrderType, TOOAssignedUser: params.AssignedTo, + CounselingOffice: params.CounselingOffice, } // When no status filter applied, TOO should only see moves with status of New Move, Service Counseling Completed, or Approvals Requested @@ -237,7 +238,7 @@ func (h GetPaymentRequestsQueueHandler) Handle( listPaymentRequestParams := services.FetchPaymentRequestListParams{ Branch: params.Branch, Locator: params.Locator, - DodID: params.DodID, + Edipi: params.Edipi, Emplid: params.Emplid, CustomerName: params.CustomerName, DestinationDutyLocation: params.DestinationDutyLocation, @@ -250,6 +251,7 @@ func (h GetPaymentRequestsQueueHandler) Handle( OriginDutyLocation: params.OriginDutyLocation, OrderType: params.OrderType, TIOAssignedUser: params.AssignedTo, + CounselingOffice: params.CounselingOffice, } listPaymentRequestParams.Status = []string{string(models.QueuePaymentRequestPaymentRequested)} @@ -368,7 +370,7 @@ func (h GetServicesCounselingQueueHandler) Handle( ListOrderParams := services.ListOrderParams{ Branch: params.Branch, Locator: params.Locator, - DodID: params.DodID, + Edipi: params.Edipi, Emplid: params.Emplid, CustomerName: params.CustomerName, OriginDutyLocation: params.OriginDutyLocation, diff --git a/pkg/handlers/ghcapi/queues_test.go b/pkg/handlers/ghcapi/queues_test.go index aef11b33dbf..e2c0fcb00df 100644 --- a/pkg/handlers/ghcapi/queues_test.go +++ b/pkg/handlers/ghcapi/queues_test.go @@ -818,7 +818,7 @@ func (suite *HandlerSuite) TestGetMoveQueuesHandlerCustomerInfoFilters() { suite.Run("returns results matching Dod ID search term", func() { params := queues.GetMovesQueueParams{ HTTPRequest: request, - DodID: serviceMember1.Edipi, + Edipi: serviceMember1.Edipi, } // Validate incoming payload: no body to validate @@ -887,7 +887,7 @@ func (suite *HandlerSuite) TestGetMoveQueuesHandlerCustomerInfoFilters() { params := queues.GetMovesQueueParams{ HTTPRequest: request, CustomerName: models.StringPointer("Dar"), - DodID: serviceMember1.Edipi, + Edipi: serviceMember1.Edipi, Locator: &move1.Locator, OriginDutyLocation: originDutyLocations, } diff --git a/pkg/handlers/internalapi/mto_shipment_test.go b/pkg/handlers/internalapi/mto_shipment_test.go index a45c7427832..fca0de04f0b 100644 --- a/pkg/handlers/internalapi/mto_shipment_test.go +++ b/pkg/handlers/internalapi/mto_shipment_test.go @@ -366,6 +366,12 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandlerV1() { mock.AnythingOfType("*models.PPMShipment")). Return(nil, nil, nil).Once() + ppmEstimator.On("MaxIncentive", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("models.PPMShipment"), + mock.AnythingOfType("*models.PPMShipment")). + Return(nil, nil) + suite.Nil(params.Body.Validate(strfmt.Default)) response := subtestData.handler.Handle(params) @@ -449,6 +455,12 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandlerV1() { mock.AnythingOfType("*models.PPMShipment")). Return(nil, nil, nil).Once() + ppmEstimator.On("MaxIncentive", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("models.PPMShipment"), + mock.AnythingOfType("*models.PPMShipment")). + Return(nil, nil) + suite.Nil(params.Body.Validate(strfmt.Default)) response := subtestData.handler.Handle(params) @@ -1322,6 +1334,12 @@ func (suite *HandlerSuite) TestUpdateMTOShipmentHandler() { mock.AnythingOfType("*models.PPMShipment")). Return(tc.estimatedIncentive, nil, nil).Once() + ppmEstimator.On("MaxIncentive", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("models.PPMShipment"), + mock.AnythingOfType("*models.PPMShipment")). + Return(nil, nil) + ppmEstimator.On("FinalIncentiveWithDefaultChecks", mock.AnythingOfType("*appcontext.appContext"), mock.AnythingOfType("models.PPMShipment"), diff --git a/pkg/handlers/primeapi/mto_service_item.go b/pkg/handlers/primeapi/mto_service_item.go index 0427f667f75..f704b84758c 100644 --- a/pkg/handlers/primeapi/mto_service_item.go +++ b/pkg/handlers/primeapi/mto_service_item.go @@ -25,10 +25,11 @@ import ( // THIS WILL NEED TO BE UPDATED AS WE CONTINUE TO ADD MORE SERVICE ITEMS. // We will eventually remove this when all service items are added. var CreateableServiceItemMap = map[primemessages.MTOServiceItemModelType]bool{ - primemessages.MTOServiceItemModelTypeMTOServiceItemOriginSIT: true, - primemessages.MTOServiceItemModelTypeMTOServiceItemDestSIT: true, - primemessages.MTOServiceItemModelTypeMTOServiceItemShuttle: true, - primemessages.MTOServiceItemModelTypeMTOServiceItemDomesticCrating: true, + primemessages.MTOServiceItemModelTypeMTOServiceItemOriginSIT: true, + primemessages.MTOServiceItemModelTypeMTOServiceItemDestSIT: true, + primemessages.MTOServiceItemModelTypeMTOServiceItemShuttle: true, + primemessages.MTOServiceItemModelTypeMTOServiceItemDomesticCrating: true, + primemessages.MTOServiceItemModelTypeMTOServiceItemInternationalCrating: true, } // CreateMTOServiceItemHandler is the handler to create MTO service items @@ -43,6 +44,21 @@ func (h CreateMTOServiceItemHandler) Handle(params mtoserviceitemops.CreateMTOSe return h.AuditableAppContextFromRequestWithErrors(params.HTTPRequest, func(appCtx appcontext.AppContext) (middleware.Responder, error) { + /** Feature Flag - Alaska **/ + isAlaskaEnabled := false + featureFlagName := "enable_alaska" + flag, err := h.FeatureFlagFetcher().GetBooleanFlag(params.HTTPRequest.Context(), appCtx.Logger(), "", featureFlagName, map[string]string{}) + if err != nil { + appCtx.Logger().Error("Error fetching feature flag", zap.String("featureFlagKey", featureFlagName), zap.Error(err)) + } else { + isAlaskaEnabled = flag.Match + } + + /** Turn on/off international crating/uncrating service items **/ + if !isAlaskaEnabled { + delete(CreateableServiceItemMap, primemessages.MTOServiceItemModelTypeMTOServiceItemInternationalCrating) + } + // restrict creation to a list if _, ok := CreateableServiceItemMap[params.Body.ModelType()]; !ok { // throw error if modelType() not on the list diff --git a/pkg/handlers/primeapi/payloads/model_to_payload.go b/pkg/handlers/primeapi/payloads/model_to_payload.go index f9018e010d5..32fbcde6cec 100644 --- a/pkg/handlers/primeapi/payloads/model_to_payload.go +++ b/pkg/handlers/primeapi/payloads/model_to_payload.go @@ -705,6 +705,46 @@ func MTOServiceItem(mtoServiceItem *models.MTOServiceItem) primemessages.MTOServ Width: crate.Width.Int32Ptr(), } payload = &cratingSI + + case models.ReServiceCodeICRT, models.ReServiceCodeIUCRT: + item := GetDimension(mtoServiceItem.Dimensions, models.DimensionTypeItem) + crate := GetDimension(mtoServiceItem.Dimensions, models.DimensionTypeCrate) + cratingSI := primemessages.MTOServiceItemInternationalCrating{ + ReServiceCode: handlers.FmtString(string(mtoServiceItem.ReService.Code)), + Description: mtoServiceItem.Description, + Reason: mtoServiceItem.Reason, + StandaloneCrate: mtoServiceItem.StandaloneCrate, + ExternalCrate: mtoServiceItem.ExternalCrate, + } + cratingSI.Item.MTOServiceItemDimension = primemessages.MTOServiceItemDimension{ + ID: strfmt.UUID(item.ID.String()), + Height: item.Height.Int32Ptr(), + Length: item.Length.Int32Ptr(), + Width: item.Width.Int32Ptr(), + } + cratingSI.Crate.MTOServiceItemDimension = primemessages.MTOServiceItemDimension{ + ID: strfmt.UUID(crate.ID.String()), + Height: crate.Height.Int32Ptr(), + Length: crate.Length.Int32Ptr(), + Width: crate.Width.Int32Ptr(), + } + if mtoServiceItem.ReService.Code == models.ReServiceCodeICRT && mtoServiceItem.MTOShipment.PickupAddress != nil { + if *mtoServiceItem.MTOShipment.PickupAddress.IsOconus { + cratingSI.Market = models.MarketOconus.FullString() + } else { + cratingSI.Market = models.MarketConus.FullString() + } + } + + if mtoServiceItem.ReService.Code == models.ReServiceCodeIUCRT && mtoServiceItem.MTOShipment.DestinationAddress != nil { + if *mtoServiceItem.MTOShipment.DestinationAddress.IsOconus { + cratingSI.Market = models.MarketOconus.FullString() + } else { + cratingSI.Market = models.MarketConus.FullString() + } + } + payload = &cratingSI + case models.ReServiceCodeDDSHUT, models.ReServiceCodeDOSHUT: payload = &primemessages.MTOServiceItemShuttle{ ReServiceCode: handlers.FmtString(string(mtoServiceItem.ReService.Code)), diff --git a/pkg/handlers/primeapi/payloads/model_to_payload_test.go b/pkg/handlers/primeapi/payloads/model_to_payload_test.go index fade33c2511..74e90a5ee3c 100644 --- a/pkg/handlers/primeapi/payloads/model_to_payload_test.go +++ b/pkg/handlers/primeapi/payloads/model_to_payload_test.go @@ -628,6 +628,76 @@ func (suite *PayloadsSuite) TestMTOServiceItemDCRT() { suite.True(ok) } +func (suite *PayloadsSuite) TestMTOServiceItemICRTandIUCRT() { + icrtReServiceCode := models.ReServiceCodeICRT + iucrtReServiceCode := models.ReServiceCodeIUCRT + reason := "reason" + standaloneCrate := false + externalCrate := false + dateOfContact1 := time.Now() + timeMilitary1 := "1500Z" + firstAvailableDeliveryDate1 := dateOfContact1.AddDate(0, 0, 10) + dateOfContact2 := time.Now().AddDate(0, 0, 5) + timeMilitary2 := "1300Z" + firstAvailableDeliveryDate2 := dateOfContact2.AddDate(0, 0, 10) + + mtoServiceItemICRT := &models.MTOServiceItem{ + ID: uuid.Must(uuid.NewV4()), + ReService: models.ReService{Code: icrtReServiceCode}, + Reason: &reason, + StandaloneCrate: &standaloneCrate, + ExternalCrate: &externalCrate, + CustomerContacts: models.MTOServiceItemCustomerContacts{ + models.MTOServiceItemCustomerContact{ + DateOfContact: dateOfContact1, + TimeMilitary: timeMilitary1, + FirstAvailableDeliveryDate: firstAvailableDeliveryDate1, + Type: models.CustomerContactTypeFirst, + }, + models.MTOServiceItemCustomerContact{ + DateOfContact: dateOfContact2, + TimeMilitary: timeMilitary2, + FirstAvailableDeliveryDate: firstAvailableDeliveryDate2, + Type: models.CustomerContactTypeSecond, + }, + }, + } + + mtoServiceItemIUCRT := &models.MTOServiceItem{ + ID: uuid.Must(uuid.NewV4()), + ReService: models.ReService{Code: iucrtReServiceCode}, + Reason: &reason, + StandaloneCrate: &standaloneCrate, + ExternalCrate: &externalCrate, + CustomerContacts: models.MTOServiceItemCustomerContacts{ + models.MTOServiceItemCustomerContact{ + DateOfContact: dateOfContact1, + TimeMilitary: timeMilitary1, + FirstAvailableDeliveryDate: firstAvailableDeliveryDate1, + Type: models.CustomerContactTypeFirst, + }, + models.MTOServiceItemCustomerContact{ + DateOfContact: dateOfContact2, + TimeMilitary: timeMilitary2, + FirstAvailableDeliveryDate: firstAvailableDeliveryDate2, + Type: models.CustomerContactTypeSecond, + }, + }, + } + + resultICRT := MTOServiceItem(mtoServiceItemICRT) + resultIUCRT := MTOServiceItem(mtoServiceItemIUCRT) + + suite.NotNil(resultICRT) + suite.NotNil(resultIUCRT) + + _, ok := resultICRT.(*primemessages.MTOServiceItemInternationalCrating) + suite.True(ok) + + _, ok = resultIUCRT.(*primemessages.MTOServiceItemInternationalCrating) + suite.True(ok) +} + func (suite *PayloadsSuite) TestMTOServiceItemDDSHUT() { reServiceCode := models.ReServiceCodeDDSHUT reason := "reason" diff --git a/pkg/handlers/primeapi/payloads/payload_to_model.go b/pkg/handlers/primeapi/payloads/payload_to_model.go index 5f0969df133..4e12758781e 100644 --- a/pkg/handlers/primeapi/payloads/payload_to_model.go +++ b/pkg/handlers/primeapi/payloads/payload_to_model.go @@ -560,6 +560,44 @@ func MTOServiceItemModel(mtoServiceItem primemessages.MTOServiceItem) (*models.M Width: unit.ThousandthInches(*domesticCrating.Crate.Width), }, } + case primemessages.MTOServiceItemModelTypeMTOServiceItemInternationalCrating: + internationalCrating := mtoServiceItem.(*primemessages.MTOServiceItemInternationalCrating) + + // additional validation for this specific service item type + verrs := validateInternationalCrating(*internationalCrating) + if verrs.HasAny() { + return nil, verrs + } + + // have to get code from payload + model.ReService.Code = models.ReServiceCode(*internationalCrating.ReServiceCode) + model.Description = internationalCrating.Description + model.Reason = internationalCrating.Reason + model.StandaloneCrate = internationalCrating.StandaloneCrate + model.ExternalCrate = internationalCrating.ExternalCrate + + if model.ReService.Code == models.ReServiceCodeICRT { + if internationalCrating.StandaloneCrate == nil { + model.StandaloneCrate = models.BoolPointer(false) + } + if internationalCrating.ExternalCrate == nil { + model.ExternalCrate = models.BoolPointer(false) + } + } + model.Dimensions = models.MTOServiceItemDimensions{ + models.MTOServiceItemDimension{ + Type: models.DimensionTypeItem, + Length: unit.ThousandthInches(*internationalCrating.Item.Length), + Height: unit.ThousandthInches(*internationalCrating.Item.Height), + Width: unit.ThousandthInches(*internationalCrating.Item.Width), + }, + models.MTOServiceItemDimension{ + Type: models.DimensionTypeCrate, + Length: unit.ThousandthInches(*internationalCrating.Crate.Length), + Height: unit.ThousandthInches(*internationalCrating.Crate.Height), + Width: unit.ThousandthInches(*internationalCrating.Crate.Width), + }, + } default: // assume basic service item, take in provided re service code basic := mtoServiceItem.(*primemessages.MTOServiceItemBasic) @@ -730,6 +768,18 @@ func validateDomesticCrating(m primemessages.MTOServiceItemDomesticCrating) *val ) } +// validateInternationalCrating validates this mto service item international crating +func validateInternationalCrating(m primemessages.MTOServiceItemInternationalCrating) *validate.Errors { + return validate.Validate( + &models.ItemCanFitInsideCrate{ + Name: "Item", + NameCompared: "Crate", + Item: &m.Item.MTOServiceItemDimension, + Crate: &m.Crate.MTOServiceItemDimension, + }, + ) +} + // validateDDFSITForCreate validates DDFSIT service item has all required fields func validateDDFSITForCreate(m primemessages.MTOServiceItemDestSIT) *validate.Errors { verrs := validate.NewErrors() diff --git a/pkg/handlers/primeapi/payloads/payload_to_model_test.go b/pkg/handlers/primeapi/payloads/payload_to_model_test.go index c7beddad167..2cf4c11549d 100644 --- a/pkg/handlers/primeapi/payloads/payload_to_model_test.go +++ b/pkg/handlers/primeapi/payloads/payload_to_model_test.go @@ -163,6 +163,128 @@ func (suite *PayloadsSuite) TestMTOServiceItemModel() { }) + suite.Run("Success - Returns a ICRT/IUCRT service item model", func() { + // ICRT + icrtCode := models.ReServiceCodeICRT.String() + externalCrate := false + ICRTServiceItem := &primemessages.MTOServiceItemInternationalCrating{ + ReServiceCode: &icrtCode, + Reason: &reason, + Description: &description, + StandaloneCrate: &standaloneCrate, + ExternalCrate: &externalCrate, + } + ICRTServiceItem.Item.MTOServiceItemDimension = *item + ICRTServiceItem.Crate.MTOServiceItemDimension = *crate + + ICRTServiceItem.SetMoveTaskOrderID(handlers.FmtUUID(moveTaskOrderIDField)) + ICRTServiceItem.SetMtoShipmentID(*mtoShipmentIDString) + + returnedModel, verrs := MTOServiceItemModel(ICRTServiceItem) + + var returnedItem, returnedCrate models.MTOServiceItemDimension + for _, dimension := range returnedModel.Dimensions { + if dimension.Type == models.DimensionTypeItem { + returnedItem = dimension + } else { + returnedCrate = dimension + } + } + + suite.NoVerrs(verrs) + suite.Equal(moveTaskOrderIDField.String(), returnedModel.MoveTaskOrderID.String()) + suite.Equal(mtoShipmentIDField.String(), returnedModel.MTOShipmentID.String()) + suite.Equal(models.ReServiceCodeICRT, returnedModel.ReService.Code) + suite.Equal(ICRTServiceItem.Reason, returnedModel.Reason) + suite.Equal(ICRTServiceItem.Description, returnedModel.Description) + suite.Equal(ICRTServiceItem.StandaloneCrate, returnedModel.StandaloneCrate) + suite.Equal(ICRTServiceItem.ExternalCrate, returnedModel.ExternalCrate) + suite.Equal(unit.ThousandthInches(*ICRTServiceItem.Item.Length), returnedItem.Length) + suite.Equal(unit.ThousandthInches(*ICRTServiceItem.Crate.Length), returnedCrate.Length) + + // IUCRT + iucrtCode := models.ReServiceCodeIUCRT.String() + IUCRTServiceItem := &primemessages.MTOServiceItemInternationalCrating{ + ReServiceCode: &iucrtCode, + Reason: &reason, + Description: &description, + } + IUCRTServiceItem.Item.MTOServiceItemDimension = *item + IUCRTServiceItem.Crate.MTOServiceItemDimension = *crate + + IUCRTServiceItem.SetMoveTaskOrderID(handlers.FmtUUID(moveTaskOrderIDField)) + IUCRTServiceItem.SetMtoShipmentID(*mtoShipmentIDString) + + iucrtReturnedModel, verrs := MTOServiceItemModel(IUCRTServiceItem) + + var icurtReturnedItem, icurtReturnedCrate models.MTOServiceItemDimension + for _, dimension := range iucrtReturnedModel.Dimensions { + if dimension.Type == models.DimensionTypeItem { + icurtReturnedItem = dimension + } else { + icurtReturnedCrate = dimension + } + } + + suite.NoVerrs(verrs) + suite.Equal(moveTaskOrderIDField.String(), iucrtReturnedModel.MoveTaskOrderID.String()) + suite.Equal(mtoShipmentIDField.String(), iucrtReturnedModel.MTOShipmentID.String()) + suite.Equal(models.ReServiceCodeIUCRT, iucrtReturnedModel.ReService.Code) + suite.Equal(IUCRTServiceItem.Reason, iucrtReturnedModel.Reason) + suite.Equal(IUCRTServiceItem.Description, iucrtReturnedModel.Description) + suite.Equal(unit.ThousandthInches(*ICRTServiceItem.Item.Length), icurtReturnedItem.Length) + suite.Equal(unit.ThousandthInches(*ICRTServiceItem.Crate.Length), icurtReturnedCrate.Length) + }) + + suite.Run("Fail - Returns error for ICRT/IUCRT service item because of validation error", func() { + // ICRT + icrtCode := models.ReServiceCodeICRT.String() + externalCrate := false + badCrateMeasurement := int32(200) + badCrate := &primemessages.MTOServiceItemDimension{ + Height: &badCrateMeasurement, + Width: &badCrateMeasurement, + Length: &badCrateMeasurement, + } + + badICRTServiceItem := &primemessages.MTOServiceItemInternationalCrating{ + ReServiceCode: &icrtCode, + Reason: &reason, + Description: &description, + StandaloneCrate: &standaloneCrate, + ExternalCrate: &externalCrate, + } + badICRTServiceItem.Item.MTOServiceItemDimension = *item + badICRTServiceItem.Crate.MTOServiceItemDimension = *badCrate + + badICRTServiceItem.SetMoveTaskOrderID(handlers.FmtUUID(moveTaskOrderIDField)) + badICRTServiceItem.SetMtoShipmentID(*mtoShipmentIDString) + + returnedModel, verrs := MTOServiceItemModel(badICRTServiceItem) + + suite.True(verrs.HasAny(), fmt.Sprintf("invalid crate dimensions for %s service item", models.ReServiceCodeICRT)) + suite.Nil(returnedModel, "returned a model when erroring") + + // IUCRT + iucrtCode := models.ReServiceCodeIUCRT.String() + + badIUCRTServiceItem := &primemessages.MTOServiceItemInternationalCrating{ + ReServiceCode: &iucrtCode, + Reason: &reason, + Description: &description, + } + badIUCRTServiceItem.Item.MTOServiceItemDimension = *item + badIUCRTServiceItem.Crate.MTOServiceItemDimension = *badCrate + + badIUCRTServiceItem.SetMoveTaskOrderID(handlers.FmtUUID(moveTaskOrderIDField)) + badIUCRTServiceItem.SetMtoShipmentID(*mtoShipmentIDString) + + iucrtReturnedModel, verrs := MTOServiceItemModel(badIUCRTServiceItem) + + suite.True(verrs.HasAny(), fmt.Sprintf("invalid crate dimensions for %s service item", models.ReServiceCodeIUCRT)) + suite.Nil(iucrtReturnedModel, "returned a model when erroring") + }) + suite.Run("Success - Returns SIT origin service item model", func() { originSITServiceItem := &primemessages.MTOServiceItemOriginSIT{ ReServiceCode: &originServiceCode, diff --git a/pkg/handlers/primeapiv2/mto_service_item.go b/pkg/handlers/primeapiv2/mto_service_item.go index 7b513fec838..e26332964c8 100644 --- a/pkg/handlers/primeapiv2/mto_service_item.go +++ b/pkg/handlers/primeapiv2/mto_service_item.go @@ -23,10 +23,11 @@ import ( // THIS WILL NEED TO BE UPDATED AS WE CONTINUE TO ADD MORE SERVICE ITEMS. // We will eventually remove this when all service items are added. var CreateableServiceItemMap = map[primev2messages.MTOServiceItemModelType]bool{ - primev2messages.MTOServiceItemModelTypeMTOServiceItemOriginSIT: true, - primev2messages.MTOServiceItemModelTypeMTOServiceItemDestSIT: true, - primev2messages.MTOServiceItemModelTypeMTOServiceItemShuttle: true, - primev2messages.MTOServiceItemModelTypeMTOServiceItemDomesticCrating: true, + primev2messages.MTOServiceItemModelTypeMTOServiceItemOriginSIT: true, + primev2messages.MTOServiceItemModelTypeMTOServiceItemDestSIT: true, + primev2messages.MTOServiceItemModelTypeMTOServiceItemShuttle: true, + primev2messages.MTOServiceItemModelTypeMTOServiceItemDomesticCrating: true, + primev2messages.MTOServiceItemModelTypeMTOServiceItemInternationalCrating: true, } // CreateMTOServiceItemHandler is the handler to create MTO service items @@ -41,6 +42,21 @@ func (h CreateMTOServiceItemHandler) Handle(params mtoserviceitemops.CreateMTOSe return h.AuditableAppContextFromRequestWithErrors(params.HTTPRequest, func(appCtx appcontext.AppContext) (middleware.Responder, error) { + /** Feature Flag - Alaska **/ + isAlaskaEnabled := false + featureFlagName := "enable_alaska" + flag, err := h.FeatureFlagFetcher().GetBooleanFlag(params.HTTPRequest.Context(), appCtx.Logger(), "", featureFlagName, map[string]string{}) + if err != nil { + appCtx.Logger().Error("Error fetching feature flag", zap.String("featureFlagKey", featureFlagName), zap.Error(err)) + } else { + isAlaskaEnabled = flag.Match + } + + /** Turn on/off international crating/uncrating service items **/ + if !isAlaskaEnabled { + delete(CreateableServiceItemMap, primev2messages.MTOServiceItemModelTypeMTOServiceItemInternationalCrating) + } + // restrict creation to a list if _, ok := primeapi.CreateableServiceItemMap[params.Body.ModelType()]; !ok { // throw error if modelType() not on the list diff --git a/pkg/handlers/primeapiv2/mto_shipment.go b/pkg/handlers/primeapiv2/mto_shipment.go index 8f0e59efd59..35672bbc02e 100644 --- a/pkg/handlers/primeapiv2/mto_shipment.go +++ b/pkg/handlers/primeapiv2/mto_shipment.go @@ -142,6 +142,9 @@ func (h CreateMTOShipmentHandler) Handle(params mtoshipmentops.CreateMTOShipment case apperror.NotFoundError: return mtoshipmentops.NewCreateMTOShipmentNotFound().WithPayload( payloads.ClientError(handlers.NotFoundMessage, err.Error(), h.GetTraceIDFromRequest(params.HTTPRequest))), err + case apperror.EventError: + return mtoshipmentops.NewUpdateMTOShipmentBadRequest().WithPayload( + payloads.ClientError(handlers.InternalServerErrMessage, err.Error(), h.GetTraceIDFromRequest(params.HTTPRequest))), err case apperror.InvalidInputError: return mtoshipmentops.NewCreateMTOShipmentUnprocessableEntity().WithPayload( payloads.ValidationError(err.Error(), h.GetTraceIDFromRequest(params.HTTPRequest), e.ValidationErrors)), err @@ -206,6 +209,9 @@ func (h UpdateMTOShipmentHandler) Handle(params mtoshipmentops.UpdateMTOShipment case apperror.NotFoundError: return mtoshipmentops.NewUpdateMTOShipmentNotFound().WithPayload( payloads.ClientError(handlers.NotFoundMessage, err.Error(), h.GetTraceIDFromRequest(params.HTTPRequest))), err + case apperror.EventError: + return mtoshipmentops.NewUpdateMTOShipmentBadRequest().WithPayload( + payloads.ClientError(handlers.InternalServerErrMessage, err.Error(), h.GetTraceIDFromRequest(params.HTTPRequest))), err case apperror.InvalidInputError: payload := payloads.ValidationError(err.Error(), h.GetTraceIDFromRequest(params.HTTPRequest), e.ValidationErrors) return mtoshipmentops.NewUpdateMTOShipmentUnprocessableEntity().WithPayload(payload), err diff --git a/pkg/handlers/primeapiv2/mto_shipment_test.go b/pkg/handlers/primeapiv2/mto_shipment_test.go index 9db87f6319f..151fff9352f 100644 --- a/pkg/handlers/primeapiv2/mto_shipment_test.go +++ b/pkg/handlers/primeapiv2/mto_shipment_test.go @@ -300,6 +300,12 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { mock.AnythingOfType("*models.PPMShipment")). Return(models.CentPointer(unit.Cents(estimatedIncentive)), models.CentPointer(unit.Cents(sitEstimatedCost)), nil).Once() + ppmEstimator.On("MaxIncentive", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("models.PPMShipment"), + mock.AnythingOfType("*models.PPMShipment")). + Return(nil, nil) + // Validate incoming payload suite.NoError(params.Body.Validate(strfmt.Default)) @@ -607,6 +613,42 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { suite.Contains(*typedResponse.Payload.Detail, unavailableMove.ID.String()) }) + suite.Run("POST failure - 500 - App Event Internal DTOD Server Error", func() { + // Under Test: CreateMTOShipmentHandler + // Setup: Create a shipment with DTOD outage simulated or bad zip + // Expected: 500 Internal Server Error returned + + handler, move := setupTestData(false) + req := httptest.NewRequest("POST", "/mto-shipments", nil) + handler.ShipmentCreator = &mockCreator + + err := apperror.EventError{} + + mockCreator.On("CreateShipment", + mock.AnythingOfType("*appcontext.appContext"), + mock.Anything, + ).Return(nil, nil, err) + + params := mtoshipmentops.CreateMTOShipmentParams{ + HTTPRequest: req, + Body: &primev2messages.CreateMTOShipment{ + MoveTaskOrderID: handlers.FmtUUID(move.ID), + Agents: nil, + CustomerRemarks: nil, + PointOfContact: "John Doe", + RequestedPickupDate: handlers.FmtDatePtr(models.TimePointer(time.Now())), + ShipmentType: primev2messages.NewMTOShipmentType(primev2messages.MTOShipmentTypeHHG), + PickupAddress: struct{ primev2messages.Address }{pickupAddress}, + DestinationAddress: struct{ primev2messages.Address }{destinationAddress}, + }, + } + + response := handler.Handle(params) + suite.IsType(&mtoshipmentops.CreateMTOShipmentInternalServerError{}, response) + typedResponse := response.(*mtoshipmentops.CreateMTOShipmentInternalServerError) + suite.Contains(*typedResponse.Payload.Detail, "An internal server error has occurred") + }) + suite.Run("POST failure - 422 - modelType() not supported", func() { // Under Test: CreateMTOShipmentHandler // Setup: Create a shipment with service items that don't match the modeltype diff --git a/pkg/handlers/primeapiv2/payloads/model_to_payload.go b/pkg/handlers/primeapiv2/payloads/model_to_payload.go index 59d907abe7c..e7350548dd9 100644 --- a/pkg/handlers/primeapiv2/payloads/model_to_payload.go +++ b/pkg/handlers/primeapiv2/payloads/model_to_payload.go @@ -633,6 +633,46 @@ func MTOServiceItem(mtoServiceItem *models.MTOServiceItem) primev2messages.MTOSe Width: crate.Width.Int32Ptr(), } payload = &cratingSI + + case models.ReServiceCodeICRT, models.ReServiceCodeIUCRT: + item := GetDimension(mtoServiceItem.Dimensions, models.DimensionTypeItem) + crate := GetDimension(mtoServiceItem.Dimensions, models.DimensionTypeCrate) + cratingSI := primev2messages.MTOServiceItemInternationalCrating{ + ReServiceCode: handlers.FmtString(string(mtoServiceItem.ReService.Code)), + Description: mtoServiceItem.Description, + Reason: mtoServiceItem.Reason, + StandaloneCrate: mtoServiceItem.StandaloneCrate, + ExternalCrate: mtoServiceItem.ExternalCrate, + } + cratingSI.Item.MTOServiceItemDimension = primev2messages.MTOServiceItemDimension{ + ID: strfmt.UUID(item.ID.String()), + Height: item.Height.Int32Ptr(), + Length: item.Length.Int32Ptr(), + Width: item.Width.Int32Ptr(), + } + cratingSI.Crate.MTOServiceItemDimension = primev2messages.MTOServiceItemDimension{ + ID: strfmt.UUID(crate.ID.String()), + Height: crate.Height.Int32Ptr(), + Length: crate.Length.Int32Ptr(), + Width: crate.Width.Int32Ptr(), + } + if mtoServiceItem.ReService.Code == models.ReServiceCodeICRT && mtoServiceItem.MTOShipment.PickupAddress != nil { + if *mtoServiceItem.MTOShipment.PickupAddress.IsOconus { + cratingSI.Market = models.MarketOconus.FullString() + } else { + cratingSI.Market = models.MarketConus.FullString() + } + } + + if mtoServiceItem.ReService.Code == models.ReServiceCodeIUCRT && mtoServiceItem.MTOShipment.DestinationAddress != nil { + if *mtoServiceItem.MTOShipment.DestinationAddress.IsOconus { + cratingSI.Market = models.MarketOconus.FullString() + } else { + cratingSI.Market = models.MarketConus.FullString() + } + } + payload = &cratingSI + case models.ReServiceCodeDDSHUT, models.ReServiceCodeDOSHUT: payload = &primev2messages.MTOServiceItemShuttle{ ReServiceCode: handlers.FmtString(string(mtoServiceItem.ReService.Code)), diff --git a/pkg/handlers/primeapiv2/payloads/model_to_payload_test.go b/pkg/handlers/primeapiv2/payloads/model_to_payload_test.go index b80a8aecc06..ba3a804b267 100644 --- a/pkg/handlers/primeapiv2/payloads/model_to_payload_test.go +++ b/pkg/handlers/primeapiv2/payloads/model_to_payload_test.go @@ -683,6 +683,76 @@ func (suite *PayloadsSuite) TestMTOServiceItemDCRT() { suite.True(ok) } +func (suite *PayloadsSuite) TestMTOServiceItemICRTandIUCRT() { + icrtReServiceCode := models.ReServiceCodeICRT + iucrtReServiceCode := models.ReServiceCodeIUCRT + reason := "reason" + standaloneCrate := false + externalCrate := false + dateOfContact1 := time.Now() + timeMilitary1 := "1500Z" + firstAvailableDeliveryDate1 := dateOfContact1.AddDate(0, 0, 10) + dateOfContact2 := time.Now().AddDate(0, 0, 5) + timeMilitary2 := "1300Z" + firstAvailableDeliveryDate2 := dateOfContact2.AddDate(0, 0, 10) + + mtoServiceItemICRT := &models.MTOServiceItem{ + ID: uuid.Must(uuid.NewV4()), + ReService: models.ReService{Code: icrtReServiceCode}, + Reason: &reason, + StandaloneCrate: &standaloneCrate, + ExternalCrate: &externalCrate, + CustomerContacts: models.MTOServiceItemCustomerContacts{ + models.MTOServiceItemCustomerContact{ + DateOfContact: dateOfContact1, + TimeMilitary: timeMilitary1, + FirstAvailableDeliveryDate: firstAvailableDeliveryDate1, + Type: models.CustomerContactTypeFirst, + }, + models.MTOServiceItemCustomerContact{ + DateOfContact: dateOfContact2, + TimeMilitary: timeMilitary2, + FirstAvailableDeliveryDate: firstAvailableDeliveryDate2, + Type: models.CustomerContactTypeSecond, + }, + }, + } + + mtoServiceItemIUCRT := &models.MTOServiceItem{ + ID: uuid.Must(uuid.NewV4()), + ReService: models.ReService{Code: iucrtReServiceCode}, + Reason: &reason, + StandaloneCrate: &standaloneCrate, + ExternalCrate: &externalCrate, + CustomerContacts: models.MTOServiceItemCustomerContacts{ + models.MTOServiceItemCustomerContact{ + DateOfContact: dateOfContact1, + TimeMilitary: timeMilitary1, + FirstAvailableDeliveryDate: firstAvailableDeliveryDate1, + Type: models.CustomerContactTypeFirst, + }, + models.MTOServiceItemCustomerContact{ + DateOfContact: dateOfContact2, + TimeMilitary: timeMilitary2, + FirstAvailableDeliveryDate: firstAvailableDeliveryDate2, + Type: models.CustomerContactTypeSecond, + }, + }, + } + + resultICRT := MTOServiceItem(mtoServiceItemICRT) + resultIUCRT := MTOServiceItem(mtoServiceItemIUCRT) + + suite.NotNil(resultICRT) + suite.NotNil(resultIUCRT) + + _, ok := resultICRT.(*primev2messages.MTOServiceItemInternationalCrating) + suite.True(ok) + + _, ok = resultIUCRT.(*primev2messages.MTOServiceItemInternationalCrating) + suite.True(ok) +} + func (suite *PayloadsSuite) TestMTOServiceItemDDSHUT() { reServiceCode := models.ReServiceCodeDDSHUT reason := "reason" diff --git a/pkg/handlers/primeapiv2/payloads/payload_to_model.go b/pkg/handlers/primeapiv2/payloads/payload_to_model.go index d5a0ff9f487..8c8b663d123 100644 --- a/pkg/handlers/primeapiv2/payloads/payload_to_model.go +++ b/pkg/handlers/primeapiv2/payloads/payload_to_model.go @@ -653,6 +653,44 @@ func MTOServiceItemModel(mtoServiceItem primev2messages.MTOServiceItem) (*models Width: unit.ThousandthInches(*domesticCrating.Crate.Width), }, } + case primev2messages.MTOServiceItemModelTypeMTOServiceItemInternationalCrating: + internationalCrating := mtoServiceItem.(*primev2messages.MTOServiceItemInternationalCrating) + + // additional validation for this specific service item type + verrs := validateInternationalCrating(*internationalCrating) + if verrs.HasAny() { + return nil, verrs + } + + // have to get code from payload + model.ReService.Code = models.ReServiceCode(*internationalCrating.ReServiceCode) + model.Description = internationalCrating.Description + model.Reason = internationalCrating.Reason + model.StandaloneCrate = internationalCrating.StandaloneCrate + model.ExternalCrate = internationalCrating.ExternalCrate + + if model.ReService.Code == models.ReServiceCodeICRT { + if internationalCrating.StandaloneCrate == nil { + model.StandaloneCrate = models.BoolPointer(false) + } + if internationalCrating.ExternalCrate == nil { + model.ExternalCrate = models.BoolPointer(false) + } + } + model.Dimensions = models.MTOServiceItemDimensions{ + models.MTOServiceItemDimension{ + Type: models.DimensionTypeItem, + Length: unit.ThousandthInches(*internationalCrating.Item.Length), + Height: unit.ThousandthInches(*internationalCrating.Item.Height), + Width: unit.ThousandthInches(*internationalCrating.Item.Width), + }, + models.MTOServiceItemDimension{ + Type: models.DimensionTypeCrate, + Length: unit.ThousandthInches(*internationalCrating.Crate.Length), + Height: unit.ThousandthInches(*internationalCrating.Crate.Height), + Width: unit.ThousandthInches(*internationalCrating.Crate.Width), + }, + } default: // assume basic service item, take in provided re service code basic := mtoServiceItem.(*primev2messages.MTOServiceItemBasic) @@ -823,6 +861,18 @@ func validateDomesticCrating(m primev2messages.MTOServiceItemDomesticCrating) *v ) } +// validateInternationalCrating validates this mto service item international crating +func validateInternationalCrating(m primev2messages.MTOServiceItemInternationalCrating) *validate.Errors { + return validate.Validate( + &models.ItemCanFitInsideCrateV2{ + Name: "Item", + NameCompared: "Crate", + Item: &m.Item.MTOServiceItemDimension, + Crate: &m.Crate.MTOServiceItemDimension, + }, + ) +} + // validateDDFSITForCreate validates DDFSIT service item has all required fields func validateDDFSITForCreate(m primev2messages.MTOServiceItemDestSIT) *validate.Errors { verrs := validate.NewErrors() diff --git a/pkg/handlers/primeapiv2/payloads/payload_to_model_test.go b/pkg/handlers/primeapiv2/payloads/payload_to_model_test.go index 9b3187d947c..ccfd9a10f21 100644 --- a/pkg/handlers/primeapiv2/payloads/payload_to_model_test.go +++ b/pkg/handlers/primeapiv2/payloads/payload_to_model_test.go @@ -144,6 +144,130 @@ func (suite *PayloadsSuite) TestMTOServiceItemModel() { }) + suite.Run("Success - Returns a ICRT/IUCRT service item model", func() { + // ICRT + icrtCode := models.ReServiceCodeICRT.String() + externalCrate := false + ICRTServiceItem := &primev2messages.MTOServiceItemInternationalCrating{ + ReServiceCode: &icrtCode, + Reason: &reason, + Description: &description, + StandaloneCrate: &standaloneCrate, + ExternalCrate: &externalCrate, + } + ICRTServiceItem.Item.MTOServiceItemDimension = *item + ICRTServiceItem.Crate.MTOServiceItemDimension = *crate + + ICRTServiceItem.SetMoveTaskOrderID(handlers.FmtUUID(moveTaskOrderIDField)) + ICRTServiceItem.SetMtoShipmentID(*mtoShipmentIDString) + + returnedModel, verrs := MTOServiceItemModel(ICRTServiceItem) + + var returnedItem, returnedCrate models.MTOServiceItemDimension + for _, dimension := range returnedModel.Dimensions { + if dimension.Type == models.DimensionTypeItem { + returnedItem = dimension + } else { + returnedCrate = dimension + } + } + + suite.NoVerrs(verrs) + suite.Equal(moveTaskOrderIDField.String(), returnedModel.MoveTaskOrderID.String()) + suite.Equal(mtoShipmentIDField.String(), returnedModel.MTOShipmentID.String()) + suite.Equal(models.ReServiceCodeICRT, returnedModel.ReService.Code) + suite.Equal(ICRTServiceItem.Reason, returnedModel.Reason) + suite.Equal(ICRTServiceItem.Description, returnedModel.Description) + suite.Equal(ICRTServiceItem.StandaloneCrate, returnedModel.StandaloneCrate) + suite.Equal(ICRTServiceItem.ExternalCrate, returnedModel.ExternalCrate) + suite.Equal(unit.ThousandthInches(*ICRTServiceItem.Item.Length), returnedItem.Length) + suite.Equal(unit.ThousandthInches(*ICRTServiceItem.Crate.Length), returnedCrate.Length) + + // IUCRT + iucrtCode := models.ReServiceCodeIUCRT.String() + IUCRTServiceItem := &primev2messages.MTOServiceItemInternationalCrating{ + ReServiceCode: &iucrtCode, + Reason: &reason, + Description: &description, + } + IUCRTServiceItem.Item.MTOServiceItemDimension = *item + IUCRTServiceItem.Crate.MTOServiceItemDimension = *crate + + IUCRTServiceItem.SetMoveTaskOrderID(handlers.FmtUUID(moveTaskOrderIDField)) + IUCRTServiceItem.SetMtoShipmentID(*mtoShipmentIDString) + + iucrtReturnedModel, verrs := MTOServiceItemModel(IUCRTServiceItem) + + var icurtReturnedItem, icurtReturnedCrate models.MTOServiceItemDimension + for _, dimension := range iucrtReturnedModel.Dimensions { + if dimension.Type == models.DimensionTypeItem { + icurtReturnedItem = dimension + } else { + icurtReturnedCrate = dimension + } + } + + suite.NoVerrs(verrs) + suite.Equal(moveTaskOrderIDField.String(), iucrtReturnedModel.MoveTaskOrderID.String()) + suite.Equal(mtoShipmentIDField.String(), iucrtReturnedModel.MTOShipmentID.String()) + suite.Equal(models.ReServiceCodeIUCRT, iucrtReturnedModel.ReService.Code) + suite.Equal(IUCRTServiceItem.Reason, iucrtReturnedModel.Reason) + suite.Equal(IUCRTServiceItem.Description, iucrtReturnedModel.Description) + suite.Equal(unit.ThousandthInches(*ICRTServiceItem.Item.Length), icurtReturnedItem.Length) + suite.Equal(unit.ThousandthInches(*ICRTServiceItem.Crate.Length), icurtReturnedCrate.Length) + }) + + suite.Run("Fail - Returns error for ICRT/IUCRT service item because of validation error", func() { + // ICRT + icrtCode := models.ReServiceCodeICRT.String() + externalCrate := false + badCrateMeasurement := int32(200) + badCrate := &primev2messages.MTOServiceItemDimension{ + Height: &badCrateMeasurement, + Width: &badCrateMeasurement, + Length: &badCrateMeasurement, + } + + badICRTServiceItem := &primev2messages.MTOServiceItemInternationalCrating{ + ReServiceCode: &icrtCode, + Reason: &reason, + Description: &description, + StandaloneCrate: &standaloneCrate, + ExternalCrate: &externalCrate, + } + badICRTServiceItem.Item.MTOServiceItemDimension = *item + badICRTServiceItem.Crate.MTOServiceItemDimension = *badCrate + + badICRTServiceItem.SetMoveTaskOrderID(handlers.FmtUUID(moveTaskOrderIDField)) + badICRTServiceItem.SetMtoShipmentID(*mtoShipmentIDString) + + returnedModel, verrs := MTOServiceItemModel(badICRTServiceItem) + + suite.True(verrs.HasAny(), fmt.Sprintf("invalid crate dimensions for %s service item", models.ReServiceCodeICRT)) + suite.Nil(returnedModel, "returned a model when erroring") + + // IUCRT + iucrtCode := models.ReServiceCodeIUCRT.String() + + badIUCRTServiceItem := &primev2messages.MTOServiceItemInternationalCrating{ + ReServiceCode: &iucrtCode, + Reason: &reason, + Description: &description, + StandaloneCrate: &standaloneCrate, + ExternalCrate: &externalCrate, + } + badIUCRTServiceItem.Item.MTOServiceItemDimension = *item + badIUCRTServiceItem.Crate.MTOServiceItemDimension = *badCrate + + badIUCRTServiceItem.SetMoveTaskOrderID(handlers.FmtUUID(moveTaskOrderIDField)) + badIUCRTServiceItem.SetMtoShipmentID(*mtoShipmentIDString) + + iucrtReturnedModel, verrs := MTOServiceItemModel(badIUCRTServiceItem) + + suite.True(verrs.HasAny(), fmt.Sprintf("invalid crate dimensions for %s service item", models.ReServiceCodeIUCRT)) + suite.Nil(iucrtReturnedModel, "returned a model when erroring") + }) + suite.Run("Success - Returns SIT destination service item model", func() { destSITServiceItem := &primev2messages.MTOServiceItemDestSIT{ ReServiceCode: &destServiceCode, diff --git a/pkg/handlers/primeapiv3/mto_service_item.go b/pkg/handlers/primeapiv3/mto_service_item.go index 1080d3afed2..4b83e3d1fd2 100644 --- a/pkg/handlers/primeapiv3/mto_service_item.go +++ b/pkg/handlers/primeapiv3/mto_service_item.go @@ -23,10 +23,11 @@ import ( // THIS WILL NEED TO BE UPDATED AS WE CONTINUE TO ADD MORE SERVICE ITEMS. // We will eventually remove this when all service items are added. var CreateableServiceItemMap = map[primev3messages.MTOServiceItemModelType]bool{ - primev3messages.MTOServiceItemModelTypeMTOServiceItemOriginSIT: true, - primev3messages.MTOServiceItemModelTypeMTOServiceItemDestSIT: true, - primev3messages.MTOServiceItemModelTypeMTOServiceItemShuttle: true, - primev3messages.MTOServiceItemModelTypeMTOServiceItemDomesticCrating: true, + primev3messages.MTOServiceItemModelTypeMTOServiceItemOriginSIT: true, + primev3messages.MTOServiceItemModelTypeMTOServiceItemDestSIT: true, + primev3messages.MTOServiceItemModelTypeMTOServiceItemShuttle: true, + primev3messages.MTOServiceItemModelTypeMTOServiceItemDomesticCrating: true, + primev3messages.MTOServiceItemModelTypeMTOServiceItemInternationalCrating: true, } // CreateMTOServiceItemHandler is the handler to create MTO service items @@ -41,6 +42,21 @@ func (h CreateMTOServiceItemHandler) Handle(params mtoserviceitemops.CreateMTOSe return h.AuditableAppContextFromRequestWithErrors(params.HTTPRequest, func(appCtx appcontext.AppContext) (middleware.Responder, error) { + /** Feature Flag - Alaska **/ + isAlaskaEnabled := false + featureFlagName := "enable_alaska" + flag, err := h.FeatureFlagFetcher().GetBooleanFlag(params.HTTPRequest.Context(), appCtx.Logger(), "", featureFlagName, map[string]string{}) + if err != nil { + appCtx.Logger().Error("Error fetching feature flag", zap.String("featureFlagKey", featureFlagName), zap.Error(err)) + } else { + isAlaskaEnabled = flag.Match + } + + /** Turn on/off international crating/uncrating service items **/ + if !isAlaskaEnabled { + delete(CreateableServiceItemMap, primev3messages.MTOServiceItemModelTypeMTOServiceItemInternationalCrating) + } + // restrict creation to a list if _, ok := primeapi.CreateableServiceItemMap[params.Body.ModelType()]; !ok { // throw error if modelType() not on the list diff --git a/pkg/handlers/primeapiv3/mto_shipment.go b/pkg/handlers/primeapiv3/mto_shipment.go index 95aa36d94f0..00130a14323 100644 --- a/pkg/handlers/primeapiv3/mto_shipment.go +++ b/pkg/handlers/primeapiv3/mto_shipment.go @@ -142,6 +142,9 @@ func (h CreateMTOShipmentHandler) Handle(params mtoshipmentops.CreateMTOShipment case apperror.NotFoundError: return mtoshipmentops.NewCreateMTOShipmentNotFound().WithPayload( payloads.ClientError(handlers.NotFoundMessage, err.Error(), h.GetTraceIDFromRequest(params.HTTPRequest))), err + case apperror.EventError: + return mtoshipmentops.NewUpdateMTOShipmentBadRequest().WithPayload( + payloads.ClientError(handlers.InternalServerErrMessage, err.Error(), h.GetTraceIDFromRequest(params.HTTPRequest))), err case apperror.InvalidInputError: return mtoshipmentops.NewCreateMTOShipmentUnprocessableEntity().WithPayload( payloads.ValidationError(err.Error(), h.GetTraceIDFromRequest(params.HTTPRequest), e.ValidationErrors)), err @@ -206,6 +209,9 @@ func (h UpdateMTOShipmentHandler) Handle(params mtoshipmentops.UpdateMTOShipment case apperror.NotFoundError: return mtoshipmentops.NewUpdateMTOShipmentNotFound().WithPayload( payloads.ClientError(handlers.NotFoundMessage, err.Error(), h.GetTraceIDFromRequest(params.HTTPRequest))), err + case apperror.EventError: + return mtoshipmentops.NewUpdateMTOShipmentBadRequest().WithPayload( + payloads.ClientError(handlers.InternalServerErrMessage, err.Error(), h.GetTraceIDFromRequest(params.HTTPRequest))), err case apperror.InvalidInputError: payload := payloads.ValidationError(err.Error(), h.GetTraceIDFromRequest(params.HTTPRequest), e.ValidationErrors) return mtoshipmentops.NewUpdateMTOShipmentUnprocessableEntity().WithPayload(payload), err diff --git a/pkg/handlers/primeapiv3/mto_shipment_test.go b/pkg/handlers/primeapiv3/mto_shipment_test.go index b5c08ce6734..71d1d07f37c 100644 --- a/pkg/handlers/primeapiv3/mto_shipment_test.go +++ b/pkg/handlers/primeapiv3/mto_shipment_test.go @@ -95,10 +95,11 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { var pickupAddress primev3messages.Address var secondaryPickupAddress primev3messages.Address + var tertiaryPickupAddress primev3messages.Address var destinationAddress primev3messages.Address var ppmDestinationAddress primev3messages.PPMDestinationAddress var secondaryDestinationAddress primev3messages.Address - + var tertiaryDestinationAddress primev3messages.Address creator := paymentrequest.NewPaymentRequestCreator(planner, ghcrateengine.NewServiceItemPricer()) statusUpdater := paymentrequest.NewPaymentRequestStatusUpdater(query.NewQueryBuilder()) recalculator := paymentrequest.NewPaymentRequestRecalculator(creator, statusUpdater) @@ -211,6 +212,22 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { StreetAddress2: newAddress.StreetAddress2, StreetAddress3: newAddress.StreetAddress3, } + secondaryPickupAddress = primev3messages.Address{ + City: &newAddress.City, + PostalCode: &newAddress.PostalCode, + State: &newAddress.State, + StreetAddress1: &newAddress.StreetAddress1, + StreetAddress2: newAddress.StreetAddress2, + StreetAddress3: newAddress.StreetAddress3, + } + tertiaryPickupAddress = primev3messages.Address{ + City: &newAddress.City, + PostalCode: &newAddress.PostalCode, + State: &newAddress.State, + StreetAddress1: &newAddress.StreetAddress1, + StreetAddress2: newAddress.StreetAddress2, + StreetAddress3: newAddress.StreetAddress3, + } newAddress = factory.BuildAddress(nil, nil, []factory.Trait{factory.GetTraitAddress2}) destinationAddress = primev3messages.Address{ City: &newAddress.City, @@ -220,6 +237,22 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { StreetAddress2: newAddress.StreetAddress2, StreetAddress3: newAddress.StreetAddress3, } + secondaryDestinationAddress = primev3messages.Address{ + City: &newAddress.City, + PostalCode: &newAddress.PostalCode, + State: &newAddress.State, + StreetAddress1: &newAddress.StreetAddress1, + StreetAddress2: newAddress.StreetAddress2, + StreetAddress3: newAddress.StreetAddress3, + } + tertiaryDestinationAddress = primev3messages.Address{ + City: &newAddress.City, + PostalCode: &newAddress.PostalCode, + State: &newAddress.State, + StreetAddress1: &newAddress.StreetAddress1, + StreetAddress2: newAddress.StreetAddress2, + StreetAddress3: newAddress.StreetAddress3, + } return handler, move } @@ -338,6 +371,13 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { PostalCode: "62225", } + address3 := models.Address{ + StreetAddress1: "some address", + City: "city", + State: "VA", + PostalCode: "23435", + } + expectedPickupAddress := address1 pickupAddress = primev3messages.Address{ City: &expectedPickupAddress.City, @@ -358,6 +398,16 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { StreetAddress3: expectedSecondaryPickupAddress.StreetAddress3, } + expectedTertiaryPickupAddress := address3 + tertiaryDestinationAddress = primev3messages.Address{ + City: &expectedTertiaryPickupAddress.City, + PostalCode: &expectedTertiaryPickupAddress.PostalCode, + State: &expectedTertiaryPickupAddress.State, + StreetAddress1: &expectedTertiaryPickupAddress.StreetAddress1, + StreetAddress2: expectedTertiaryPickupAddress.StreetAddress2, + StreetAddress3: expectedTertiaryPickupAddress.StreetAddress3, + } + expectedDestinationAddress := address1 destinationAddress = primev3messages.Address{ City: &expectedDestinationAddress.City, @@ -386,6 +436,16 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { StreetAddress3: expectedSecondaryDestinationAddress.StreetAddress3, } + expectedTertiaryDestinationAddress := address3 + tertiaryDestinationAddress = primev3messages.Address{ + City: &expectedTertiaryDestinationAddress.City, + PostalCode: &expectedTertiaryDestinationAddress.PostalCode, + State: &expectedTertiaryDestinationAddress.State, + StreetAddress1: &expectedTertiaryDestinationAddress.StreetAddress1, + StreetAddress2: expectedTertiaryDestinationAddress.StreetAddress2, + StreetAddress3: expectedTertiaryDestinationAddress.StreetAddress3, + } + params := mtoshipmentops.CreateMTOShipmentParams{ HTTPRequest: req, Body: &primev3messages.CreateMTOShipment{ @@ -419,6 +479,12 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { mock.AnythingOfType("*models.PPMShipment")). Return(models.CentPointer(unit.Cents(estimatedIncentive)), models.CentPointer(unit.Cents(sitEstimatedCost)), nil).Once() + ppmEstimator.On("MaxIncentive", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("models.PPMShipment"), + mock.AnythingOfType("*models.PPMShipment")). + Return(nil, nil) + // Validate incoming payload suite.NoError(params.Body.Validate(strfmt.Default)) @@ -465,7 +531,13 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { mock.AnythingOfType("*appcontext.appContext"), mock.AnythingOfType("models.PPMShipment"), mock.AnythingOfType("*models.PPMShipment")). - Return(models.CentPointer(unit.Cents(estimatedIncentive)), models.CentPointer(unit.Cents(sitEstimatedCost)), nil).Times(2) + Return(models.CentPointer(unit.Cents(estimatedIncentive)), models.CentPointer(unit.Cents(sitEstimatedCost)), nil).Times(4) + + ppmEstimator.On("MaxIncentive", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("models.PPMShipment"), + mock.AnythingOfType("*models.PPMShipment")). + Return(nil, nil) ppmEstimator.On("FinalIncentiveWithDefaultChecks", mock.AnythingOfType("*appcontext.appContext"), @@ -525,6 +597,7 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { suite.NoError(err) eTag = etag.GenerateEtag(mtoShipment.UpdatedAt) patchParams.IfMatch = eTag + patchParams.MtoShipmentID = createdPPM.ShipmentID patchParams.Body.PpmShipment = &primev3messages.UpdatePPMShipment{ HasProGear: &hasProGear, HasSecondaryPickupAddress: models.BoolPointer(false), @@ -545,6 +618,71 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { suite.Nil(updatedPPM.SecondaryDestinationAddress) suite.False(*models.BoolPointer(*updatedPPM.HasSecondaryPickupAddress)) suite.False(*models.BoolPointer(*updatedPPM.HasSecondaryDestinationAddress)) + + // ************************************************************************************* + // ************************************************************************************* + // Run it third time, but really add secondary addresses with has flags set to true + // ************************************************************************************* + eTag = etag.GenerateEtag(time.Time(updatedShipment.UpdatedAt)) + patchParams.IfMatch = eTag + patchParams.MtoShipmentID = createdPPM.ShipmentID + + patchParams.Body.PpmShipment = &primev3messages.UpdatePPMShipment{ + HasProGear: &hasProGear, + HasSecondaryPickupAddress: models.BoolPointer(true), + HasSecondaryDestinationAddress: models.BoolPointer(true), + SecondaryPickupAddress: struct{ primev3messages.Address }{secondaryPickupAddress}, + SecondaryDestinationAddress: struct{ primev3messages.Address }{secondaryDestinationAddress}, + } + + // Validate incoming payload + suite.NoError(patchParams.Body.Validate(strfmt.Default)) + + patchResponse = patchHandler.Handle(patchParams) + suite.IsType(&mtoshipmentops.UpdateMTOShipmentOK{}, patchResponse) + okPatchResponse = patchResponse.(*mtoshipmentops.UpdateMTOShipmentOK) + updatedShipment = okPatchResponse.Payload + + // Validate outgoing payload + suite.NoError(updatedShipment.Validate(strfmt.Default)) + + updatedPPM = updatedShipment.PpmShipment + suite.NotNil(updatedPPM.SecondaryPickupAddress) + suite.NotNil(updatedPPM.SecondaryDestinationAddress) + suite.True(*models.BoolPointer(*updatedPPM.HasSecondaryPickupAddress)) + suite.True(*models.BoolPointer(*updatedPPM.HasSecondaryDestinationAddress)) + + // ************************************************************************************* + // ************************************************************************************* + // Run it fourth time, but really add tertiary addresses with has flags set to true + // ************************************************************************************* + eTag = etag.GenerateEtag(time.Time(updatedShipment.UpdatedAt)) + patchParams.IfMatch = eTag + patchParams.MtoShipmentID = updatedPPM.ShipmentID + patchParams.Body.PpmShipment = &primev3messages.UpdatePPMShipment{ + HasProGear: &hasProGear, + HasTertiaryPickupAddress: models.BoolPointer(true), + HasTertiaryDestinationAddress: models.BoolPointer(true), + TertiaryPickupAddress: struct{ primev3messages.Address }{tertiaryPickupAddress}, + TertiaryDestinationAddress: struct{ primev3messages.Address }{tertiaryDestinationAddress}, + } + + // Validate incoming payload + suite.NoError(patchParams.Body.Validate(strfmt.Default)) + + patchResponse = patchHandler.Handle(patchParams) + suite.IsType(&mtoshipmentops.UpdateMTOShipmentOK{}, patchResponse) + okPatchResponse = patchResponse.(*mtoshipmentops.UpdateMTOShipmentOK) + updatedShipment = okPatchResponse.Payload + + // Validate outgoing payload + suite.NoError(updatedShipment.Validate(strfmt.Default)) + + updatedPPM = updatedShipment.PpmShipment + suite.NotNil(updatedPPM.TertiaryPickupAddress) + suite.NotNil(updatedPPM.TertiaryDestinationAddress) + suite.True(*models.BoolPointer(*updatedPPM.HasTertiaryPickupAddress)) + suite.True(*models.BoolPointer(*updatedPPM.HasTertiaryDestinationAddress)) }) suite.Run("Successful POST/PATCH - Integration Test (PPM) - Destination address street 1 OPTIONAL", func() { @@ -640,6 +778,12 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { mock.AnythingOfType("*models.PPMShipment")). Return(models.CentPointer(unit.Cents(estimatedIncentive)), models.CentPointer(unit.Cents(sitEstimatedCost)), nil).Once() + ppmEstimator.On("MaxIncentive", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("models.PPMShipment"), + mock.AnythingOfType("*models.PPMShipment")). + Return(nil, nil) + // Validate incoming payload suite.NoError(params.Body.Validate(strfmt.Default)) @@ -670,6 +814,12 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { mock.AnythingOfType("*models.PPMShipment")). Return(models.CentPointer(unit.Cents(estimatedIncentive)), models.CentPointer(unit.Cents(sitEstimatedCost)), nil).Times(2) + ppmEstimator.On("MaxIncentive", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("models.PPMShipment"), + mock.AnythingOfType("*models.PPMShipment")). + Return(nil, nil) + ppmEstimator.On("FinalIncentiveWithDefaultChecks", mock.AnythingOfType("*appcontext.appContext"), mock.AnythingOfType("models.PPMShipment"), @@ -1005,6 +1155,42 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { suite.Contains(*typedResponse.Payload.Detail, unavailableMove.ID.String()) }) + suite.Run("POST failure - 500 - App Event Internal DTOD Server Error", func() { + // Under Test: CreateMTOShipmentHandler + // Setup: Create a shipment with DTOD outage simulated or bad zip + // Expected: 500 Internal Server Error returned + + handler, move := setupTestData(true, false) + req := httptest.NewRequest("POST", "/mto-shipments", nil) + handler.ShipmentCreator = &mockCreator + + err := apperror.EventError{} + + mockCreator.On("CreateShipment", + mock.AnythingOfType("*appcontext.appContext"), + mock.Anything, + ).Return(nil, nil, err) + + params := mtoshipmentops.CreateMTOShipmentParams{ + HTTPRequest: req, + Body: &primev3messages.CreateMTOShipment{ + MoveTaskOrderID: handlers.FmtUUID(move.ID), + Agents: nil, + CustomerRemarks: nil, + PointOfContact: "John Doe", + RequestedPickupDate: handlers.FmtDatePtr(models.TimePointer(time.Now())), + ShipmentType: primev3messages.NewMTOShipmentType(primev3messages.MTOShipmentTypeHHG), + PickupAddress: struct{ primev3messages.Address }{pickupAddress}, + DestinationAddress: struct{ primev3messages.Address }{destinationAddress}, + }, + } + + response := handler.Handle(params) + suite.IsType(&mtoshipmentops.CreateMTOShipmentInternalServerError{}, response) + typedResponse := response.(*mtoshipmentops.CreateMTOShipmentInternalServerError) + suite.Contains(*typedResponse.Payload.Detail, "An internal server error has occurred") + }) + suite.Run("POST failure - 422 - MTO Shipment object not formatted correctly", func() { // Under Test: CreateMTOShipmentHandler // Setup: Create a shipment with service items that don't match the modeltype @@ -1169,4 +1355,420 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { suite.Contains(*errResponse.Payload.Detail, "Unaccompanied baggage shipments can't be created unless the unaccompanied_baggage feature flag is enabled.") }) + + suite.Run("POST failure - Error creating a mto shipment contains tertiary destination address no secondary destination address.", func() { + // Under Test: CreateMTOShipmentHandler + // Setup: If underlying CreateMTOShipment returns error, handler should return 422 response + // Expected: 422 Response returned + + handler, move := setupTestData(false, false) + + req := httptest.NewRequest("POST", "/mto-shipments", nil) + + newAddress := factory.BuildAddress(nil, []factory.Customization{ + { + Model: models.Address{ + ID: uuid.Must(uuid.NewV4()), + }, + }, + }, nil) + destinationAddress = primev3messages.Address{ + City: &newAddress.City, + PostalCode: &newAddress.PostalCode, + State: &newAddress.State, + StreetAddress1: &newAddress.StreetAddress1, + StreetAddress2: newAddress.StreetAddress2, + StreetAddress3: newAddress.StreetAddress3, + } + destinationAddress = primev3messages.Address{ + City: &newAddress.City, + PostalCode: &newAddress.PostalCode, + State: &newAddress.State, + StreetAddress1: &newAddress.StreetAddress1, + StreetAddress2: newAddress.StreetAddress2, + StreetAddress3: newAddress.StreetAddress3, + } + tertiaryDestinationAddress = primev3messages.Address{ + City: &newAddress.City, + PostalCode: &newAddress.PostalCode, + State: &newAddress.State, + StreetAddress1: &newAddress.StreetAddress1, + StreetAddress2: newAddress.StreetAddress2, + StreetAddress3: newAddress.StreetAddress3, + } + + params := mtoshipmentops.CreateMTOShipmentParams{ + HTTPRequest: req, + Body: &primev3messages.CreateMTOShipment{ + MoveTaskOrderID: handlers.FmtUUID(move.ID), + Agents: nil, + CustomerRemarks: nil, + PointOfContact: "John Doe", + PrimeEstimatedWeight: handlers.FmtInt64(1200), + RequestedPickupDate: handlers.FmtDatePtr(models.TimePointer(time.Now())), + ShipmentType: primev3messages.NewMTOShipmentType(primev3messages.MTOShipmentTypeHHG), + PickupAddress: struct{ primev3messages.Address }{pickupAddress}, + DestinationAddress: struct{ primev3messages.Address }{destinationAddress}, + TertiaryDestinationAddress: struct{ primev3messages.Address }{tertiaryDestinationAddress}, + }, + } + + // Validate incoming payload + suite.NoError(params.Body.Validate(strfmt.Default)) + + response := handler.Handle(params) + suite.IsType(&mtoshipmentops.CreateMTOShipmentUnprocessableEntity{}, response) + errResponse := response.(*mtoshipmentops.CreateMTOShipmentUnprocessableEntity) + + suite.Contains(*errResponse.Payload.Detail, "Invalid input found while validating the MTO shipment") + suite.Contains(errResponse.Payload.InvalidFields["error validating mto shipment"][0], "Shipment cannot have a third address without a second address present") + }) + + suite.Run("POST failure - Error creating an mto shipment with ppm shipment contains tertiary pickup address no secondary pickup address.", func() { + // Under Test: UpdateMTOShipmentHandler + // Setup: If underlying UpdateMTOShipment returns error, handler should return 422 response + // Expected: 422 Response returned + + handler, move := setupTestData(false, false) + + req := httptest.NewRequest("POST", "/mto-shipments", nil) + + expectedDepartureDate := time.Now().AddDate(0, 0, 10) + sitExpected := true + estimatedWeight := unit.Pound(1500) + hasProGear := true + ppmShipmentDestinationAddress := primev3messages.PPMDestinationAddress{ + City: destinationAddress.City, + PostalCode: destinationAddress.PostalCode, + State: destinationAddress.State, + StreetAddress1: destinationAddress.StreetAddress1, + StreetAddress2: destinationAddress.StreetAddress2, + StreetAddress3: destinationAddress.StreetAddress3, + } + ppmShipmentParams := primev3messages.CreatePPMShipment{ + PickupAddress: struct{ primev3messages.Address }{pickupAddress}, + DestinationAddress: struct { + primev3messages.PPMDestinationAddress + }{ppmShipmentDestinationAddress}, + TertiaryPickupAddress: struct{ primev3messages.Address }{tertiaryPickupAddress}, + ExpectedDepartureDate: handlers.FmtDate(expectedDepartureDate), + EstimatedWeight: handlers.FmtPoundPtr(&estimatedWeight), + HasProGear: &hasProGear, + SitExpected: &sitExpected, + } + params := mtoshipmentops.CreateMTOShipmentParams{ + HTTPRequest: req, + Body: &primev3messages.CreateMTOShipment{ + MoveTaskOrderID: handlers.FmtUUID(move.ID), + Agents: nil, + CustomerRemarks: nil, + PointOfContact: "John Doe", + PrimeEstimatedWeight: handlers.FmtInt64(1200), + RequestedPickupDate: handlers.FmtDatePtr(models.TimePointer(time.Now())), + ShipmentType: primev3messages.NewMTOShipmentType(primev3messages.MTOShipmentTypePPM), + PickupAddress: struct{ primev3messages.Address }{pickupAddress}, + DestinationAddress: struct{ primev3messages.Address }{destinationAddress}, + PpmShipment: &ppmShipmentParams, + }, + } + + // Validate incoming payload + suite.NoError(params.Body.Validate(strfmt.Default)) + + response := handler.Handle(params) + suite.IsType(&mtoshipmentops.CreateMTOShipmentUnprocessableEntity{}, response) + errResponse := response.(*mtoshipmentops.CreateMTOShipmentUnprocessableEntity) + + suite.Contains(*errResponse.Payload.Detail, "Invalid input found while validating the PPM shipment.") + suite.Contains(errResponse.Payload.InvalidFields["error validating ppm shipment"][0], "Shipment cannot have a third address without a second address present") + }) + + suite.Run("POST failure - Error creating mto shipment containing a ppm shipment contains tertiary destination address no secondary destination address.", func() { + // Under Test: UpdateMTOShipmentHandler + // Setup: If underlying UpdateMTOShipment returns error, handler should return 422 response + // Expected: 422 Response returned + + handler, move := setupTestData(false, false) + + req := httptest.NewRequest("POST", "/mto-shipments", nil) + + expectedDepartureDate := time.Now().AddDate(0, 0, 10) + sitExpected := true + estimatedWeight := unit.Pound(1500) + hasProGear := true + ppmShipmentDestinationAddress := primev3messages.PPMDestinationAddress{ + City: destinationAddress.City, + PostalCode: destinationAddress.PostalCode, + State: destinationAddress.State, + StreetAddress1: destinationAddress.StreetAddress1, + StreetAddress2: destinationAddress.StreetAddress2, + StreetAddress3: destinationAddress.StreetAddress3, + } + ppmShipmentParams := primev3messages.CreatePPMShipment{ + PickupAddress: struct{ primev3messages.Address }{pickupAddress}, + DestinationAddress: struct { + primev3messages.PPMDestinationAddress + }{ppmShipmentDestinationAddress}, + TertiaryDestinationAddress: struct{ primev3messages.Address }{tertiaryDestinationAddress}, + ExpectedDepartureDate: handlers.FmtDate(expectedDepartureDate), + EstimatedWeight: handlers.FmtPoundPtr(&estimatedWeight), + HasProGear: &hasProGear, + SitExpected: &sitExpected, + } + params := mtoshipmentops.CreateMTOShipmentParams{ + HTTPRequest: req, + Body: &primev3messages.CreateMTOShipment{ + MoveTaskOrderID: handlers.FmtUUID(move.ID), + Agents: nil, + CustomerRemarks: nil, + PointOfContact: "John Doe", + PrimeEstimatedWeight: handlers.FmtInt64(1200), + RequestedPickupDate: handlers.FmtDatePtr(models.TimePointer(time.Now())), + ShipmentType: primev3messages.NewMTOShipmentType(primev3messages.MTOShipmentTypePPM), + PickupAddress: struct{ primev3messages.Address }{pickupAddress}, + DestinationAddress: struct{ primev3messages.Address }{destinationAddress}, + PpmShipment: &ppmShipmentParams, + }, + } + + // Validate incoming payload + suite.NoError(params.Body.Validate(strfmt.Default)) + + response := handler.Handle(params) + suite.IsType(&mtoshipmentops.CreateMTOShipmentUnprocessableEntity{}, response) + errResponse := response.(*mtoshipmentops.CreateMTOShipmentUnprocessableEntity) + + suite.Contains(*errResponse.Payload.Detail, "Invalid input found while validating the PPM shipment.") + suite.Contains(errResponse.Payload.InvalidFields["error validating ppm shipment"][0], "Shipment cannot have a third address without a second address present") + }) + suite.Run("PATCH failure - Error updating an mto shipment contains tertiary pickup address no secondary pickup address.", func() { + // Under Test: UpdateMTOShipmentHandler + // Setup: If underlying CreateMTOShipment returns error, handler should return 422 response + // Expected: 422 Response returned + + shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater) + patchHandler := UpdateMTOShipmentHandler{ + suite.HandlerConfig(), + shipmentUpdater, + } + + now := time.Now() + mto_shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "some address", + City: "city", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.PickupAddress, + }, + { + Model: models.Address{ + StreetAddress1: "some address", + City: "city", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.DeliveryAddress, + }, + }, nil) + move := factory.BuildMoveWithPPMShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + AvailableToPrimeAt: &now, + ApprovedAt: &now, + Status: models.MoveStatusAPPROVED, + }, + }, + }, nil) + + var testMove models.Move + err := suite.DB().EagerPreload("MTOShipments.PPMShipment").Find(&testMove, move.ID) + suite.NoError(err) + var testMtoShipment models.MTOShipment + err = suite.DB().Find(&testMtoShipment, mto_shipment.ID) + suite.NoError(err) + testMtoShipment.MoveTaskOrderID = testMove.ID + testMtoShipment.MoveTaskOrder = testMove + err = suite.DB().Save(&testMtoShipment) + suite.NoError(err) + testMove.MTOShipments = append(testMove.MTOShipments, mto_shipment) + err = suite.DB().Save(&testMove) + suite.NoError(err) + + patchReq := httptest.NewRequest("PATCH", fmt.Sprintf("/mto-shipments/%s", testMove.MTOShipments[0].ID), nil) + + eTag := etag.GenerateEtag(testMove.MTOShipments[0].UpdatedAt) + patchParams := mtoshipmentops.UpdateMTOShipmentParams{ + HTTPRequest: patchReq, + MtoShipmentID: strfmt.UUID(testMove.MTOShipments[0].ID.String()), + IfMatch: eTag, + } + tertiaryAddress := GetTestAddress() + patchParams.Body = &primev3messages.UpdateMTOShipment{ + TertiaryDeliveryAddress: struct{ primev3messages.Address }{tertiaryAddress}, + } + patchResponse := patchHandler.Handle(patchParams) + errResponse := patchResponse.(*mtoshipmentops.UpdateMTOShipmentUnprocessableEntity) + suite.Contains(*errResponse.Payload.Detail, "Invalid input found while validating the MTO shipment") + suite.Contains(errResponse.Payload.InvalidFields["error validating mto shipment"][0], "Shipment cannot have a third address without a second address present") + + }) + suite.Run("PATCH failure - Error updating an ppm shipment contains tertiary destination address no secondary destination address", func() { + // Under Test: UpdateMTOShipmentHandler + // Mocked: UpdateMTOShipment creator + // Setup: If underlying UpdateMTOShipment returns error, handler should return 422 response + // Expected: 422 Response returned + + shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater) + patchHandler := UpdateMTOShipmentHandler{ + suite.HandlerConfig(), + shipmentUpdater, + } + + move := factory.BuildAvailableToPrimeMove(suite.DB(), []factory.Customization{}, nil) + factory.BuildPPMShipment(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + }, nil) + + var testMove models.Move + err := suite.DB().EagerPreload("MTOShipments.PPMShipment").Find(&testMove, move.ID) + suite.NoError(err) + + patchReq := httptest.NewRequest("PATCH", fmt.Sprintf("/mto-shipments/%s", testMove.MTOShipments[0].ID.String()), nil) + + eTag := etag.GenerateEtag(testMove.MTOShipments[0].UpdatedAt) + patchParams := mtoshipmentops.UpdateMTOShipmentParams{ + HTTPRequest: patchReq, + MtoShipmentID: strfmt.UUID(testMove.MTOShipments[0].ID.String()), + IfMatch: eTag, + } + tertiaryAddress := GetTestAddress() + ppmShipmentParamSetup := primev3messages.UpdatePPMShipment{ + HasTertiaryDestinationAddress: models.BoolPointer(true), + TertiaryDestinationAddress: struct{ primev3messages.Address }{tertiaryAddress}, + } + mtoShipmentParamSetup := primev3messages.UpdateMTOShipment{ + PpmShipment: &ppmShipmentParamSetup, + } + + patchParams.Body = &mtoShipmentParamSetup + patchResponse := patchHandler.Handle(patchParams) + errResponse := patchResponse.(*mtoshipmentops.UpdateMTOShipmentUnprocessableEntity) + suite.Contains(*errResponse.Payload.Detail, "Invalid input found while validating the PPM shipment") + suite.Contains(errResponse.Payload.InvalidFields["error validating ppm shipment"][0], "Shipment cannot have a third address without a second address present") + + }) + + suite.Run("PATCH sucess - updating an mto shipment contains tertiary pickup and secondary pickup address.", func() { + // Under Test: UpdateMTOShipmentHandler + // Setup: If underlying CreateMTOShipment returns error, handler should return 422 response + // Expected: 422 Response returned + + shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater) + patchHandler := UpdateMTOShipmentHandler{ + suite.HandlerConfig(), + shipmentUpdater, + } + + now := time.Now() + mto_shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "some address", + City: "city", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.PickupAddress, + }, + { + Model: models.Address{ + StreetAddress1: "some address", + City: "city", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.SecondaryPickupAddress, + }, + { + Model: models.Address{ + StreetAddress1: "some address", + City: "city", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.DeliveryAddress, + }, + { + Model: models.Address{ + StreetAddress1: "some address", + City: "city", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.SecondaryDeliveryAddress, + }, + }, nil) + move := factory.BuildMoveWithPPMShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + AvailableToPrimeAt: &now, + ApprovedAt: &now, + Status: models.MoveStatusAPPROVED, + }, + }, + }, nil) + + var testMove models.Move + err := suite.DB().EagerPreload("MTOShipments.PPMShipment").Find(&testMove, move.ID) + suite.NoError(err) + var testMtoShipment models.MTOShipment + err = suite.DB().Find(&testMtoShipment, mto_shipment.ID) + suite.NoError(err) + testMtoShipment.MoveTaskOrderID = testMove.ID + testMtoShipment.MoveTaskOrder = testMove + err = suite.DB().Save(&testMtoShipment) + suite.NoError(err) + testMove.MTOShipments = append(testMove.MTOShipments, mto_shipment) + err = suite.DB().Save(&testMove) + suite.NoError(err) + + patchReq := httptest.NewRequest("PATCH", fmt.Sprintf("/mto-shipments/%s", testMove.MTOShipments[0].ID), nil) + + eTag := etag.GenerateEtag(testMtoShipment.UpdatedAt) + patchParams := mtoshipmentops.UpdateMTOShipmentParams{ + HTTPRequest: patchReq, + MtoShipmentID: strfmt.UUID(testMtoShipment.ID.String()), + IfMatch: eTag, + } + tertiaryAddress := GetTestAddress() + patchParams.Body = &primev3messages.UpdateMTOShipment{ + TertiaryDeliveryAddress: struct{ primev3messages.Address }{tertiaryAddress}, + } + patchResponse := patchHandler.Handle(patchParams) + response := patchResponse.(*mtoshipmentops.UpdateMTOShipmentOK) + suite.IsType(&mtoshipmentops.UpdateMTOShipmentOK{}, response) + }) +} +func GetTestAddress() primev3messages.Address { + newAddress := factory.BuildAddress(nil, []factory.Customization{ + { + Model: models.Address{ + ID: uuid.Must(uuid.NewV4()), + }, + }, + }, nil) + return primev3messages.Address{ + City: &newAddress.City, + PostalCode: &newAddress.PostalCode, + State: &newAddress.State, + StreetAddress1: &newAddress.StreetAddress1, + StreetAddress2: newAddress.StreetAddress2, + StreetAddress3: newAddress.StreetAddress3, + } } diff --git a/pkg/handlers/primeapiv3/payloads/model_to_payload.go b/pkg/handlers/primeapiv3/payloads/model_to_payload.go index 17a027a1758..ab2655b1bd8 100644 --- a/pkg/handlers/primeapiv3/payloads/model_to_payload.go +++ b/pkg/handlers/primeapiv3/payloads/model_to_payload.go @@ -749,6 +749,46 @@ func MTOServiceItem(mtoServiceItem *models.MTOServiceItem) primev3messages.MTOSe Width: crate.Width.Int32Ptr(), } payload = &cratingSI + + case models.ReServiceCodeICRT, models.ReServiceCodeIUCRT: + item := GetDimension(mtoServiceItem.Dimensions, models.DimensionTypeItem) + crate := GetDimension(mtoServiceItem.Dimensions, models.DimensionTypeCrate) + cratingSI := primev3messages.MTOServiceItemInternationalCrating{ + ReServiceCode: handlers.FmtString(string(mtoServiceItem.ReService.Code)), + Description: mtoServiceItem.Description, + Reason: mtoServiceItem.Reason, + StandaloneCrate: mtoServiceItem.StandaloneCrate, + ExternalCrate: mtoServiceItem.ExternalCrate, + } + cratingSI.Item.MTOServiceItemDimension = primev3messages.MTOServiceItemDimension{ + ID: strfmt.UUID(item.ID.String()), + Height: item.Height.Int32Ptr(), + Length: item.Length.Int32Ptr(), + Width: item.Width.Int32Ptr(), + } + cratingSI.Crate.MTOServiceItemDimension = primev3messages.MTOServiceItemDimension{ + ID: strfmt.UUID(crate.ID.String()), + Height: crate.Height.Int32Ptr(), + Length: crate.Length.Int32Ptr(), + Width: crate.Width.Int32Ptr(), + } + if mtoServiceItem.ReService.Code == models.ReServiceCodeICRT && mtoServiceItem.MTOShipment.PickupAddress != nil { + if *mtoServiceItem.MTOShipment.PickupAddress.IsOconus { + cratingSI.Market = models.MarketOconus.FullString() + } else { + cratingSI.Market = models.MarketConus.FullString() + } + } + + if mtoServiceItem.ReService.Code == models.ReServiceCodeIUCRT && mtoServiceItem.MTOShipment.DestinationAddress != nil { + if *mtoServiceItem.MTOShipment.DestinationAddress.IsOconus { + cratingSI.Market = models.MarketOconus.FullString() + } else { + cratingSI.Market = models.MarketConus.FullString() + } + } + payload = &cratingSI + case models.ReServiceCodeDDSHUT, models.ReServiceCodeDOSHUT: payload = &primev3messages.MTOServiceItemShuttle{ ReServiceCode: handlers.FmtString(string(mtoServiceItem.ReService.Code)), diff --git a/pkg/handlers/primeapiv3/payloads/model_to_payload_test.go b/pkg/handlers/primeapiv3/payloads/model_to_payload_test.go index 29ea4d19d78..c22dea1582d 100644 --- a/pkg/handlers/primeapiv3/payloads/model_to_payload_test.go +++ b/pkg/handlers/primeapiv3/payloads/model_to_payload_test.go @@ -733,6 +733,76 @@ func (suite *PayloadsSuite) TestMTOServiceItemDCRT() { suite.True(ok) } +func (suite *PayloadsSuite) TestMTOServiceItemICRTandIUCRT() { + icrtReServiceCode := models.ReServiceCodeICRT + iucrtReServiceCode := models.ReServiceCodeIUCRT + reason := "reason" + standaloneCrate := false + externalCrate := false + dateOfContact1 := time.Now() + timeMilitary1 := "1500Z" + firstAvailableDeliveryDate1 := dateOfContact1.AddDate(0, 0, 10) + dateOfContact2 := time.Now().AddDate(0, 0, 5) + timeMilitary2 := "1300Z" + firstAvailableDeliveryDate2 := dateOfContact2.AddDate(0, 0, 10) + + mtoServiceItemICRT := &models.MTOServiceItem{ + ID: uuid.Must(uuid.NewV4()), + ReService: models.ReService{Code: icrtReServiceCode}, + Reason: &reason, + StandaloneCrate: &standaloneCrate, + ExternalCrate: &externalCrate, + CustomerContacts: models.MTOServiceItemCustomerContacts{ + models.MTOServiceItemCustomerContact{ + DateOfContact: dateOfContact1, + TimeMilitary: timeMilitary1, + FirstAvailableDeliveryDate: firstAvailableDeliveryDate1, + Type: models.CustomerContactTypeFirst, + }, + models.MTOServiceItemCustomerContact{ + DateOfContact: dateOfContact2, + TimeMilitary: timeMilitary2, + FirstAvailableDeliveryDate: firstAvailableDeliveryDate2, + Type: models.CustomerContactTypeSecond, + }, + }, + } + + mtoServiceItemIUCRT := &models.MTOServiceItem{ + ID: uuid.Must(uuid.NewV4()), + ReService: models.ReService{Code: iucrtReServiceCode}, + Reason: &reason, + StandaloneCrate: &standaloneCrate, + ExternalCrate: &externalCrate, + CustomerContacts: models.MTOServiceItemCustomerContacts{ + models.MTOServiceItemCustomerContact{ + DateOfContact: dateOfContact1, + TimeMilitary: timeMilitary1, + FirstAvailableDeliveryDate: firstAvailableDeliveryDate1, + Type: models.CustomerContactTypeFirst, + }, + models.MTOServiceItemCustomerContact{ + DateOfContact: dateOfContact2, + TimeMilitary: timeMilitary2, + FirstAvailableDeliveryDate: firstAvailableDeliveryDate2, + Type: models.CustomerContactTypeSecond, + }, + }, + } + + resultICRT := MTOServiceItem(mtoServiceItemICRT) + resultIUCRT := MTOServiceItem(mtoServiceItemIUCRT) + + suite.NotNil(resultICRT) + suite.NotNil(resultIUCRT) + + _, ok := resultICRT.(*primev3messages.MTOServiceItemInternationalCrating) + suite.True(ok) + + _, ok = resultIUCRT.(*primev3messages.MTOServiceItemInternationalCrating) + suite.True(ok) +} + func (suite *PayloadsSuite) TestMTOServiceItemDDSHUT() { reServiceCode := models.ReServiceCodeDDSHUT reason := "reason" diff --git a/pkg/handlers/primeapiv3/payloads/payload_to_model.go b/pkg/handlers/primeapiv3/payloads/payload_to_model.go index 467dd95ec70..7eb48d87ad7 100644 --- a/pkg/handlers/primeapiv3/payloads/payload_to_model.go +++ b/pkg/handlers/primeapiv3/payloads/payload_to_model.go @@ -557,6 +557,8 @@ func PPMShipmentModelFromUpdate(ppmShipment *primev3messages.UpdatePPMShipment) SpouseProGearWeight: handlers.PoundPtrFromInt64Ptr(ppmShipment.SpouseProGearWeight), HasSecondaryPickupAddress: ppmShipment.HasSecondaryPickupAddress, HasSecondaryDestinationAddress: ppmShipment.HasSecondaryDestinationAddress, + HasTertiaryPickupAddress: ppmShipment.HasTertiaryPickupAddress, + HasTertiaryDestinationAddress: ppmShipment.HasTertiaryDestinationAddress, } // Set up address models @@ -795,6 +797,44 @@ func MTOServiceItemModel(mtoServiceItem primev3messages.MTOServiceItem) (*models Width: unit.ThousandthInches(*domesticCrating.Crate.Width), }, } + case primev3messages.MTOServiceItemModelTypeMTOServiceItemInternationalCrating: + internationalCrating := mtoServiceItem.(*primev3messages.MTOServiceItemInternationalCrating) + + // additional validation for this specific service item type + verrs := validateInternationalCrating(*internationalCrating) + if verrs.HasAny() { + return nil, verrs + } + + // have to get code from payload + model.ReService.Code = models.ReServiceCode(*internationalCrating.ReServiceCode) + model.Description = internationalCrating.Description + model.Reason = internationalCrating.Reason + model.StandaloneCrate = internationalCrating.StandaloneCrate + model.ExternalCrate = internationalCrating.ExternalCrate + + if model.ReService.Code == models.ReServiceCodeICRT { + if internationalCrating.StandaloneCrate == nil { + model.StandaloneCrate = models.BoolPointer(false) + } + if internationalCrating.ExternalCrate == nil { + model.ExternalCrate = models.BoolPointer(false) + } + } + model.Dimensions = models.MTOServiceItemDimensions{ + models.MTOServiceItemDimension{ + Type: models.DimensionTypeItem, + Length: unit.ThousandthInches(*internationalCrating.Item.Length), + Height: unit.ThousandthInches(*internationalCrating.Item.Height), + Width: unit.ThousandthInches(*internationalCrating.Item.Width), + }, + models.MTOServiceItemDimension{ + Type: models.DimensionTypeCrate, + Length: unit.ThousandthInches(*internationalCrating.Crate.Length), + Height: unit.ThousandthInches(*internationalCrating.Crate.Height), + Width: unit.ThousandthInches(*internationalCrating.Crate.Width), + }, + } default: // assume basic service item, take in provided re service code basic := mtoServiceItem.(*primev3messages.MTOServiceItemBasic) @@ -964,6 +1004,18 @@ func validateDomesticCrating(m primev3messages.MTOServiceItemDomesticCrating) *v ) } +// validateInternationalCrating validates this mto service item international crating +func validateInternationalCrating(m primev3messages.MTOServiceItemInternationalCrating) *validate.Errors { + return validate.Validate( + &models.ItemCanFitInsideCrateV3{ + Name: "Item", + NameCompared: "Crate", + Item: &m.Item.MTOServiceItemDimension, + Crate: &m.Crate.MTOServiceItemDimension, + }, + ) +} + // validateDDFSITForCreate validates DDFSIT service item has all required fields func validateDDFSITForCreate(m primev3messages.MTOServiceItemDestSIT) *validate.Errors { verrs := validate.NewErrors() diff --git a/pkg/handlers/primeapiv3/payloads/payload_to_model_test.go b/pkg/handlers/primeapiv3/payloads/payload_to_model_test.go index ba195e8da7d..7d3ffd37cf1 100644 --- a/pkg/handlers/primeapiv3/payloads/payload_to_model_test.go +++ b/pkg/handlers/primeapiv3/payloads/payload_to_model_test.go @@ -144,6 +144,130 @@ func (suite *PayloadsSuite) TestMTOServiceItemModel() { }) + suite.Run("Success - Returns a ICRT/IUCRT service item model", func() { + // ICRT + icrtCode := models.ReServiceCodeICRT.String() + externalCrate := false + ICRTServiceItem := &primev3messages.MTOServiceItemInternationalCrating{ + ReServiceCode: &icrtCode, + Reason: &reason, + Description: &description, + StandaloneCrate: &standaloneCrate, + ExternalCrate: &externalCrate, + } + ICRTServiceItem.Item.MTOServiceItemDimension = *item + ICRTServiceItem.Crate.MTOServiceItemDimension = *crate + + ICRTServiceItem.SetMoveTaskOrderID(handlers.FmtUUID(moveTaskOrderIDField)) + ICRTServiceItem.SetMtoShipmentID(*mtoShipmentIDString) + + returnedModel, verrs := MTOServiceItemModel(ICRTServiceItem) + + var returnedItem, returnedCrate models.MTOServiceItemDimension + for _, dimension := range returnedModel.Dimensions { + if dimension.Type == models.DimensionTypeItem { + returnedItem = dimension + } else { + returnedCrate = dimension + } + } + + suite.NoVerrs(verrs) + suite.Equal(moveTaskOrderIDField.String(), returnedModel.MoveTaskOrderID.String()) + suite.Equal(mtoShipmentIDField.String(), returnedModel.MTOShipmentID.String()) + suite.Equal(models.ReServiceCodeICRT, returnedModel.ReService.Code) + suite.Equal(ICRTServiceItem.Reason, returnedModel.Reason) + suite.Equal(ICRTServiceItem.Description, returnedModel.Description) + suite.Equal(ICRTServiceItem.StandaloneCrate, returnedModel.StandaloneCrate) + suite.Equal(ICRTServiceItem.ExternalCrate, returnedModel.ExternalCrate) + suite.Equal(unit.ThousandthInches(*ICRTServiceItem.Item.Length), returnedItem.Length) + suite.Equal(unit.ThousandthInches(*ICRTServiceItem.Crate.Length), returnedCrate.Length) + + // IUCRT + iucrtCode := models.ReServiceCodeIUCRT.String() + IUCRTServiceItem := &primev3messages.MTOServiceItemInternationalCrating{ + ReServiceCode: &iucrtCode, + Reason: &reason, + Description: &description, + } + IUCRTServiceItem.Item.MTOServiceItemDimension = *item + IUCRTServiceItem.Crate.MTOServiceItemDimension = *crate + + IUCRTServiceItem.SetMoveTaskOrderID(handlers.FmtUUID(moveTaskOrderIDField)) + IUCRTServiceItem.SetMtoShipmentID(*mtoShipmentIDString) + + iucrtReturnedModel, verrs := MTOServiceItemModel(IUCRTServiceItem) + + var icurtReturnedItem, icurtReturnedCrate models.MTOServiceItemDimension + for _, dimension := range iucrtReturnedModel.Dimensions { + if dimension.Type == models.DimensionTypeItem { + icurtReturnedItem = dimension + } else { + icurtReturnedCrate = dimension + } + } + + suite.NoVerrs(verrs) + suite.Equal(moveTaskOrderIDField.String(), iucrtReturnedModel.MoveTaskOrderID.String()) + suite.Equal(mtoShipmentIDField.String(), iucrtReturnedModel.MTOShipmentID.String()) + suite.Equal(models.ReServiceCodeIUCRT, iucrtReturnedModel.ReService.Code) + suite.Equal(IUCRTServiceItem.Reason, iucrtReturnedModel.Reason) + suite.Equal(IUCRTServiceItem.Description, iucrtReturnedModel.Description) + suite.Equal(unit.ThousandthInches(*ICRTServiceItem.Item.Length), icurtReturnedItem.Length) + suite.Equal(unit.ThousandthInches(*ICRTServiceItem.Crate.Length), icurtReturnedCrate.Length) + }) + + suite.Run("Fail - Returns error for ICRT/IUCRT service item because of validation error", func() { + // ICRT + icrtCode := models.ReServiceCodeICRT.String() + externalCrate := false + badCrateMeasurement := int32(200) + badCrate := &primev3messages.MTOServiceItemDimension{ + Height: &badCrateMeasurement, + Width: &badCrateMeasurement, + Length: &badCrateMeasurement, + } + + badICRTServiceItem := &primev3messages.MTOServiceItemInternationalCrating{ + ReServiceCode: &icrtCode, + Reason: &reason, + Description: &description, + StandaloneCrate: &standaloneCrate, + ExternalCrate: &externalCrate, + } + badICRTServiceItem.Item.MTOServiceItemDimension = *item + badICRTServiceItem.Crate.MTOServiceItemDimension = *badCrate + + badICRTServiceItem.SetMoveTaskOrderID(handlers.FmtUUID(moveTaskOrderIDField)) + badICRTServiceItem.SetMtoShipmentID(*mtoShipmentIDString) + + returnedModel, verrs := MTOServiceItemModel(badICRTServiceItem) + + suite.True(verrs.HasAny(), fmt.Sprintf("invalid crate dimensions for %s service item", models.ReServiceCodeICRT)) + suite.Nil(returnedModel, "returned a model when erroring") + + // IUCRT + iucrtCode := models.ReServiceCodeIUCRT.String() + + badIUCRTServiceItem := &primev3messages.MTOServiceItemInternationalCrating{ + ReServiceCode: &iucrtCode, + Reason: &reason, + Description: &description, + StandaloneCrate: &standaloneCrate, + ExternalCrate: &externalCrate, + } + badIUCRTServiceItem.Item.MTOServiceItemDimension = *item + badIUCRTServiceItem.Crate.MTOServiceItemDimension = *badCrate + + badIUCRTServiceItem.SetMoveTaskOrderID(handlers.FmtUUID(moveTaskOrderIDField)) + badIUCRTServiceItem.SetMtoShipmentID(*mtoShipmentIDString) + + iucrtReturnedModel, verrs := MTOServiceItemModel(badIUCRTServiceItem) + + suite.True(verrs.HasAny(), fmt.Sprintf("invalid crate dimensions for %s service item", models.ReServiceCodeIUCRT)) + suite.Nil(iucrtReturnedModel, "returned a model when erroring") + }) + suite.Run("Success - Returns SIT destination service item model", func() { destSITServiceItem := &primev3messages.MTOServiceItemDestSIT{ ReServiceCode: &destServiceCode, @@ -1055,11 +1179,17 @@ func (suite *PayloadsSuite) TestPPMShipmentModelWithOptionalDestinationStreet1Fr time := time.Now() expectedDepartureDate := handlers.FmtDatePtr(&time) + country := models.Country{ + Country: "US", + CountryName: "United States", + } + address := models.Address{ StreetAddress1: "some address", City: "city", State: "state", PostalCode: "12345", + Country: &country, } var pickupAddress primev3messages.Address @@ -1067,6 +1197,7 @@ func (suite *PayloadsSuite) TestPPMShipmentModelWithOptionalDestinationStreet1Fr pickupAddress = primev3messages.Address{ City: &address.City, + Country: &address.Country.Country, PostalCode: &address.PostalCode, State: &address.State, StreetAddress1: &address.StreetAddress1, @@ -1075,6 +1206,7 @@ func (suite *PayloadsSuite) TestPPMShipmentModelWithOptionalDestinationStreet1Fr } destinationAddress = primev3messages.PPMDestinationAddress{ City: &address.City, + Country: &address.Country.Country, PostalCode: &address.PostalCode, State: &address.State, StreetAddress1: models.StringPointer(""), // empty string @@ -1082,7 +1214,7 @@ func (suite *PayloadsSuite) TestPPMShipmentModelWithOptionalDestinationStreet1Fr StreetAddress3: address.StreetAddress3, } - ppmShipment := primev3messages.CreatePPMShipment{ + ppmShipment := primev3messages.UpdatePPMShipment{ ExpectedDepartureDate: expectedDepartureDate, PickupAddress: struct{ primev3messages.Address }{pickupAddress}, DestinationAddress: struct { @@ -1090,15 +1222,14 @@ func (suite *PayloadsSuite) TestPPMShipmentModelWithOptionalDestinationStreet1Fr }{destinationAddress}, } - model := PPMShipmentModelFromCreate(&ppmShipment) + model := PPMShipmentModelFromUpdate(&ppmShipment) suite.NotNil(model) - suite.Equal(models.PPMShipmentStatusSubmitted, model.Status) suite.Equal(model.DestinationAddress.StreetAddress1, models.STREET_ADDRESS_1_NOT_PROVIDED) // test when street address 1 contains white spaces destinationAddress.StreetAddress1 = models.StringPointer(" ") - ppmShipmentWhiteSpaces := primev3messages.CreatePPMShipment{ + ppmShipmentWhiteSpaces := primev3messages.UpdatePPMShipment{ ExpectedDepartureDate: expectedDepartureDate, PickupAddress: struct{ primev3messages.Address }{pickupAddress}, DestinationAddress: struct { @@ -1106,13 +1237,13 @@ func (suite *PayloadsSuite) TestPPMShipmentModelWithOptionalDestinationStreet1Fr }{destinationAddress}, } - model2 := PPMShipmentModelFromCreate(&ppmShipmentWhiteSpaces) + model2 := PPMShipmentModelFromUpdate(&ppmShipmentWhiteSpaces) suite.Equal(model2.DestinationAddress.StreetAddress1, models.STREET_ADDRESS_1_NOT_PROVIDED) // test with valid street address 2 streetAddress1 := "1234 Street" destinationAddress.StreetAddress1 = &streetAddress1 - ppmShipmentValidDestinatonStreet1 := primev3messages.CreatePPMShipment{ + ppmShipmentValidDestinatonStreet1 := primev3messages.UpdatePPMShipment{ ExpectedDepartureDate: expectedDepartureDate, PickupAddress: struct{ primev3messages.Address }{pickupAddress}, DestinationAddress: struct { @@ -1120,7 +1251,7 @@ func (suite *PayloadsSuite) TestPPMShipmentModelWithOptionalDestinationStreet1Fr }{destinationAddress}, } - model3 := PPMShipmentModelFromCreate(&ppmShipmentValidDestinatonStreet1) + model3 := PPMShipmentModelFromUpdate(&ppmShipmentValidDestinatonStreet1) suite.Equal(model3.DestinationAddress.StreetAddress1, streetAddress1) } diff --git a/pkg/handlers/routing/base_routing_suite.go b/pkg/handlers/routing/base_routing_suite.go index ddc3a1891f8..23e538792b7 100644 --- a/pkg/handlers/routing/base_routing_suite.go +++ b/pkg/handlers/routing/base_routing_suite.go @@ -174,7 +174,7 @@ func (suite *BaseRoutingSuite) SetupCustomSiteHandler(routingConfig *Config) htt } func (suite *BaseRoutingSuite) SetupCustomSiteHandlerWithTelemetry(routingConfig *Config, telemetryConfig *telemetry.Config) http.Handler { - siteHandler, err := InitRouting(suite.serverName, suite.AppContextForTest(), nil, routingConfig, telemetryConfig) + siteHandler, err := InitRouting(suite.serverName, suite.AppContextForTest(), nil, routingConfig, telemetryConfig, false) suite.FatalNoError(err) return siteHandler } diff --git a/pkg/handlers/routing/routing_init.go b/pkg/handlers/routing/routing_init.go index 223b54e4122..df74fc921b4 100644 --- a/pkg/handlers/routing/routing_init.go +++ b/pkg/handlers/routing/routing_init.go @@ -461,9 +461,9 @@ func mountDebugRoutes(appCtx appcontext.AppContext, routingConfig *Config, site } } -func mountInternalAPI(appCtx appcontext.AppContext, routingConfig *Config, site chi.Router) { +func mountInternalAPI(appCtx appcontext.AppContext, routingConfig *Config, site chi.Router, maintenanceFlag bool) { if routingConfig.ServeAPIInternal { - isLoggedInMiddleware := authentication.IsLoggedInMiddleware(appCtx.Logger()) + isLoggedInMiddleware := authentication.IsLoggedInMiddleware(appCtx.Logger(), maintenanceFlag) userAuthMiddleware := authentication.UserAuthMiddleware(appCtx.Logger()) addAuditUserToRequestContextMiddleware := authentication.AddAuditUserIDToRequestContextMiddleware(appCtx) site.Route("/internal", func(r chi.Router) { @@ -744,7 +744,7 @@ func mountSessionRoutes(appCtx appcontext.AppContext, routingConfig *Config, bas } func newMilRouter(appCtx appcontext.AppContext, redisPool *redis.Pool, - routingConfig *Config, telemetryConfig *telemetry.Config, serverName string) chi.Router { + routingConfig *Config, telemetryConfig *telemetry.Config, serverName string, maintenanceFlag bool) chi.Router { site := newBaseRouter(appCtx, routingConfig, telemetryConfig, serverName) @@ -757,7 +757,7 @@ func newMilRouter(appCtx appcontext.AppContext, redisPool *redis.Pool, milSessionManager := routingConfig.HandlerConfig.SessionManagers().Mil mountSessionRoutes(appCtx, routingConfig, site, milSessionManager, func(sessionRoute chi.Router) { - mountInternalAPI(appCtx, routingConfig, sessionRoute) + mountInternalAPI(appCtx, routingConfig, sessionRoute, maintenanceFlag) }, ) @@ -767,7 +767,7 @@ func newMilRouter(appCtx appcontext.AppContext, redisPool *redis.Pool, } func newOfficeRouter(appCtx appcontext.AppContext, redisPool *redis.Pool, - routingConfig *Config, telemetryConfig *telemetry.Config, serverName string) chi.Router { + routingConfig *Config, telemetryConfig *telemetry.Config, serverName string, maintenanceFlag bool) chi.Router { site := newBaseRouter(appCtx, routingConfig, telemetryConfig, serverName) @@ -780,7 +780,7 @@ func newOfficeRouter(appCtx appcontext.AppContext, redisPool *redis.Pool, officeSessionManager := routingConfig.HandlerConfig.SessionManagers().Office mountSessionRoutes(appCtx, routingConfig, site, officeSessionManager, func(sessionRoute chi.Router) { - mountInternalAPI(appCtx, routingConfig, sessionRoute) + mountInternalAPI(appCtx, routingConfig, sessionRoute, maintenanceFlag) mountPrimeSimulatorAPI(appCtx, routingConfig, sessionRoute) mountGHCAPI(appCtx, routingConfig, sessionRoute) }, @@ -792,7 +792,7 @@ func newOfficeRouter(appCtx appcontext.AppContext, redisPool *redis.Pool, } func newAdminRouter(appCtx appcontext.AppContext, redisPool *redis.Pool, - routingConfig *Config, telemetryConfig *telemetry.Config, serverName string) chi.Router { + routingConfig *Config, telemetryConfig *telemetry.Config, serverName string, maintenanceFlag bool) chi.Router { site := newBaseRouter(appCtx, routingConfig, telemetryConfig, serverName) @@ -804,7 +804,7 @@ func newAdminRouter(appCtx appcontext.AppContext, redisPool *redis.Pool, adminSessionManager := routingConfig.HandlerConfig.SessionManagers().Admin mountSessionRoutes(appCtx, routingConfig, site, adminSessionManager, func(sessionRoute chi.Router) { - mountInternalAPI(appCtx, routingConfig, sessionRoute) + mountInternalAPI(appCtx, routingConfig, sessionRoute, maintenanceFlag) mountAdminAPI(appCtx, routingConfig, sessionRoute) // debug routes can go anywhere, but the admin site seems most appropriate mountDebugRoutes(appCtx, routingConfig, sessionRoute) @@ -834,7 +834,7 @@ func newPrimeRouter(appCtx appcontext.AppContext, redisPool *redis.Pool, // InitRouting sets up the routing for all the hosts (mil, office, // admin, api) func InitRouting(serverName string, appCtx appcontext.AppContext, redisPool *redis.Pool, - routingConfig *Config, telemetryConfig *telemetry.Config) (http.Handler, error) { + routingConfig *Config, telemetryConfig *telemetry.Config, maintenanceFlag bool) (http.Handler, error) { // check for missing CSRF middleware ASAP if routingConfig.CSRFMiddleware == nil { @@ -850,15 +850,15 @@ func InitRouting(serverName string, appCtx appcontext.AppContext, redisPool *red hostRouter := NewHostRouter() milServerName := routingConfig.HandlerConfig.AppNames().MilServername - milRouter := newMilRouter(appCtx, redisPool, routingConfig, telemetryConfig, serverName) + milRouter := newMilRouter(appCtx, redisPool, routingConfig, telemetryConfig, serverName, maintenanceFlag) hostRouter.Map(milServerName, milRouter) officeServerName := routingConfig.HandlerConfig.AppNames().OfficeServername - officeRouter := newOfficeRouter(appCtx, redisPool, routingConfig, telemetryConfig, serverName) + officeRouter := newOfficeRouter(appCtx, redisPool, routingConfig, telemetryConfig, serverName, maintenanceFlag) hostRouter.Map(officeServerName, officeRouter) adminServerName := routingConfig.HandlerConfig.AppNames().AdminServername - adminRouter := newAdminRouter(appCtx, redisPool, routingConfig, telemetryConfig, serverName) + adminRouter := newAdminRouter(appCtx, redisPool, routingConfig, telemetryConfig, serverName, maintenanceFlag) hostRouter.Map(adminServerName, adminRouter) primeServerName := routingConfig.HandlerConfig.AppNames().PrimeServername diff --git a/pkg/models/address.go b/pkg/models/address.go index 5d34502e21c..df2cc66adaa 100644 --- a/pkg/models/address.go +++ b/pkg/models/address.go @@ -11,6 +11,8 @@ import ( "github.com/gofrs/uuid" "go.uber.org/zap" "go.uber.org/zap/zapcore" + + "github.com/transcom/mymove/pkg/utils" ) const STREET_ADDRESS_1_NOT_PROVIDED string = "n/a" @@ -155,6 +157,15 @@ func (a *Address) Copy() *Address { } return nil } +func IsAddressEmpty(a *Address) bool { + return a == nil || ((utils.IsNullOrWhiteSpace(&a.StreetAddress1) || IsDefaultAddressValue(a.StreetAddress1)) && + (utils.IsNullOrWhiteSpace(&a.City) || IsDefaultAddressValue(a.City)) && + (utils.IsNullOrWhiteSpace(&a.State) || IsDefaultAddressValue(a.State)) && + (utils.IsNullOrWhiteSpace(&a.PostalCode) || IsDefaultAddressValue(a.PostalCode))) +} +func IsDefaultAddressValue(s string) bool { + return s == "n/a" +} // Check if an address is CONUS or OCONUS func IsAddressOconus(db *pop.Connection, address Address) (bool, error) { diff --git a/pkg/models/address_test.go b/pkg/models/address_test.go index 6b5e016911d..649ee61e465 100644 --- a/pkg/models/address_test.go +++ b/pkg/models/address_test.go @@ -101,6 +101,33 @@ func (suite *ModelSuite) TestIsAddressOconusForAKState() { suite.Equal(true, result) } +func (suite *ModelSuite) TestAddressIsEmpty() { + suite.Run("empty whitespace address", func() { + testAddress := m.Address{ + StreetAddress1: " ", + State: " ", + PostalCode: " ", + } + suite.True(m.IsAddressEmpty(&testAddress)) + }) + suite.Run("empty n/a address", func() { + testAddress := m.Address{ + StreetAddress1: "n/a", + State: "n/a", + PostalCode: "n/a", + } + suite.True(m.IsAddressEmpty(&testAddress)) + }) + suite.Run("nonempty address", func() { + testAddress := m.Address{ + StreetAddress1: "street 1", + State: "state", + PostalCode: "90210", + } + suite.False(m.IsAddressEmpty(&testAddress)) + }) +} + func (suite *ModelSuite) TestAddressFormat() { country := factory.FetchOrBuildCountry(suite.DB(), nil, nil) newAddress := &m.Address{ diff --git a/pkg/models/mto_service_items.go b/pkg/models/mto_service_items.go index 862a25fce32..b20600cd0a4 100644 --- a/pkg/models/mto_service_items.go +++ b/pkg/models/mto_service_items.go @@ -66,6 +66,7 @@ type MTOServiceItem struct { SITDeliveryMiles *int `db:"sit_delivery_miles"` PricingEstimate *unit.Cents `db:"pricing_estimate"` StandaloneCrate *bool `db:"standalone_crate"` + ExternalCrate *bool `db:"external_crate"` LockedPriceCents *unit.Cents `db:"locked_price_cents"` ServiceLocation *ServiceLocationType `db:"service_location"` } diff --git a/pkg/models/mto_shipments.go b/pkg/models/mto_shipments.go index dcd99dcdc48..f77a8e53a80 100644 --- a/pkg/models/mto_shipments.go +++ b/pkg/models/mto_shipments.go @@ -275,6 +275,11 @@ func GetCustomerFromShipment(db *pop.Connection, shipmentID uuid.UUID) (*Service return &serviceMember, nil } +// Helper function to check that an MTO Shipment contains a PPM Shipment +func (m MTOShipment) ContainsAPPMShipment() bool { + return m.PPMShipment != nil +} + // determining the market code for a shipment based off of address isOconus value // this function takes in a shipment and returns the same shipment with the updated MarketCode value func DetermineShipmentMarketCode(shipment *MTOShipment) *MTOShipment { diff --git a/pkg/models/mto_shipments_test.go b/pkg/models/mto_shipments_test.go index 9050d042d44..dffb0371112 100644 --- a/pkg/models/mto_shipments_test.go +++ b/pkg/models/mto_shipments_test.go @@ -3,6 +3,7 @@ package models_test import ( "github.com/gofrs/uuid" + "github.com/transcom/mymove/pkg/factory" "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/unit" ) @@ -91,6 +92,15 @@ func (suite *ModelSuite) TestMTOShipmentValidation() { } suite.verifyValidationErrors(&invalidMTOShipment, expErrors) }) + suite.Run("test MTO Shipment has a PPM Shipment", func() { + ppmShipment := factory.BuildPPMShipment(suite.DB(), nil, nil) + mtoShipment := factory.BuildMTOShipmentMinimal(suite.DB(), nil, nil) + + mtoShipment.PPMShipment = &ppmShipment + result := mtoShipment.ContainsAPPMShipment() + + suite.True(result, "Expected mtoShipment to cotain a PPM Shipment") + }) } func (suite *ModelSuite) TestDetermineShipmentMarketCode() { diff --git a/pkg/models/ppm_shipment.go b/pkg/models/ppm_shipment.go index e2dd2a00868..46bcf3af14d 100644 --- a/pkg/models/ppm_shipment.go +++ b/pkg/models/ppm_shipment.go @@ -215,6 +215,7 @@ type PPMShipment struct { ProGearWeight *unit.Pound `json:"pro_gear_weight" db:"pro_gear_weight"` SpouseProGearWeight *unit.Pound `json:"spouse_pro_gear_weight" db:"spouse_pro_gear_weight"` EstimatedIncentive *unit.Cents `json:"estimated_incentive" db:"estimated_incentive"` + MaxIncentive *unit.Cents `json:"max_incentive" db:"max_incentive"` FinalIncentive *unit.Cents `json:"final_incentive" db:"final_incentive"` HasRequestedAdvance *bool `json:"has_requested_advance" db:"has_requested_advance"` AdvanceAmountRequested *unit.Cents `json:"advance_amount_requested" db:"advance_amount_requested"` @@ -276,6 +277,7 @@ func (p PPMShipment) Validate(_ *pop.Connection) (*validate.Errors, error) { &OptionalPoundIsNonNegative{Name: "ProGearWeight", Field: p.ProGearWeight}, &OptionalPoundIsNonNegative{Name: "SpouseProGearWeight", Field: p.SpouseProGearWeight}, &OptionalCentIsNotNegative{Name: "EstimatedIncentive", Field: p.EstimatedIncentive}, + &OptionalCentIsNotNegative{Name: "MaxIncentive", Field: p.MaxIncentive}, &OptionalCentIsPositive{Name: "FinalIncentive", Field: p.FinalIncentive}, &OptionalCentIsNotNegative{Name: "AdvanceAmountRequested", Field: p.AdvanceAmountRequested}, &OptionalStringInclusion{Name: "AdvanceStatus", List: AllowedPPMAdvanceStatuses, Field: (*string)(p.AdvanceStatus)}, diff --git a/pkg/models/ppm_shipment_test.go b/pkg/models/ppm_shipment_test.go index c31bfd3424e..db00f31ecda 100644 --- a/pkg/models/ppm_shipment_test.go +++ b/pkg/models/ppm_shipment_test.go @@ -71,6 +71,7 @@ func (suite *ModelSuite) TestPPMShipmentValidation() { ProGearWeight: models.PoundPointer(unit.Pound(-1)), SpouseProGearWeight: models.PoundPointer(unit.Pound(-1)), EstimatedIncentive: models.CentPointer(unit.Cents(-1)), + MaxIncentive: models.CentPointer(unit.Cents(-1)), FinalIncentive: models.CentPointer(unit.Cents(0)), AdvanceAmountRequested: models.CentPointer(unit.Cents(-1)), AdvanceStatus: &blankAdvanceStatus, @@ -98,6 +99,7 @@ func (suite *ModelSuite) TestPPMShipmentValidation() { "pro_gear_weight": {"-1 is less than zero."}, "spouse_pro_gear_weight": {"-1 is less than zero."}, "estimated_incentive": {"EstimatedIncentive cannot be negative, got: -1."}, + "max_incentive": {"MaxIncentive cannot be negative, got: -1."}, "final_incentive": {"FinalIncentive must be greater than zero, got: 0."}, "advance_amount_requested": {"AdvanceAmountRequested cannot be negative, got: -1."}, "advance_status": {fmt.Sprintf("AdvanceStatus is not in the list [%s].", validPPMShipmentAdvanceStatuses)}, diff --git a/pkg/models/re_intl_accessorial_price.go b/pkg/models/re_intl_accessorial_price.go index 052d0efd730..b61b281b76a 100644 --- a/pkg/models/re_intl_accessorial_price.go +++ b/pkg/models/re_intl_accessorial_price.go @@ -61,3 +61,14 @@ func (r *ReIntlAccessorialPrice) Validate(_ *pop.Connection) (*validate.Errors, &validators.IntIsGreaterThan{Field: r.PerUnitCents.Int(), Name: "PerUnitCents", Compared: -1}, ), nil } + +func (m Market) FullString() string { + switch m { + case MarketConus: + return "CONUS" + case MarketOconus: + return "OCONUS" + default: + return "" + } +} diff --git a/pkg/models/re_service.go b/pkg/models/re_service.go index 0226311ccc9..5fc9d9b3e75 100644 --- a/pkg/models/re_service.go +++ b/pkg/models/re_service.go @@ -77,8 +77,6 @@ const ( ReServiceCodeICOUB ReServiceCode = "ICOUB" // ReServiceCodeICRT International crating ReServiceCodeICRT ReServiceCode = "ICRT" - // ReServiceCodeICRTSA International crating - standalone - ReServiceCodeICRTSA ReServiceCode = "ICRTSA" // ReServiceCodeIDASIT International destination add'l day SIT ReServiceCodeIDASIT ReServiceCode = "IDASIT" // ReServiceCodeIDDSIT International destination SIT delivery diff --git a/pkg/models/worksheet_shipment.go b/pkg/models/worksheet_shipment.go index 7c7f8593a8d..3fd8a5ea62c 100644 --- a/pkg/models/worksheet_shipment.go +++ b/pkg/models/worksheet_shipment.go @@ -18,6 +18,7 @@ type WorkSheetShipments struct { // WorkSheetShipment is an object representing specific shipment items on Shipment Summary Worksheet type WorkSheetShipment struct { EstimatedIncentive string + MaxIncentive string MaxAdvance string FinalIncentive string AdvanceAmountReceived string diff --git a/pkg/services/customer.go b/pkg/services/customer.go index 9ade10b0f8e..243429390ed 100644 --- a/pkg/services/customer.go +++ b/pkg/services/customer.go @@ -27,7 +27,7 @@ type CustomerSearcher interface { } type SearchCustomersParams struct { - DodID *string + Edipi *string Emplid *string Branch *string CustomerName *string diff --git a/pkg/services/ghcimport/import_re_intl_accessorial_prices.go b/pkg/services/ghcimport/import_re_intl_accessorial_prices.go index 107e4ea7d2d..6fb1a2a04d7 100644 --- a/pkg/services/ghcimport/import_re_intl_accessorial_prices.go +++ b/pkg/services/ghcimport/import_re_intl_accessorial_prices.go @@ -21,7 +21,6 @@ func (gre *GHCRateEngineImporter) importREIntlAccessorialPrices(appCtx appcontex serviceProvided string }{ {models.ReServiceCodeICRT, "Crating (per cubic ft.)"}, - {models.ReServiceCodeICRTSA, "Crating (per cubic ft.)"}, {models.ReServiceCodeIUCRT, "Uncrating (per cubic ft.)"}, {models.ReServiceCodeIDSHUT, "Shuttle Service (per cwt)"}, {models.ReServiceCodeIOSHUT, "Shuttle Service (per cwt)"}, diff --git a/pkg/services/ghcimport/import_re_intl_accessorial_prices_test.go b/pkg/services/ghcimport/import_re_intl_accessorial_prices_test.go index 2a7bc6aa400..e9311817530 100644 --- a/pkg/services/ghcimport/import_re_intl_accessorial_prices_test.go +++ b/pkg/services/ghcimport/import_re_intl_accessorial_prices_test.go @@ -43,7 +43,7 @@ func (suite *GHCRateEngineImportSuite) Test_importREIntlAccessorialPrices() { func (suite *GHCRateEngineImportSuite) helperVerifyIntlAccessorialPrices() { count, err := suite.DB().Count(&models.ReIntlAccessorialPrice{}) suite.NoError(err) - suite.Equal(10, count) + suite.Equal(8, count) } func (suite *GHCRateEngineImportSuite) helperCheckIntlAccessorialPrices() { @@ -59,7 +59,6 @@ func (suite *GHCRateEngineImportSuite) helperCheckIntlAccessorialPrices() { isError bool }{ {models.ReServiceCodeICRT, "C", 2561, false}, - {models.ReServiceCodeICRTSA, "C", 2561, false}, {models.ReServiceCodeIUCRT, "C", 654, false}, {models.ReServiceCodeIDSHUT, "C", 14529, false}, {models.ReServiceCodeIDSHUT, "O", 15623, false}, diff --git a/pkg/services/mocks/PPMEstimator.go b/pkg/services/mocks/PPMEstimator.go index 3fb92aeb48c..637d4d29b2e 100644 --- a/pkg/services/mocks/PPMEstimator.go +++ b/pkg/services/mocks/PPMEstimator.go @@ -145,6 +145,36 @@ func (_m *PPMEstimator) FinalIncentiveWithDefaultChecks(appCtx appcontext.AppCon return r0, r1 } +// MaxIncentive provides a mock function with given fields: appCtx, oldPPMShipment, newPPMShipment +func (_m *PPMEstimator) MaxIncentive(appCtx appcontext.AppContext, oldPPMShipment models.PPMShipment, newPPMShipment *models.PPMShipment) (*unit.Cents, error) { + ret := _m.Called(appCtx, oldPPMShipment, newPPMShipment) + + if len(ret) == 0 { + panic("no return value specified for MaxIncentive") + } + + var r0 *unit.Cents + var r1 error + if rf, ok := ret.Get(0).(func(appcontext.AppContext, models.PPMShipment, *models.PPMShipment) (*unit.Cents, error)); ok { + return rf(appCtx, oldPPMShipment, newPPMShipment) + } + if rf, ok := ret.Get(0).(func(appcontext.AppContext, models.PPMShipment, *models.PPMShipment) *unit.Cents); ok { + r0 = rf(appCtx, oldPPMShipment, newPPMShipment) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*unit.Cents) + } + } + + if rf, ok := ret.Get(1).(func(appcontext.AppContext, models.PPMShipment, *models.PPMShipment) error); ok { + r1 = rf(appCtx, oldPPMShipment, newPPMShipment) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // PriceBreakdown provides a mock function with given fields: appCtx, ppmShipment func (_m *PPMEstimator) PriceBreakdown(appCtx appcontext.AppContext, ppmShipment *models.PPMShipment) (unit.Cents, unit.Cents, unit.Cents, unit.Cents, unit.Cents, unit.Cents, unit.Cents, error) { ret := _m.Called(appCtx, ppmShipment) diff --git a/pkg/services/move.go b/pkg/services/move.go index f48f7f9f26b..8a524accbe2 100644 --- a/pkg/services/move.go +++ b/pkg/services/move.go @@ -125,3 +125,7 @@ type MoveAssignedOfficeUserUpdater interface { UpdateAssignedOfficeUser(appCtx appcontext.AppContext, moveID uuid.UUID, officeUser *models.OfficeUser, role roles.RoleType) (*models.Move, error) DeleteAssignedOfficeUser(appCtx appcontext.AppContext, moveID uuid.UUID, role roles.RoleType) (*models.Move, error) } + +type CheckForLockedMovesAndUnlockHandler interface { + CheckForLockedMovesAndUnlock(appCtx appcontext.AppContext, officeUserID uuid.UUID) error +} diff --git a/pkg/services/move/move_router.go b/pkg/services/move/move_router.go index c6244b8ba3c..245f3602027 100644 --- a/pkg/services/move/move_router.go +++ b/pkg/services/move/move_router.go @@ -411,7 +411,8 @@ func allSITExtensionsAreReviewed(move models.Move) bool { func allShipmentsAreApproved(move models.Move) bool { for _, shipment := range move.MTOShipments { - if shipment.Status == models.MTOShipmentStatusSubmitted { + // ignores deleted shipments + if shipment.Status == models.MTOShipmentStatusSubmitted && shipment.DeletedAt == nil { return false } } diff --git a/pkg/services/move/move_router_test.go b/pkg/services/move/move_router_test.go index ee64ea15576..b0d0eb87aee 100644 --- a/pkg/services/move/move_router_test.go +++ b/pkg/services/move/move_router_test.go @@ -1067,6 +1067,39 @@ func (suite *MoveServiceSuite) TestApproveOrRequestApproval() { suite.Equal(move.ApprovalsRequestedAt.Format(time.RFC3339), moveInDB.ApprovalsRequestedAt.Format(time.RFC3339)) }) + suite.Run("approves move if unapproved shipment is deleted", func() { + move := factory.BuildAvailableToPrimeMove(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + }, + }, + }, nil) + + deletedAt := time.Now() + factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + DeletedAt: &deletedAt, + }, + }, + { + Model: move, + LinkOnly: true, + }, + }, nil) + + updatedMove, err := moveRouter.ApproveOrRequestApproval(suite.AppContextForTest(), move) + + suite.NoError(err) + suite.Equal(models.MoveStatusAPPROVED, updatedMove.Status) + + var moveInDB models.Move + err = suite.DB().Find(&moveInDB, move.ID) + suite.NoError(err) + suite.Equal(models.MoveStatusAPPROVED, moveInDB.Status) + }) + suite.Run("does not approve the move if excess weight risk exists and has not been acknowledged", func() { now := time.Now() move := factory.BuildApprovalsRequestedMove(suite.DB(), []factory.Customization{ diff --git a/pkg/services/mto_service_item/mto_service_item_creator.go b/pkg/services/mto_service_item/mto_service_item_creator.go index c3707a66f2f..2ab0802badd 100644 --- a/pkg/services/mto_service_item/mto_service_item_creator.go +++ b/pkg/services/mto_service_item/mto_service_item_creator.go @@ -676,6 +676,11 @@ func (o *mtoServiceItemCreator) CreateMTOServiceItem(appCtx appcontext.AppContex return fmt.Errorf("%#v %e", verrs, err) } + // need isOconus information for InternationalCrates in model_to_payload + if requestedServiceItem.ReService.Code == models.ReServiceCodeICRT || requestedServiceItem.ReService.Code == models.ReServiceCodeIUCRT { + requestedServiceItem.MTOShipment = mtoShipment + } + createdServiceItems = append(createdServiceItems, *requestedServiceItem) // create dimensions if any diff --git a/pkg/services/mto_shipment/mto_shipment_creator.go b/pkg/services/mto_shipment/mto_shipment_creator.go index 0beb077d217..9a98e962b57 100644 --- a/pkg/services/mto_shipment/mto_shipment_creator.go +++ b/pkg/services/mto_shipment/mto_shipment_creator.go @@ -36,19 +36,19 @@ func NewMTOShipmentCreatorV1(builder createMTOShipmentQueryBuilder, fetcher serv fetcher, moveRouter, addressCreator, - []validator{protectV1Diversion()}, + []validator{protectV1Diversion(), MTOShipmentHasTertiaryAddressWithNoSecondaryAddressCreate()}, } } // NewMTOShipmentCreator creates a new struct with the service dependencies -// This is utilized in Prime API V2 +// This is utilized in Prime API V2 and Prime API V3 func NewMTOShipmentCreatorV2(builder createMTOShipmentQueryBuilder, fetcher services.Fetcher, moveRouter services.MoveRouter, addressCreator services.AddressCreator) services.MTOShipmentCreator { return &mtoShipmentCreator{ builder, fetcher, moveRouter, addressCreator, - []validator{checkDiversionValid(), childDiversionPrimeWeightRule()}, + []validator{checkDiversionValid(), childDiversionPrimeWeightRule(), MTOShipmentHasTertiaryAddressWithNoSecondaryAddressCreate()}, } } diff --git a/pkg/services/mto_shipment/mto_shipment_updater.go b/pkg/services/mto_shipment/mto_shipment_updater.go index cc3ca14e81d..5b3bd8cb659 100644 --- a/pkg/services/mto_shipment/mto_shipment_updater.go +++ b/pkg/services/mto_shipment/mto_shipment_updater.go @@ -75,7 +75,7 @@ func NewCustomerMTOShipmentUpdater(builder UpdateMTOShipmentQueryBuilder, _ serv moveWeights, sender, recalculator, - []validator{checkStatus()}, + []validator{checkStatus(), MTOShipmentHasTertiaryAddressWithNoSecondaryAddressUpdate()}, } } @@ -90,7 +90,7 @@ func NewOfficeMTOShipmentUpdater(builder UpdateMTOShipmentQueryBuilder, _ servic moveWeights, sender, recalculator, - []validator{checkStatus(), checkUpdateAllowed()}, + []validator{checkStatus(), checkUpdateAllowed(), MTOShipmentHasTertiaryAddressWithNoSecondaryAddressUpdate()}, } } @@ -107,7 +107,7 @@ func NewPrimeMTOShipmentUpdater(builder UpdateMTOShipmentQueryBuilder, _ service moveWeights, sender, recalculator, - []validator{checkStatus(), checkAvailToPrime(), checkPrimeValidationsOnModel(planner)}, + []validator{checkStatus(), checkAvailToPrime(), checkPrimeValidationsOnModel(planner), MTOShipmentHasTertiaryAddressWithNoSecondaryAddressUpdate()}, } } diff --git a/pkg/services/mto_shipment/mto_shipment_updater_test.go b/pkg/services/mto_shipment/mto_shipment_updater_test.go index 230bc161da9..dfabece3112 100644 --- a/pkg/services/mto_shipment/mto_shipment_updater_test.go +++ b/pkg/services/mto_shipment/mto_shipment_updater_test.go @@ -309,6 +309,11 @@ func (suite *MTOShipmentServiceSuite) TestMTOShipmentUpdater() { ShipmentType: models.MTOShipmentTypeHHG, }, }, + { + Model: secondaryPickupAddress, + LinkOnly: true, + Type: &factory.Addresses.SecondaryPickupAddress, + }, { Model: tertiaryPickupAddress, LinkOnly: true, diff --git a/pkg/services/mto_shipment/rules.go b/pkg/services/mto_shipment/rules.go index 3c6e37c37e9..46663cc2c00 100644 --- a/pkg/services/mto_shipment/rules.go +++ b/pkg/services/mto_shipment/rules.go @@ -52,7 +52,7 @@ func checkAvailToPrime() validator { return apperror.NewQueryError("Move", err, "Unexpected error") } if !availToPrime { - return apperror.NewNotFoundError(newer.ID, "for mtoShipment") + return apperror.NewNotFoundError(newer.ID, "not available to prime for mtoShipment") } return nil }) @@ -149,6 +149,78 @@ func checkPrimeDeleteAllowed() validator { }) } +// helper function to check if the secondary address is empty, but the tertiary is not +func isMTOShipmentAddressCreateSequenceValid(mtoShipmentToCheck models.MTOShipment) bool { + bothPickupAddressesEmpty := (models.IsAddressEmpty(mtoShipmentToCheck.SecondaryPickupAddress) && models.IsAddressEmpty(mtoShipmentToCheck.TertiaryPickupAddress)) + bothDestinationAddressesEmpty := (models.IsAddressEmpty(mtoShipmentToCheck.SecondaryDeliveryAddress) && models.IsAddressEmpty(mtoShipmentToCheck.TertiaryDeliveryAddress)) + bothPickupAddressesNotEmpty := !bothPickupAddressesEmpty + bothDestinationAddressesNotEmpty := !bothDestinationAddressesEmpty + hasNoSecondaryHasTertiaryPickup := (models.IsAddressEmpty(mtoShipmentToCheck.SecondaryPickupAddress) && !models.IsAddressEmpty(mtoShipmentToCheck.TertiaryPickupAddress)) + hasNoSecondaryHasTertiaryDestination := (models.IsAddressEmpty(mtoShipmentToCheck.SecondaryDeliveryAddress) && !models.IsAddressEmpty(mtoShipmentToCheck.TertiaryDeliveryAddress)) + + // need an explicit case to capture when both are empty or not empty + if ((bothPickupAddressesEmpty && bothDestinationAddressesEmpty) || (bothPickupAddressesNotEmpty && bothDestinationAddressesNotEmpty)) && !(hasNoSecondaryHasTertiaryPickup || hasNoSecondaryHasTertiaryDestination) { + return true + } + if hasNoSecondaryHasTertiaryPickup || hasNoSecondaryHasTertiaryDestination { + return false + } + return true +} + +// helper function to check if the secondary address is empty, but the tertiary is not +func hasTertiaryWithNoSecondaryAddress(secondaryAddress *models.Address, tertiaryAddress *models.Address) bool { + return (models.IsAddressEmpty(secondaryAddress) && !models.IsAddressEmpty(tertiaryAddress)) +} + +/* Checks if a valid address sequence is being maintained. This will return false if the tertiary address is being updated while the secondary address remains empty +* + */ +func isMTOAddressUpdateSequenceValid(shipmentToUpdateWith *models.MTOShipment, currentShipment *models.MTOShipment) bool { + // if the incoming model has both fields, then we know the model will be updated with both secondary and tertiary addresses. therefore return true + if !models.IsAddressEmpty(shipmentToUpdateWith.SecondaryPickupAddress) && !models.IsAddressEmpty(shipmentToUpdateWith.TertiaryPickupAddress) { + return true + } + if !models.IsAddressEmpty(shipmentToUpdateWith.SecondaryDeliveryAddress) && !models.IsAddressEmpty(shipmentToUpdateWith.TertiaryDeliveryAddress) { + return true + } + if currentShipment.SecondaryPickupAddress == nil && shipmentToUpdateWith.TertiaryPickupAddress != nil { + return !hasTertiaryWithNoSecondaryAddress(currentShipment.SecondaryPickupAddress, shipmentToUpdateWith.TertiaryPickupAddress) + } + if currentShipment.SecondaryDeliveryAddress == nil && shipmentToUpdateWith.TertiaryDeliveryAddress != nil { + return !hasTertiaryWithNoSecondaryAddress(currentShipment.SecondaryDeliveryAddress, shipmentToUpdateWith.TertiaryDeliveryAddress) + } + // no addresses are being updated, so correct address sequence is maintained, return true + return true +} + +func MTOShipmentHasTertiaryAddressWithNoSecondaryAddressUpdate() validator { + return validatorFunc(func(appCtx appcontext.AppContext, newer *models.MTOShipment, older *models.MTOShipment) error { + verrs := validate.NewErrors() + if newer != nil && older != nil { + squenceIsValid := isMTOAddressUpdateSequenceValid(newer, older) + if !squenceIsValid { + verrs.Add("error validating mto shipment", "Shipment cannot have a third address without a second address present") + return apperror.NewInvalidInputError(newer.ID, nil, verrs, "Invalid input found while validating the MTO shipment") + } + } + return nil + }) +} +func MTOShipmentHasTertiaryAddressWithNoSecondaryAddressCreate() validator { + return validatorFunc(func(appCtx appcontext.AppContext, newer *models.MTOShipment, _ *models.MTOShipment) error { + verrs := validate.NewErrors() + if newer != nil { + squenceIsValid := isMTOShipmentAddressCreateSequenceValid(*newer) + if !squenceIsValid { + verrs.Add("error validating mto shipment", "Shipment cannot have a third address without a second address present") + return apperror.NewInvalidInputError(newer.ID, nil, verrs, "Invalid input found while validating the MTO shipment") + } + } + return nil + }) +} + // This function checks Prime specific validations on the model // It expects older to represent what's in the db and mtoShipment to represent the requested update // It updates mtoShipment accordingly if there are dependent updates like requiredDeliveryDate diff --git a/pkg/services/mto_shipment/rules_test.go b/pkg/services/mto_shipment/rules_test.go index e89166df36d..22b7fdf1a26 100644 --- a/pkg/services/mto_shipment/rules_test.go +++ b/pkg/services/mto_shipment/rules_test.go @@ -42,6 +42,108 @@ func (suite *MTOShipmentServiceSuite) TestUpdateValidations() { } }) + suite.Run("MTOShipmentHasTertiaryAddressWithNoSecondaryAddressUpdate Invalid add tertiary address without secondary", func() { + tertiaryDeliveryAddress := factory.BuildAddress(suite.DB(), nil, nil) + + minimalMove := models.MTOShipment{ + TertiaryDeliveryAddress: &tertiaryDeliveryAddress, + } + + mtoShipment_ThNScndP_address_Move := factory.BuildMoveWithShipment(suite.DB(), nil, nil) + + checker := MTOShipmentHasTertiaryAddressWithNoSecondaryAddressUpdate() + err := checker.Validate(suite.AppContextForTest(), &minimalMove, &mtoShipment_ThNScndP_address_Move.MTOShipments[0]) + suite.Error(err) + }) + + suite.Run("MTOShipmentHasTertiaryAddressWithNoSecondaryAddressUpdate Valid add secondary address", func() { + secondaryPickupAddress := factory.BuildAddress(suite.DB(), nil, nil) + + minimalMove := models.MTOShipment{ + SecondaryPickupAddress: &secondaryPickupAddress, + } + + mtoShipment_ThNScndP_address_Move := factory.BuildMoveWithShipment(suite.DB(), nil, nil) + + checker := MTOShipmentHasTertiaryAddressWithNoSecondaryAddressUpdate() + err := checker.Validate(suite.AppContextForTest(), &mtoShipment_ThNScndP_address_Move.MTOShipments[0], &minimalMove) + suite.NoError(err) + }) + + suite.Run("MTOShipmentHasTertiaryAddressWithNoSecondaryAddressUpdate Valid remove secondary address", func() { + secondaryPickupAddress := factory.BuildAddress(suite.DB(), nil, nil) + + oldMove := factory.BuildMoveWithShipment(suite.DB(), nil, nil) + oldMove.MTOShipments[0].SecondaryPickupAddress = &secondaryPickupAddress + + newMove := factory.BuildMoveWithShipment(suite.DB(), nil, nil) + + checker := MTOShipmentHasTertiaryAddressWithNoSecondaryAddressUpdate() + err := checker.Validate(suite.AppContextForTest(), &newMove.MTOShipments[0], &oldMove.MTOShipments[0]) + suite.NoError(err) + }) + + suite.Run("MTOShipmentHasTertiaryAddressWithNoSecondaryAddressUpdate Valid", func() { + tertiaryPickupAddress := factory.BuildAddress(suite.DB(), nil, nil) + + minimalMove := models.MTOShipment{ + TertiaryPickupAddress: &tertiaryPickupAddress, + } + + mtoShipment_ThNScndP_address_Move := factory.BuildMoveWithShipment(suite.DB(), nil, nil) + + checker := MTOShipmentHasTertiaryAddressWithNoSecondaryAddressUpdate() + err := checker.Validate(suite.AppContextForTest(), &mtoShipment_ThNScndP_address_Move.MTOShipments[0], &minimalMove) + suite.NoError(err) + }) + + suite.Run("MTOShipmentHasTertiaryAddressWithNoSecondaryAddressCreate No Secondary Address With Tertiary Invalid", func() { + secondaryPickupAddress := factory.BuildAddress(suite.DB(), nil, nil) + TertiaryDestinationAddress := factory.BuildAddress(suite.DB(), nil, nil) + tertiaryPickupAddress := factory.BuildAddress(suite.DB(), nil, nil) + + mtoShipment_Valid_address := factory.BuildMoveWithShipment(suite.DB(), nil, nil) + mtoShipment_Valid_address.MTOShipments[0].SecondaryPickupAddress = &secondaryPickupAddress + mtoShipment_Valid_address.MTOShipments[0].TertiaryPickupAddress = &tertiaryPickupAddress + mtoShipment_Valid_address.MTOShipments[0].TertiaryDeliveryAddress = &TertiaryDestinationAddress + + checker := MTOShipmentHasTertiaryAddressWithNoSecondaryAddressCreate() + err := checker.Validate(suite.AppContextForTest(), &mtoShipment_Valid_address.MTOShipments[0], nil) + suite.Error(err) + }) + + suite.Run("MTOShipmentHasTertiaryAddressWithNoSecondaryAddressCreate with Secondary Address Valid", func() { + secondaryPickupAddress := factory.BuildAddress(suite.DB(), nil, nil) + TertiaryDestinationAddress := factory.BuildAddress(suite.DB(), nil, nil) + tertiaryPickupAddress := factory.BuildAddress(suite.DB(), nil, nil) + + mtoShipment_Valid_address := factory.BuildMoveWithShipment(suite.DB(), nil, nil) + mtoShipment_Valid_address.MTOShipments[0].SecondaryPickupAddress = &secondaryPickupAddress + mtoShipment_Valid_address.MTOShipments[0].TertiaryPickupAddress = &tertiaryPickupAddress + mtoShipment_Valid_address.MTOShipments[0].TertiaryDeliveryAddress = &TertiaryDestinationAddress + + checker := MTOShipmentHasTertiaryAddressWithNoSecondaryAddressCreate() + err := checker.Validate(suite.AppContextForTest(), &mtoShipment_Valid_address.MTOShipments[0], nil) + suite.Error(err) + }) + + suite.Run("MTOShipmentHasTertiaryAddressWithNoSecondaryAddressCreate Valid", func() { + SecondaryDestinationAddress := factory.BuildAddress(suite.DB(), nil, nil) + secondaryPickupAddress := factory.BuildAddress(suite.DB(), nil, nil) + TertiaryDestinationAddress := factory.BuildAddress(suite.DB(), nil, nil) + tertiaryPickupAddress := factory.BuildAddress(suite.DB(), nil, nil) + + mtoShipment_Valid_address := factory.BuildMoveWithShipment(suite.DB(), nil, nil) + mtoShipment_Valid_address.MTOShipments[0].SecondaryPickupAddress = &secondaryPickupAddress + mtoShipment_Valid_address.MTOShipments[0].SecondaryDeliveryAddress = &SecondaryDestinationAddress + mtoShipment_Valid_address.MTOShipments[0].TertiaryPickupAddress = &tertiaryPickupAddress + mtoShipment_Valid_address.MTOShipments[0].TertiaryDeliveryAddress = &TertiaryDestinationAddress + + checker := MTOShipmentHasTertiaryAddressWithNoSecondaryAddressCreate() + err := checker.Validate(suite.AppContextForTest(), &mtoShipment_Valid_address.MTOShipments[0], nil) + suite.NoError(err) + }) + suite.Run("checkAvailToPrime", func() { appCtx := suite.AppContextForTest() diff --git a/pkg/services/office_user/customer/customer_searcher.go b/pkg/services/office_user/customer/customer_searcher.go index 5b58593aa5a..6f065ffe629 100644 --- a/pkg/services/office_user/customer/customer_searcher.go +++ b/pkg/services/office_user/customer/customer_searcher.go @@ -23,13 +23,13 @@ func NewCustomerSearcher() services.CustomerSearcher { type QueryOption func(*pop.Query) func (s customerSearcher) SearchCustomers(appCtx appcontext.AppContext, params *services.SearchCustomersParams) (models.ServiceMemberSearchResults, int, error) { - if params.DodID == nil && params.CustomerName == nil { + if params.Edipi == nil && params.CustomerName == nil { verrs := validate.NewErrors() verrs.Add("search key", "DOD ID or customer name must be provided") return models.ServiceMemberSearchResults{}, 0, apperror.NewInvalidInputError(uuid.Nil, nil, verrs, "") } - if params.CustomerName != nil && params.DodID != nil { + if params.CustomerName != nil && params.Edipi != nil { verrs := validate.NewErrors() verrs.Add("search key", "search by multiple keys is not supported") return models.ServiceMemberSearchResults{}, 0, apperror.NewInvalidInputError(uuid.Nil, nil, verrs, "") @@ -82,7 +82,7 @@ func (s customerSearcher) SearchCustomers(appCtx appcontext.AppContext, params * rawquery += ` WHERE (` } - if params.DodID != nil { + if params.Edipi != nil { rawquery += ` service_members.edipi = $1) ) distinct_customers` if params.Sort != nil && params.Order != nil { sortTerm := parameters[*params.Sort] @@ -90,7 +90,7 @@ func (s customerSearcher) SearchCustomers(appCtx appcontext.AppContext, params * } else { rawquery += ` ORDER BY distinct_customers.last_name ASC` } - query = appCtx.DB().RawQuery(rawquery, params.DodID) + query = appCtx.DB().RawQuery(rawquery, params.Edipi) } else { rawquery += ` f_unaccent(lower($1)) % searchable_full_name(first_name, last_name)) ) distinct_customers ORDER BY total_sim DESC` if params.Sort != nil && params.Order != nil { @@ -101,7 +101,7 @@ func (s customerSearcher) SearchCustomers(appCtx appcontext.AppContext, params * } customerNameQuery := customerNameSearch(params.CustomerName) - dodIDQuery := dodIDSearch(params.DodID) + dodIDQuery := dodIDSearch(params.Edipi) options := [2]QueryOption{customerNameQuery, dodIDQuery} for _, option := range options { diff --git a/pkg/services/office_user/customer/customer_searcher_test.go b/pkg/services/office_user/customer/customer_searcher_test.go index 1e2cd117f7c..5b20be935a2 100644 --- a/pkg/services/office_user/customer/customer_searcher_test.go +++ b/pkg/services/office_user/customer/customer_searcher_test.go @@ -55,7 +55,7 @@ func (suite CustomerServiceSuite) TestCustomerSearch() { }, }, nil) - customers, _, err := searcher.SearchCustomers(suite.AppContextWithSessionForTest(&session), &services.SearchCustomersParams{DodID: serviceMember1.Edipi}) + customers, _, err := searcher.SearchCustomers(suite.AppContextWithSessionForTest(&session), &services.SearchCustomersParams{Edipi: serviceMember1.Edipi}) suite.NoError(err) suite.Len(customers, 1) suite.Equal(serviceMember1.Edipi, customers[0].Edipi) @@ -108,7 +108,7 @@ func (suite CustomerServiceSuite) TestCustomerSearch() { }, nil) _, _, err := searcher.SearchCustomers(suite.AppContextWithSessionForTest(&session), &services.SearchCustomersParams{ - DodID: serviceMember1.Edipi, + Edipi: serviceMember1.Edipi, CustomerName: models.StringPointer("Page McConnel"), }) suite.Error(err) @@ -154,7 +154,7 @@ func (suite CustomerServiceSuite) TestCustomerSearch() { }, }, nil) - customers, _, err := searcher.SearchCustomers(suite.AppContextWithSessionForTest(&session), &services.SearchCustomersParams{DodID: serviceMember.Orders.ServiceMember.Edipi}) + customers, _, err := searcher.SearchCustomers(suite.AppContextWithSessionForTest(&session), &services.SearchCustomersParams{Edipi: serviceMember.Orders.ServiceMember.Edipi}) suite.NoError(err) suite.Len(customers, 0) }) @@ -179,7 +179,7 @@ func (suite CustomerServiceSuite) TestCustomerSearch() { }, }, nil) - customers, _, err := searcher.SearchCustomers(suite.AppContextWithSessionForTest(&session), &services.SearchCustomersParams{DodID: serviceMember1.Edipi}) + customers, _, err := searcher.SearchCustomers(suite.AppContextWithSessionForTest(&session), &services.SearchCustomersParams{Edipi: serviceMember1.Edipi}) suite.NoError(err) suite.Len(customers, 1) suite.Equal(serviceMember1.Edipi, customers[0].Edipi) diff --git a/pkg/services/order.go b/pkg/services/order.go index 1ec7eba411f..4f33f6f1a23 100644 --- a/pkg/services/order.go +++ b/pkg/services/order.go @@ -48,7 +48,7 @@ type ExcessWeightRiskManager interface { type ListOrderParams struct { Branch *string Locator *string - DodID *string + Edipi *string Emplid *string CustomerName *string DestinationDutyLocation *string diff --git a/pkg/services/order/order_fetcher.go b/pkg/services/order/order_fetcher.go index 018e77c5da2..8d5976fe855 100644 --- a/pkg/services/order/order_fetcher.go +++ b/pkg/services/order/order_fetcher.go @@ -104,7 +104,7 @@ func (f orderFetcher) ListOrders(appCtx appcontext.AppContext, officeUserID uuid gblocQuery = gblocFilterForTOO(gblocToFilterBy) } locatorQuery := locatorFilter(params.Locator) - dodIDQuery := dodIDFilter(params.DodID) + dodIDQuery := dodIDFilter(params.Edipi) emplidQuery := emplidFilter(params.Emplid) customerNameQuery := customerNameFilter(params.CustomerName) originDutyLocationQuery := originDutyLocationFilter(params.OriginDutyLocation) @@ -134,6 +134,8 @@ func (f orderFetcher) ListOrders(appCtx appcontext.AppContext, officeUserID uuid "Orders.OrdersType", "MTOShipments.PPMShipment", "LockedByOfficeUser", + "SCAssignedUser", + "CounselingOffice", ).InnerJoin("orders", "orders.id = moves.orders_id"). InnerJoin("service_members", "orders.service_member_id = service_members.id"). InnerJoin("mto_shipments", "moves.id = mto_shipments.move_id"). @@ -141,6 +143,8 @@ func (f orderFetcher) ListOrders(appCtx appcontext.AppContext, officeUserID uuid InnerJoin("duty_locations as origin_dl", "orders.origin_duty_location_id = origin_dl.id"). LeftJoin("duty_locations as dest_dl", "dest_dl.id = orders.new_duty_location_id"). LeftJoin("office_users", "office_users.id = moves.locked_by"). + LeftJoin("office_users as assigned_user", "moves.sc_assigned_id = assigned_user.id"). + LeftJoin("transportation_offices", "moves.counseling_transportation_office_id = transportation_offices.id"). Where("show = ?", models.BoolPointer(true)) if !privileges.HasPrivilege(models.PrivilegeTypeSafety) { @@ -727,7 +731,7 @@ func gblocFilterForPPMCloseoutForNavyMarineAndCG(gbloc *string) QueryOption { func sortOrder(sort *string, order *string, ppmCloseoutGblocs bool) QueryOption { parameters := map[string]string{ "customerName": "(service_members.last_name || ' ' || service_members.first_name)", - "dodID": "service_members.edipi", + "edipi": "service_members.edipi", "emplid": "service_members.emplid", "branch": "service_members.affiliation", "locator": "moves.locator", diff --git a/pkg/services/payment_request.go b/pkg/services/payment_request.go index 69a9d67c9e7..ae7e345dc94 100644 --- a/pkg/services/payment_request.go +++ b/pkg/services/payment_request.go @@ -80,7 +80,7 @@ type PaymentRequestReviewedProcessor interface { type FetchPaymentRequestListParams struct { Branch *string Locator *string - DodID *string + Edipi *string Emplid *string CustomerName *string DestinationDutyLocation *string @@ -94,6 +94,7 @@ type FetchPaymentRequestListParams struct { OrderType *string ViewAsGBLOC *string TIOAssignedUser *string + CounselingOffice *string } // ShipmentPaymentSITBalance is a public struct that's used to return current SIT balances to the TIO for a payment diff --git a/pkg/services/payment_request/payment_request_list_fetcher.go b/pkg/services/payment_request/payment_request_list_fetcher.go index 6794c9abc9e..11f0c52805c 100644 --- a/pkg/services/payment_request/payment_request_list_fetcher.go +++ b/pkg/services/payment_request/payment_request_list_fetcher.go @@ -21,7 +21,7 @@ type paymentRequestListFetcher struct { var parameters = map[string]string{ "customerName": "(service_members.last_name || ' ' || service_members.first_name)", - "dodID": "service_members.edipi", + "edipi": "service_members.edipi", "emplid": "service_members.emplid", "submittedAt": "payment_requests.created_at", "branch": "service_members.affiliation", @@ -30,6 +30,7 @@ var parameters = map[string]string{ "age": "payment_requests.created_at", "originDutyLocation": "duty_locations.name", "assignedTo": "assigned_user.last_name,assigned_user.first_name", + "counselingOffice": "transportation_offices.id", } // NewPaymentRequestListFetcher returns a new payment request list fetcher @@ -64,6 +65,7 @@ func (f *paymentRequestListFetcher) FetchPaymentRequestList(appCtx appcontext.Ap "MoveTaskOrder.Orders.OriginDutyLocation.TransportationOffice", "MoveTaskOrder.Orders.OriginDutyLocation.Address", "MoveTaskOrder.TIOAssignedUser", + "MoveTaskOrder.CounselingOffice", // See note further below about having to do this in a separate Load call due to a Pop issue. // "MoveTaskOrder.Orders.ServiceMember", ). @@ -72,11 +74,12 @@ func (f *paymentRequestListFetcher) FetchPaymentRequestList(appCtx appcontext.Ap InnerJoin("service_members", "orders.service_member_id = service_members.id"). InnerJoin("duty_locations", "duty_locations.id = orders.origin_duty_location_id"). // Need to use left join because some duty locations do not have transportation offices - LeftJoin("transportation_offices", "duty_locations.transportation_office_id = transportation_offices.id"). + LeftJoin("transportation_offices as origin_to", "duty_locations.transportation_office_id = origin_to.id"). // If a customer puts in an invalid ZIP for their pickup address, it won't show up in this view, // and we don't want it to get hidden from services counselors. LeftJoin("move_to_gbloc", "move_to_gbloc.move_id = moves.id"). LeftJoin("office_users as assigned_user", "moves.tio_assigned_id = assigned_user.id"). + LeftJoin("transportation_offices", "moves.counseling_transportation_office_id = transportation_offices.id"). Where("moves.show = ?", models.BoolPointer(true)) if !privileges.HasPrivilege(models.PrivilegeTypeSafety) { @@ -94,7 +97,7 @@ func (f *paymentRequestListFetcher) FetchPaymentRequestList(appCtx appcontext.Ap gblocQuery = shipmentGBLOCFilter(&gbloc) } locatorQuery := locatorFilter(params.Locator) - dodIDQuery := dodIDFilter(params.DodID) + dodIDQuery := dodIDFilter(params.Edipi) emplidQuery := emplidFilter(params.Emplid) customerNameQuery := customerNameFilter(params.CustomerName) dutyLocationQuery := destinationDutyLocationFilter(params.DestinationDutyLocation) @@ -103,8 +106,9 @@ func (f *paymentRequestListFetcher) FetchPaymentRequestList(appCtx appcontext.Ap originDutyLocationQuery := dutyLocationFilter(params.OriginDutyLocation) orderQuery := sortOrder(params.Sort, params.Order) tioAssignedUserQuery := tioAssignedUserFilter(params.TIOAssignedUser) + counselingQuery := counselingOfficeFilter(params.CounselingOffice) - options := [12]QueryOption{branchQuery, locatorQuery, dodIDQuery, customerNameQuery, dutyLocationQuery, statusQuery, originDutyLocationQuery, submittedAtQuery, gblocQuery, orderQuery, emplidQuery, tioAssignedUserQuery} + options := [13]QueryOption{branchQuery, locatorQuery, dodIDQuery, customerNameQuery, dutyLocationQuery, statusQuery, originDutyLocationQuery, submittedAtQuery, gblocQuery, orderQuery, emplidQuery, tioAssignedUserQuery, counselingQuery} for _, option := range options { if option != nil { @@ -119,8 +123,7 @@ func (f *paymentRequestListFetcher) FetchPaymentRequestList(appCtx appcontext.Ap if params.PerPage == nil { params.PerPage = models.Int64Pointer(20) } - - err = query.GroupBy("payment_requests.id, service_members.id, moves.id, duty_locations.id, duty_locations.name, assigned_user.last_name, assigned_user.first_name").Paginate(int(*params.Page), int(*params.PerPage)).All(&paymentRequests) + err = query.GroupBy("payment_requests.id, service_members.id, moves.id, duty_locations.id, duty_locations.name, assigned_user.last_name, assigned_user.first_name, transportation_offices.id, transportation_offices.name").Paginate(int(*params.Page), int(*params.PerPage)).All(&paymentRequests) if err != nil { return nil, 0, err } @@ -428,3 +431,11 @@ func tioAssignedUserFilter(tioAssigned *string) QueryOption { } } } + +func counselingOfficeFilter(office *string) QueryOption { + return func(query *pop.Query) { + if office != nil { + query.Where("transportation_offices.name ILIKE ?", "%"+*office+"%") + } + } +} diff --git a/pkg/services/payment_request/payment_request_list_fetcher_test.go b/pkg/services/payment_request/payment_request_list_fetcher_test.go index cbbf31deb7f..1fbf73d39cd 100644 --- a/pkg/services/payment_request/payment_request_list_fetcher_test.go +++ b/pkg/services/payment_request/payment_request_list_fetcher_test.go @@ -768,7 +768,7 @@ func (suite *PaymentRequestServiceSuite) TestListPaymentRequestWithSortOrder() { sort.Strings(expectedDodIDOrder) // Sort by dodID - params := services.FetchPaymentRequestListParams{Sort: models.StringPointer("dodID"), Order: models.StringPointer("asc")} + params := services.FetchPaymentRequestListParams{Sort: models.StringPointer("edipi"), Order: models.StringPointer("asc")} expectedPaymentRequests, _, err := paymentRequestListFetcher.FetchPaymentRequestList(suite.AppContextWithSessionForTest(&session), officeUser.ID, ¶ms) paymentRequests := *expectedPaymentRequests @@ -781,7 +781,7 @@ func (suite *PaymentRequestServiceSuite) TestListPaymentRequestWithSortOrder() { suite.Run("Sort by dodID DESC", func() { sort.Strings(expectedDodIDOrder) - params := services.FetchPaymentRequestListParams{Sort: models.StringPointer("dodID"), Order: models.StringPointer("desc")} + params := services.FetchPaymentRequestListParams{Sort: models.StringPointer("edipi"), Order: models.StringPointer("desc")} expectedPaymentRequests, _, err := paymentRequestListFetcher.FetchPaymentRequestList(suite.AppContextWithSessionForTest(&session), officeUser.ID, ¶ms) paymentRequests := *expectedPaymentRequests diff --git a/pkg/services/ppmshipment.go b/pkg/services/ppmshipment.go index 5d5562bd90e..d7fb898685a 100644 --- a/pkg/services/ppmshipment.go +++ b/pkg/services/ppmshipment.go @@ -46,6 +46,7 @@ type PPMDocumentFetcher interface { //go:generate mockery --name PPMEstimator type PPMEstimator interface { EstimateIncentiveWithDefaultChecks(appCtx appcontext.AppContext, oldPPMShipment models.PPMShipment, newPPMShipment *models.PPMShipment) (*unit.Cents, *unit.Cents, error) + MaxIncentive(appCtx appcontext.AppContext, oldPPMShipment models.PPMShipment, newPPMShipment *models.PPMShipment) (*unit.Cents, error) FinalIncentiveWithDefaultChecks(appCtx appcontext.AppContext, oldPPMShipment models.PPMShipment, newPPMShipment *models.PPMShipment) (*unit.Cents, error) PriceBreakdown(appCtx appcontext.AppContext, ppmShipment *models.PPMShipment) (unit.Cents, unit.Cents, unit.Cents, unit.Cents, unit.Cents, unit.Cents, unit.Cents, error) CalculatePPMSITEstimatedCost(appCtx appcontext.AppContext, ppmShipment *models.PPMShipment) (*unit.Cents, error) diff --git a/pkg/services/ppmshipment/ppm_estimator.go b/pkg/services/ppmshipment/ppm_estimator.go index 59b5fb27217..643dccfd1fb 100644 --- a/pkg/services/ppmshipment/ppm_estimator.go +++ b/pkg/services/ppmshipment/ppm_estimator.go @@ -113,11 +113,14 @@ func (f *estimatePPM) CalculatePPMSITEstimatedCostBreakdown(appCtx appcontext.Ap return ppmSITEstimatedCostInfoData, nil } -// EstimateIncentiveWithDefaultChecks func that returns the estimate hard coded to 12K (because it'll be clear that the value is coming from the service) func (f *estimatePPM) EstimateIncentiveWithDefaultChecks(appCtx appcontext.AppContext, oldPPMShipment models.PPMShipment, newPPMShipment *models.PPMShipment) (*unit.Cents, *unit.Cents, error) { return f.estimateIncentive(appCtx, oldPPMShipment, newPPMShipment, f.checks...) } +func (f *estimatePPM) MaxIncentive(appCtx appcontext.AppContext, oldPPMShipment models.PPMShipment, newPPMShipment *models.PPMShipment) (*unit.Cents, error) { + return f.maxIncentive(appCtx, oldPPMShipment, newPPMShipment, f.checks...) +} + func (f *estimatePPM) FinalIncentiveWithDefaultChecks(appCtx appcontext.AppContext, oldPPMShipment models.PPMShipment, newPPMShipment *models.PPMShipment) (*unit.Cents, error) { return f.finalIncentive(appCtx, oldPPMShipment, newPPMShipment, f.checks...) } @@ -221,7 +224,7 @@ func (f *estimatePPM) estimateIncentive(appCtx appcontext.AppContext, oldPPMShip newPPMShipment.HasRequestedAdvance = nil newPPMShipment.AdvanceAmountRequested = nil - estimatedIncentive, err = f.calculatePrice(appCtx, newPPMShipment, 0, contract) + estimatedIncentive, err = f.calculatePrice(appCtx, newPPMShipment, 0, contract, false) if err != nil { return nil, nil, err } @@ -238,6 +241,48 @@ func (f *estimatePPM) estimateIncentive(appCtx appcontext.AppContext, oldPPMShip return estimatedIncentive, estimatedSITCost, nil } +func (f *estimatePPM) maxIncentive(appCtx appcontext.AppContext, oldPPMShipment models.PPMShipment, newPPMShipment *models.PPMShipment, checks ...ppmShipmentValidator) (*unit.Cents, error) { + // Check that all the required fields we need are present. + err := validatePPMShipment(appCtx, *newPPMShipment, &oldPPMShipment, &oldPPMShipment.Shipment, checks...) + // If a field does not pass validation return nil as error handling is happening in the validator + if err != nil { + switch err.(type) { + case apperror.InvalidInputError: + return nil, nil + default: + return nil, err + } + } + + // we have access to the MoveTaskOrderID in the ppmShipment object so we can use that to get the customer's maximum weight entitlement + var move models.Move + err = appCtx.DB().Q().Eager( + "Orders.Entitlement", + ).Where("show = TRUE").Find(&move, newPPMShipment.Shipment.MoveTaskOrderID) + if err != nil { + return nil, apperror.NewNotFoundError(newPPMShipment.ID, " error querying move") + } + orders := move.Orders + if orders.Entitlement.DBAuthorizedWeight == nil { + return nil, apperror.NewNotFoundError(newPPMShipment.ID, " DB authorized weight cannot be nil") + } + + contractDate := newPPMShipment.ExpectedDepartureDate + contract, err := serviceparamvaluelookups.FetchContract(appCtx, contractDate) + if err != nil { + return nil, err + } + + // since the max incentive is based off of the authorized weight entitlement and that value CAN change + // we will calculate the max incentive each time it is called + maxIncentive, err := f.calculatePrice(appCtx, newPPMShipment, unit.Pound(*orders.Entitlement.DBAuthorizedWeight), contract, true) + if err != nil { + return nil, err + } + + return maxIncentive, nil +} + func (f *estimatePPM) finalIncentive(appCtx appcontext.AppContext, oldPPMShipment models.PPMShipment, newPPMShipment *models.PPMShipment, checks ...ppmShipmentValidator) (*unit.Cents, error) { if newPPMShipment.Status != models.PPMShipmentStatusWaitingOnCustomer && newPPMShipment.Status != models.PPMShipmentStatusNeedsCloseout { return oldPPMShipment.FinalIncentive, nil @@ -270,7 +315,7 @@ func (f *estimatePPM) finalIncentive(appCtx appcontext.AppContext, oldPPMShipmen return nil, err } - finalIncentive, err = f.calculatePrice(appCtx, newPPMShipment, newTotalWeight, contract) + finalIncentive, err = f.calculatePrice(appCtx, newPPMShipment, newTotalWeight, contract, false) if err != nil { return nil, err } @@ -314,27 +359,55 @@ func SumWeightTickets(ppmShipment, newPPMShipment models.PPMShipment) (originalT // calculatePrice returns an incentive value for the ppm shipment as if we were pricing the service items for // an HHG shipment with the same values for a payment request. In this case we're not persisting service items, // MTOServiceItems or PaymentRequestServiceItems, to the database to avoid unnecessary work and get a quicker result. -func (f estimatePPM) calculatePrice(appCtx appcontext.AppContext, ppmShipment *models.PPMShipment, totalWeightFromWeightTickets unit.Pound, contract models.ReContract) (*unit.Cents, error) { +// we use this when calculating the estimated, final, and max incentive values +func (f estimatePPM) calculatePrice(appCtx appcontext.AppContext, ppmShipment *models.PPMShipment, totalWeight unit.Pound, contract models.ReContract, isMaxIncentiveCheck bool) (*unit.Cents, error) { logger := appCtx.Logger() zeroTotal := false serviceItemsToPrice := BaseServiceItems(ppmShipment.ShipmentID) + var move models.Move + err := appCtx.DB().Q().Eager( + "Orders.Entitlement", + "Orders.OriginDutyLocation.Address", + "Orders.NewDutyLocation.Address", + ).Where("show = TRUE").Find(&move, ppmShipment.Shipment.MoveTaskOrderID) + if err != nil { + return nil, apperror.NewNotFoundError(ppmShipment.ID, " error querying move") + } + orders := move.Orders + if orders.Entitlement.DBAuthorizedWeight == nil { + return nil, apperror.NewNotFoundError(ppmShipment.ID, " DB authorized weight cannot be nil") + } + // Replace linehaul pricer with shorthaul pricer if move is within the same Zip3 var pickupPostal, destPostal string - // Check different address values for a postal code - if ppmShipment.ActualPickupPostalCode != nil { - pickupPostal = *ppmShipment.ActualPickupPostalCode - } else if ppmShipment.PickupAddress.PostalCode != "" { - pickupPostal = ppmShipment.PickupAddress.PostalCode - } + // if we are getting the max incentive, we want to use the addresses on the orders, else use what's on the shipment + if isMaxIncentiveCheck { + if orders.OriginDutyLocation.Address.PostalCode != "" { + pickupPostal = orders.OriginDutyLocation.Address.PostalCode + } else { + return nil, apperror.NewNotFoundError(ppmShipment.ID, " No postal code for origin duty location on orders when comparing postal codes") + } - // Same for destination - if ppmShipment.ActualDestinationPostalCode != nil { - destPostal = *ppmShipment.ActualDestinationPostalCode - } else if ppmShipment.DestinationAddress.PostalCode != "" { - destPostal = ppmShipment.DestinationAddress.PostalCode + if orders.NewDutyLocation.Address.PostalCode != "" { + destPostal = orders.NewDutyLocation.Address.PostalCode + } else { + return nil, apperror.NewNotFoundError(ppmShipment.ID, " No postal code for destination duty location on orders when comparing postal codes") + } + } else { + if ppmShipment.ActualPickupPostalCode != nil { + pickupPostal = *ppmShipment.ActualPickupPostalCode + } else if ppmShipment.PickupAddress.PostalCode != "" { + pickupPostal = ppmShipment.PickupAddress.PostalCode + } + + if ppmShipment.ActualDestinationPostalCode != nil { + destPostal = *ppmShipment.ActualDestinationPostalCode + } else if ppmShipment.DestinationAddress.PostalCode != "" { + destPostal = ppmShipment.DestinationAddress.PostalCode + } } if pickupPostal[0:3] == destPostal[0:3] { @@ -349,9 +422,15 @@ func (f estimatePPM) calculatePrice(appCtx appcontext.AppContext, ppmShipment *m } var mtoShipment models.MTOShipment - if totalWeightFromWeightTickets > 0 { + if totalWeight > 0 && !isMaxIncentiveCheck { // Reassign ppm shipment fields to their expected location on the mto shipment for dates, addresses, weights ... - mtoShipment = MapPPMShipmentFinalFields(*ppmShipment, totalWeightFromWeightTickets) + mtoShipment = MapPPMShipmentFinalFields(*ppmShipment, totalWeight) + } else if totalWeight > 0 && isMaxIncentiveCheck { + mtoShipment, err = MapPPMShipmentMaxIncentiveFields(appCtx, *ppmShipment, totalWeight) + if err != nil { + logger.Error("unable to map PPM fields for max incentive", zap.Error(err)) + return nil, err + } } else { // Reassign ppm shipment fields to their expected location on the mto shipment for dates, addresses, weights ... mtoShipment, err = MapPPMShipmentEstimatedFields(appCtx, *ppmShipment) @@ -859,10 +938,24 @@ func priceAdditionalDaySIT(appCtx appcontext.AppContext, pricer services.ParamsP // mapPPMShipmentEstimatedFields remaps our PPMShipment specific information into the fields where the service param lookups // expect to find them on the MTOShipment model. This is only in-memory and shouldn't get saved to the database. func MapPPMShipmentEstimatedFields(appCtx appcontext.AppContext, ppmShipment models.PPMShipment) (models.MTOShipment, error) { - // we have access to the MoveTaskOrderID in the ppmShipment object so we can use that to get the customer's maximum weight entitlement + + ppmShipment.Shipment.ActualPickupDate = &ppmShipment.ExpectedDepartureDate + ppmShipment.Shipment.RequestedPickupDate = &ppmShipment.ExpectedDepartureDate + ppmShipment.Shipment.PickupAddress = &models.Address{PostalCode: ppmShipment.PickupAddress.PostalCode} + ppmShipment.Shipment.DestinationAddress = &models.Address{PostalCode: ppmShipment.DestinationAddress.PostalCode} + ppmShipment.Shipment.PrimeActualWeight = ppmShipment.EstimatedWeight + + return ppmShipment.Shipment, nil +} + +// MapPPMShipmentMaxIncentiveFields remaps our PPMShipment specific information into the fields where the service param lookups +// expect to find them on the MTOShipment model. This is only in-memory and shouldn't get saved to the database. +func MapPPMShipmentMaxIncentiveFields(appCtx appcontext.AppContext, ppmShipment models.PPMShipment, totalWeight unit.Pound) (models.MTOShipment, error) { var move models.Move err := appCtx.DB().Q().Eager( "Orders.Entitlement", + "Orders.OriginDutyLocation.Address", + "Orders.NewDutyLocation.Address", ).Where("show = TRUE").Find(&move, ppmShipment.Shipment.MoveTaskOrderID) if err != nil { return models.MTOShipment{}, apperror.NewNotFoundError(ppmShipment.ID, " error querying move") @@ -874,9 +967,9 @@ func MapPPMShipmentEstimatedFields(appCtx appcontext.AppContext, ppmShipment mod ppmShipment.Shipment.ActualPickupDate = &ppmShipment.ExpectedDepartureDate ppmShipment.Shipment.RequestedPickupDate = &ppmShipment.ExpectedDepartureDate - ppmShipment.Shipment.PickupAddress = &models.Address{PostalCode: ppmShipment.PickupAddress.PostalCode} - ppmShipment.Shipment.DestinationAddress = &models.Address{PostalCode: ppmShipment.DestinationAddress.PostalCode} - ppmShipment.Shipment.PrimeActualWeight = (*unit.Pound)(orders.Entitlement.DBAuthorizedWeight) + ppmShipment.Shipment.PickupAddress = &models.Address{PostalCode: orders.OriginDutyLocation.Address.PostalCode} + ppmShipment.Shipment.DestinationAddress = &models.Address{PostalCode: orders.NewDutyLocation.Address.PostalCode} + ppmShipment.Shipment.PrimeActualWeight = &totalWeight return ppmShipment.Shipment, nil } diff --git a/pkg/services/ppmshipment/ppm_estimator_test.go b/pkg/services/ppmshipment/ppm_estimator_test.go index 3b3964b2178..873b21274c1 100644 --- a/pkg/services/ppmshipment/ppm_estimator_test.go +++ b/pkg/services/ppmshipment/ppm_estimator_test.go @@ -514,9 +514,86 @@ func (suite *PPMShipmentSuite) TestPPMEstimator() { }) suite.Run("Estimated Incentive", func() { - suite.Run("Estimated Incentive - Success", func() { + suite.Run("Estimated Incentive - Success using estimated weight and not db authorized weight", func() { + // when the PPM shipment is in draft, we use the estimated weight and not the db authorized weight + oldPPMShipment := factory.BuildPPMShipment(suite.DB(), []factory.Customization{ + { + Model: models.PPMShipment{ + Status: models.PPMShipmentStatusDraft, + }, + }, + }, nil) + setupPricerData() + + // shipment has locations and date but is now updating the estimated weight for the first time + estimatedWeight := unit.Pound(5000) + newPPM := oldPPMShipment + newPPM.EstimatedWeight = &estimatedWeight + + mockedPaymentRequestHelper.On( + "FetchServiceParamsForServiceItems", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("[]models.MTOServiceItem")).Return(serviceParams, nil) + + // DTOD distance is going to be less than the HHG Rand McNally distance of 2361 miles + mockedPlanner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), + "50309", "30813").Return(2294, nil) + + ppmEstimate, _, err := ppmEstimator.EstimateIncentiveWithDefaultChecks(suite.AppContextForTest(), oldPPMShipment, &newPPM) + suite.NilOrNoVerrs(err) + + mockedPlanner.AssertCalled(suite.T(), "ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), + "50309", "30813") + mockedPaymentRequestHelper.AssertCalled(suite.T(), "FetchServiceParamsForServiceItems", mock.AnythingOfType("*appcontext.appContext"), mock.AnythingOfType("[]models.MTOServiceItem")) + + suite.Equal(oldPPMShipment.PickupAddress.PostalCode, newPPM.PickupAddress.PostalCode) + suite.Equal(unit.Pound(5000), *newPPM.EstimatedWeight) + suite.Equal(unit.Cents(70064364), *ppmEstimate) + }) + + suite.Run("Estimated Incentive - Success using db authorize weight and not estimated incentive", func() { + // when the PPM shipment is NOT in draft, we use the db authorized weight and not the estimated weight + oldPPMShipment := factory.BuildPPMShipment(suite.DB(), []factory.Customization{ + { + Model: models.PPMShipment{ + Status: models.PPMShipmentStatusNeedsCloseout, + }, + }, + }, nil) + setupPricerData() + + // shipment has locations and date but is now updating the estimated weight for the first time + estimatedWeight := unit.Pound(5000) + newPPM := oldPPMShipment + newPPM.EstimatedWeight = &estimatedWeight + + mockedPaymentRequestHelper.On( + "FetchServiceParamsForServiceItems", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("[]models.MTOServiceItem")).Return(serviceParams, nil) + + // DTOD distance is going to be less than the HHG Rand McNally distance of 2361 miles + mockedPlanner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), + "50309", "30813").Return(2294, nil) + + ppmEstimate, _, err := ppmEstimator.EstimateIncentiveWithDefaultChecks(suite.AppContextForTest(), oldPPMShipment, &newPPM) + suite.NilOrNoVerrs(err) + + mockedPlanner.AssertCalled(suite.T(), "ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), + "50309", "30813") + mockedPaymentRequestHelper.AssertCalled(suite.T(), "FetchServiceParamsForServiceItems", mock.AnythingOfType("*appcontext.appContext"), mock.AnythingOfType("[]models.MTOServiceItem")) + + suite.Equal(oldPPMShipment.PickupAddress.PostalCode, newPPM.PickupAddress.PostalCode) + suite.Equal(unit.Pound(5000), *newPPM.EstimatedWeight) + suite.Equal(unit.Cents(1000000), *ppmEstimate) + }) + + suite.Run("Estimated Incentive - Success when old Estimated Incentive is zero", func() { oldPPMShipment := factory.BuildMinimalPPMShipment(suite.DB(), nil, nil) + zeroIncentive := unit.Cents(0) + oldPPMShipment.EstimatedIncentive = &zeroIncentive + setupPricerData() // shipment has locations and date but is now updating the estimated weight for the first time @@ -542,7 +619,7 @@ func (suite *PPMShipmentSuite) TestPPMEstimator() { suite.Equal(oldPPMShipment.PickupAddress.PostalCode, newPPM.PickupAddress.PostalCode) suite.Equal(unit.Pound(5000), *newPPM.EstimatedWeight) - suite.Equal(unit.Cents(112102682), *ppmEstimate) + suite.Equal(unit.Cents(70064364), *ppmEstimate) }) suite.Run("Estimated Incentive - Success - clears advance and advance requested values", func() { @@ -573,7 +650,7 @@ func (suite *PPMShipmentSuite) TestPPMEstimator() { suite.NilOrNoVerrs(err) suite.Nil(newPPM.HasRequestedAdvance) suite.Nil(newPPM.AdvanceAmountRequested) - suite.Equal(unit.Cents(112102682), *ppmEstimate) + suite.Equal(unit.Cents(38213948), *ppmEstimate) }) suite.Run("Estimated Incentive - does not change when required fields are the same", func() { @@ -638,6 +715,44 @@ func (suite *PPMShipmentSuite) TestPPMEstimator() { }) }) + suite.Run("Max Incentive", func() { + suite.Run("Max Incentive - Success", func() { + oldPPMShipment := factory.BuildPPMShipment(suite.DB(), nil, nil) + setupPricerData() + + estimatedWeight := unit.Pound(5000) + newPPM := oldPPMShipment + newPPM.EstimatedWeight = &estimatedWeight + + mockedPaymentRequestHelper.On( + "FetchServiceParamsForServiceItems", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("[]models.MTOServiceItem")).Return(serviceParams, nil) + + mockedPlanner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), + "50309", "30813").Return(2294, nil) + + maxIncentive, err := ppmEstimator.MaxIncentive(suite.AppContextForTest(), oldPPMShipment, &newPPM) + suite.NilOrNoVerrs(err) + + mockedPlanner.AssertCalled(suite.T(), "ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), + "50309", "30813") + mockedPaymentRequestHelper.AssertCalled(suite.T(), "FetchServiceParamsForServiceItems", mock.AnythingOfType("*appcontext.appContext"), mock.AnythingOfType("[]models.MTOServiceItem")) + + suite.Equal(unit.Cents(112102682), *maxIncentive) + }) + + suite.Run("Max Incentive - Success - is skipped when Estimated Weight is missing", func() { + oldPPMShipment := factory.BuildMinimalPPMShipment(suite.DB(), nil, nil) + + newPPM := oldPPMShipment + newPPM.DestinationAddress.PostalCode = "94040" + _, err := ppmEstimator.MaxIncentive(suite.AppContextForTest(), oldPPMShipment, &newPPM) + suite.NoError(err) + suite.Nil(newPPM.MaxIncentive) + }) + }) + suite.Run("Final Incentive", func() { actualMoveDate := time.Date(2020, time.March, 14, 0, 0, 0, 0, time.UTC) suite.Run("Final Incentive - Success", func() { diff --git a/pkg/services/ppmshipment/ppm_shipment_creator.go b/pkg/services/ppmshipment/ppm_shipment_creator.go index d367946555c..75f8e50f7ae 100644 --- a/pkg/services/ppmshipment/ppm_shipment_creator.go +++ b/pkg/services/ppmshipment/ppm_shipment_creator.go @@ -27,6 +27,7 @@ func NewPPMShipmentCreator(estimator services.PPMEstimator, addressCreator servi checkShipmentID(), checkPPMShipmentID(), checkRequiredFields(), + checkPPMShipmentSequenceValidForCreate(), }, } } @@ -146,6 +147,12 @@ func (f *ppmShipmentCreator) createPPMShipment(appCtx appcontext.AppContext, ppm ppmShipment.EstimatedIncentive = estimatedIncentive ppmShipment.SITEstimatedCost = estimatedSITCost + maxIncentive, err := f.estimator.MaxIncentive(appCtx, models.PPMShipment{}, ppmShipment) + if err != nil { + return err + } + ppmShipment.MaxIncentive = maxIncentive + // Validate ppm shipment model object and save it to DB verrs, err := txnAppCtx.DB().ValidateAndCreate(ppmShipment) // Check validation errors diff --git a/pkg/services/ppmshipment/ppm_shipment_creator_test.go b/pkg/services/ppmshipment/ppm_shipment_creator_test.go index 0554c41d8a7..bed5fa08a7e 100644 --- a/pkg/services/ppmshipment/ppm_shipment_creator_test.go +++ b/pkg/services/ppmshipment/ppm_shipment_creator_test.go @@ -97,6 +97,13 @@ func (suite *PPMShipmentSuite) TestPPMShipmentCreator() { mock.AnythingOfType("*models.PPMShipment"), ).Return(nil, nil, nil).Once() + ppmEstimator.On( + "MaxIncentive", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("models.PPMShipment"), + mock.AnythingOfType("*models.PPMShipment"), + ).Return(nil, nil).Once() + createdPPMShipment, err := ppmShipmentCreator.CreatePPMShipmentWithDefaultCheck(appCtx, subtestData.newPPMShipment) suite.Nil(err) @@ -139,6 +146,13 @@ func (suite *PPMShipmentSuite) TestPPMShipmentCreator() { mock.AnythingOfType("*models.PPMShipment"), ).Return(nil, nil, nil).Once() + ppmEstimator.On( + "MaxIncentive", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("models.PPMShipment"), + mock.AnythingOfType("*models.PPMShipment"), + ).Return(nil, nil).Once() + createdPPMShipment, err := ppmShipmentCreator.CreatePPMShipmentWithDefaultCheck(appCtx, subtestData.newPPMShipment) suite.Nil(err) @@ -222,6 +236,7 @@ func (suite *PPMShipmentSuite) TestPPMShipmentCreator() { estimatedWeight := unit.Pound(2450) hasProGear := false estimatedIncentive := unit.Cents(123456) + maxIncentive := unit.Cents(123456) pickupAddress := models.Address{ StreetAddress1: "123 Any Pickup Street", @@ -285,6 +300,13 @@ func (suite *PPMShipmentSuite) TestPPMShipmentCreator() { mock.AnythingOfType("*models.PPMShipment"), ).Return(&estimatedIncentive, nil, nil).Once() + ppmEstimator.On( + "MaxIncentive", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("models.PPMShipment"), + mock.AnythingOfType("*models.PPMShipment"), + ).Return(&maxIncentive, nil).Once() + createdPPMShipment, err := ppmShipmentCreator.CreatePPMShipmentWithDefaultCheck(appCtx, subtestData.newPPMShipment) suite.Nil(err) @@ -297,6 +319,7 @@ func (suite *PPMShipmentSuite) TestPPMShipmentCreator() { suite.Equal(&hasProGear, createdPPMShipment.HasProGear) suite.Equal(models.PPMShipmentStatusSubmitted, createdPPMShipment.Status) suite.Equal(&estimatedIncentive, createdPPMShipment.EstimatedIncentive) + suite.Equal(&maxIncentive, createdPPMShipment.MaxIncentive) suite.NotZero(createdPPMShipment.CreatedAt) suite.NotZero(createdPPMShipment.UpdatedAt) suite.Equal(pickupAddress.StreetAddress1, createdPPMShipment.PickupAddress.StreetAddress1) diff --git a/pkg/services/ppmshipment/ppm_shipment_updater.go b/pkg/services/ppmshipment/ppm_shipment_updater.go index b9539286c4c..f8cc99b8d30 100644 --- a/pkg/services/ppmshipment/ppm_shipment_updater.go +++ b/pkg/services/ppmshipment/ppm_shipment_updater.go @@ -24,6 +24,7 @@ var PPMShipmentUpdaterChecks = []ppmShipmentValidator{ checkPPMShipmentID(), checkRequiredFields(), checkAdvanceAmountRequested(), + checkPPMShipmentSequenceValidForUpdate(), } func NewPPMShipmentUpdater(ppmEstimator services.PPMEstimator, addressCreator services.AddressCreator, addressUpdater services.AddressUpdater) services.PPMShipmentUpdater { @@ -125,6 +126,18 @@ func (f *ppmShipmentUpdater) updatePPMShipment(appCtx appcontext.AppContext, ppm updatedPPMShipment.EstimatedIncentive = estimatedIncentive updatedPPMShipment.SITEstimatedCost = estimatedSITCost + // if the PPM shipment is past closeout then we should not calculate the max incentive, it is already set in stone + if oldPPMShipment.Status != models.PPMShipmentStatusWaitingOnCustomer && + oldPPMShipment.Status != models.PPMShipmentStatusCloseoutComplete && + oldPPMShipment.Status != models.PPMShipmentStatusComplete && + oldPPMShipment.Status != models.PPMShipmentStatusNeedsCloseout { + maxIncentive, err := f.estimator.MaxIncentive(appCtx, *oldPPMShipment, updatedPPMShipment) + if err != nil { + return err + } + updatedPPMShipment.MaxIncentive = maxIncentive + } + if appCtx.Session() != nil { if appCtx.Session().IsOfficeUser() { edited := models.PPMAdvanceStatusEdited diff --git a/pkg/services/ppmshipment/ppm_shipment_updater_test.go b/pkg/services/ppmshipment/ppm_shipment_updater_test.go index c33d0e4c8b4..75f85f97646 100644 --- a/pkg/services/ppmshipment/ppm_shipment_updater_test.go +++ b/pkg/services/ppmshipment/ppm_shipment_updater_test.go @@ -33,7 +33,7 @@ func (suite *PPMShipmentSuite) TestUpdatePPMShipment() { } // setUpForTests - Sets up objects/mocks that need to be set up on a per-test basis. - setUpForTests := func(estimatedIncentiveAmount *unit.Cents, sitEstimatedCost *unit.Cents, estimatedIncentiveError error) (subtestData updateSubtestData) { + setUpForTests := func(estimatedIncentiveAmount *unit.Cents, sitEstimatedCost *unit.Cents, maxIncentiveAmount *unit.Cents, estimatedIncentiveError error) (subtestData updateSubtestData) { ppmEstimator := mocks.PPMEstimator{} ppmEstimator. On( @@ -53,6 +53,15 @@ func (suite *PPMShipmentSuite) TestUpdatePPMShipment() { ). Return(estimatedIncentiveAmount, sitEstimatedCost, estimatedIncentiveError) + ppmEstimator. + On( + "MaxIncentive", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("models.PPMShipment"), + mock.AnythingOfType("*models.PPMShipment"), + ). + Return(maxIncentiveAmount, nil) + addressCreator := address.NewAddressCreator() addressUpdater := address.NewAddressUpdater() subtestData.ppmShipmentUpdater = NewPPMShipmentUpdater(&ppmEstimator, addressCreator, addressUpdater) @@ -80,6 +89,15 @@ func (suite *PPMShipmentSuite) TestUpdatePPMShipment() { ). Return(estimatedIncentiveAmount, sitEstimatedCost, estimatedIncentiveError) + ppmEstimator. + On( + "MaxIncentive", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("models.PPMShipment"), + mock.AnythingOfType("*models.PPMShipment"), + ). + Return(nil, nil) + addressCreator := address.NewAddressCreator() addressUpdater := address.NewAddressUpdater() subtestData.ppmShipmentUpdater = NewPPMShipmentUpdater(&ppmEstimator, addressCreator, addressUpdater) @@ -407,7 +425,7 @@ func (suite *PPMShipmentSuite) TestUpdatePPMShipment() { suite.Run("Can successfully update a PPMShipment - edit estimated dates & locations", func() { appCtx := suite.AppContextWithSessionForTest(&auth.Session{}) - subtestData := setUpForTests(nil, nil, nil) + subtestData := setUpForTests(nil, nil, nil, nil) originalPPM := factory.BuildPPMShipment(appCtx.DB(), []factory.Customization{ { @@ -481,7 +499,7 @@ func (suite *PPMShipmentSuite) TestUpdatePPMShipment() { suite.Run("Can successfully update a PPMShipment and shipment market code reflects international shipment", func() { appCtx := suite.AppContextWithSessionForTest(&auth.Session{}) - subtestData := setUpForTests(nil, nil, nil) + subtestData := setUpForTests(nil, nil, nil, nil) originalPPM := factory.BuildPPMShipment(appCtx.DB(), []factory.Customization{ { @@ -544,8 +562,9 @@ func (suite *PPMShipmentSuite) TestUpdatePPMShipment() { appCtx := suite.AppContextWithSessionForTest(&auth.Session{}) newFakeEstimatedIncentive := models.CentPointer(unit.Cents(2000000)) + newFakeMaxIncentive := models.CentPointer(unit.Cents(5000000)) - subtestData := setUpForTests(newFakeEstimatedIncentive, nil, nil) + subtestData := setUpForTests(newFakeEstimatedIncentive, nil, newFakeMaxIncentive, nil) originalPPM := factory.BuildMinimalPPMShipment(appCtx.DB(), []factory.Customization{ { @@ -576,14 +595,14 @@ func (suite *PPMShipmentSuite) TestUpdatePPMShipment() { suite.Equal(newPPM.ExpectedDepartureDate.Format(dateOnly), updatedPPM.ExpectedDepartureDate.Format(dateOnly)) suite.Equal(newPPM.SITExpected, updatedPPM.SITExpected) suite.Equal(*newFakeEstimatedIncentive, *updatedPPM.EstimatedIncentive) - + suite.Equal(*newFakeMaxIncentive, *updatedPPM.MaxIncentive) suite.Equal(updatedPPM.Shipment.MarketCode, models.MarketCodeDomestic) }) suite.Run("Can successfully update a PPMShipment - add estimated weights - no pro gear", func() { appCtx := suite.AppContextWithSessionForTest(&auth.Session{}) - subtestData := setUpForTests(fakeEstimatedIncentive, nil, nil) + subtestData := setUpForTests(fakeEstimatedIncentive, nil, nil, nil) originalPPM := factory.BuildMinimalPPMShipment(appCtx.DB(), nil, nil) @@ -614,7 +633,7 @@ func (suite *PPMShipmentSuite) TestUpdatePPMShipment() { suite.Run("Can successfully update a PPMShipment - add estimated weights - has pro gear", func() { appCtx := suite.AppContextWithSessionForTest(&auth.Session{}) - subtestData := setUpForTests(fakeEstimatedIncentive, nil, nil) + subtestData := setUpForTests(fakeEstimatedIncentive, nil, nil, nil) originalPPM := factory.BuildMinimalPPMShipment(appCtx.DB(), nil, nil) @@ -649,7 +668,7 @@ func (suite *PPMShipmentSuite) TestUpdatePPMShipment() { newFakeEstimatedIncentive := models.CentPointer(unit.Cents(2000000)) - subtestData := setUpForTests(newFakeEstimatedIncentive, nil, nil) + subtestData := setUpForTests(newFakeEstimatedIncentive, nil, nil, nil) originalPPM := factory.BuildMinimalPPMShipment(appCtx.DB(), []factory.Customization{ { @@ -688,7 +707,7 @@ func (suite *PPMShipmentSuite) TestUpdatePPMShipment() { newFakeEstimatedIncentive := models.CentPointer(unit.Cents(2000000)) - subtestData := setUpForTests(newFakeEstimatedIncentive, nil, nil) + subtestData := setUpForTests(newFakeEstimatedIncentive, nil, nil, nil) originalPPM := factory.BuildMinimalPPMShipment(appCtx.DB(), []factory.Customization{ { @@ -739,7 +758,7 @@ func (suite *PPMShipmentSuite) TestUpdatePPMShipment() { HasRequestedAdvance: models.BoolPointer(false), } - subtestData := setUpForTests(originalPPM.EstimatedIncentive, nil, nil) + subtestData := setUpForTests(originalPPM.EstimatedIncentive, nil, nil, nil) updatedPPM, err := subtestData.ppmShipmentUpdater.UpdatePPMShipmentWithDefaultCheck(appCtx, &newPPM, originalPPM.ShipmentID) @@ -776,7 +795,7 @@ func (suite *PPMShipmentSuite) TestUpdatePPMShipment() { AdvanceAmountRequested: models.CentPointer(unit.Cents(300000)), } - subtestData := setUpForTests(originalPPM.EstimatedIncentive, nil, nil) + subtestData := setUpForTests(originalPPM.EstimatedIncentive, nil, nil, nil) updatedPPM, err := subtestData.ppmShipmentUpdater.UpdatePPMShipmentWithDefaultCheck(appCtx, &newPPM, originalPPM.ShipmentID) @@ -814,7 +833,7 @@ func (suite *PPMShipmentSuite) TestUpdatePPMShipment() { AdvanceAmountRequested: models.CentPointer(unit.Cents(200000)), } - subtestData := setUpForTests(originalPPM.EstimatedIncentive, nil, nil) + subtestData := setUpForTests(originalPPM.EstimatedIncentive, nil, nil, nil) updatedPPM, err := subtestData.ppmShipmentUpdater.UpdatePPMShipmentWithDefaultCheck(appCtx, &newPPM, originalPPM.ShipmentID) @@ -858,7 +877,7 @@ func (suite *PPMShipmentSuite) TestUpdatePPMShipment() { AdvanceStatus: &approved, } - subtestData := setUpForTests(originalPPM.EstimatedIncentive, nil, nil) + subtestData := setUpForTests(originalPPM.EstimatedIncentive, nil, nil, nil) updatedPPM, err := subtestData.ppmShipmentUpdater.UpdatePPMShipmentWithDefaultCheck(appCtx, &newPPM, originalPPM.ShipmentID) @@ -900,7 +919,7 @@ func (suite *PPMShipmentSuite) TestUpdatePPMShipment() { AdvanceStatus: &rejected, } - subtestData := setUpForTests(originalPPM.EstimatedIncentive, nil, nil) + subtestData := setUpForTests(originalPPM.EstimatedIncentive, nil, nil, nil) updatedPPM, err := subtestData.ppmShipmentUpdater.UpdatePPMShipmentWithDefaultCheck(appCtx, &newPPM, originalPPM.ShipmentID) @@ -942,7 +961,7 @@ func (suite *PPMShipmentSuite) TestUpdatePPMShipment() { AdvanceStatus: &approved, } - subtestData := setUpForTests(originalPPM.EstimatedIncentive, nil, nil) + subtestData := setUpForTests(originalPPM.EstimatedIncentive, nil, nil, nil) updatedPPM, err := subtestData.ppmShipmentUpdater.UpdatePPMShipmentWithDefaultCheck(appCtx, &newPPM, originalPPM.ShipmentID) @@ -982,7 +1001,7 @@ func (suite *PPMShipmentSuite) TestUpdatePPMShipment() { HasRequestedAdvance: models.BoolPointer(false), } - subtestData := setUpForTests(originalPPM.EstimatedIncentive, nil, nil) + subtestData := setUpForTests(originalPPM.EstimatedIncentive, nil, nil, nil) updatedPPM, err := subtestData.ppmShipmentUpdater.UpdatePPMShipmentWithDefaultCheck(appCtx, &newPPM, originalPPM.ShipmentID) @@ -1009,7 +1028,7 @@ func (suite *PPMShipmentSuite) TestUpdatePPMShipment() { newFakeEstimatedIncentive := models.CentPointer(unit.Cents(2000000)) - subtestData := setUpForTests(newFakeEstimatedIncentive, nil, nil) + subtestData := setUpForTests(newFakeEstimatedIncentive, nil, nil, nil) sitLocation := models.SITLocationTypeOrigin originalPPM := factory.BuildPPMShipment(suite.DB(), []factory.Customization{ @@ -1056,7 +1075,7 @@ func (suite *PPMShipmentSuite) TestUpdatePPMShipment() { newFakeEstimatedIncentive := models.CentPointer(unit.Cents(2000000)) newFakeSITEstimatedCost := models.CentPointer(unit.Cents(62500)) - subtestData := setUpForTests(newFakeEstimatedIncentive, newFakeSITEstimatedCost, nil) + subtestData := setUpForTests(newFakeEstimatedIncentive, newFakeSITEstimatedCost, nil, nil) sitLocation := models.SITLocationTypeOrigin originalPPM := factory.BuildPPMShipment(suite.DB(), []factory.Customization{ @@ -1130,7 +1149,7 @@ func (suite *PPMShipmentSuite) TestUpdatePPMShipment() { suite.Run("Can't update if Shipment can't be found", func() { badMTOShipmentID := uuid.Must(uuid.NewV4()) - subtestData := setUpForTests(nil, nil, nil) + subtestData := setUpForTests(nil, nil, nil, nil) updatedPPMShipment, err := subtestData.ppmShipmentUpdater.UpdatePPMShipmentWithDefaultCheck(suite.AppContextWithSessionForTest(&auth.Session{}), &models.PPMShipment{}, badMTOShipmentID) @@ -1144,7 +1163,7 @@ func (suite *PPMShipmentSuite) TestUpdatePPMShipment() { suite.Run("Can't update if there is invalid input", func() { appCtx := suite.AppContextWithSessionForTest(&auth.Session{}) - subtestData := setUpForTests(nil, nil, nil) + subtestData := setUpForTests(nil, nil, nil, nil) originalPPMShipment := factory.BuildPPMShipment(appCtx.DB(), nil, nil) @@ -1167,7 +1186,7 @@ func (suite *PPMShipmentSuite) TestUpdatePPMShipment() { appCtx := suite.AppContextWithSessionForTest(&auth.Session{}) fakeEstimatedIncentiveError := errors.New("failed to calculate incentive") - subtestData := setUpForTests(nil, nil, fakeEstimatedIncentiveError) + subtestData := setUpForTests(nil, nil, nil, fakeEstimatedIncentiveError) originalPPMShipment := factory.BuildPPMShipment(appCtx.DB(), nil, nil) @@ -1186,7 +1205,7 @@ func (suite *PPMShipmentSuite) TestUpdatePPMShipment() { suite.Run("Can successfully update a PPMShipment - add W-2 address", func() { appCtx := suite.AppContextWithSessionForTest(&auth.Session{}) - subtestData := setUpForTests(fakeEstimatedIncentive, nil, nil) + subtestData := setUpForTests(fakeEstimatedIncentive, nil, nil, nil) originalPPM := factory.BuildMinimalPPMShipment(appCtx.DB(), nil, nil) @@ -1221,7 +1240,7 @@ func (suite *PPMShipmentSuite) TestUpdatePPMShipment() { suite.Run("Can successfully update a PPMShipment - add W-2 address with empty strings for optional fields", func() { appCtx := suite.AppContextWithSessionForTest(&auth.Session{}) - subtestData := setUpForTests(fakeEstimatedIncentive, nil, nil) + subtestData := setUpForTests(fakeEstimatedIncentive, nil, nil, nil) originalPPM := factory.BuildMinimalPPMShipment(appCtx.DB(), nil, nil) @@ -1257,7 +1276,7 @@ func (suite *PPMShipmentSuite) TestUpdatePPMShipment() { suite.Run("Can successfully update a PPMShipment - modify W-2 address", func() { appCtx := suite.AppContextWithSessionForTest(&auth.Session{}) - subtestData := setUpForTests(fakeEstimatedIncentive, nil, nil) + subtestData := setUpForTests(fakeEstimatedIncentive, nil, nil, nil) address := factory.BuildAddress(appCtx.DB(), nil, nil) originalPPM := factory.BuildMinimalPPMShipment(appCtx.DB(), []factory.Customization{ @@ -1300,7 +1319,7 @@ func (suite *PPMShipmentSuite) TestUpdatePPMShipment() { suite.Run("Can successfully update a PPMShipment - add Pickup and Destination address", func() { appCtx := suite.AppContextWithSessionForTest(&auth.Session{}) - subtestData := setUpForTests(fakeEstimatedIncentive, nil, nil) + subtestData := setUpForTests(fakeEstimatedIncentive, nil, nil, nil) originalPPM := factory.BuildMinimalPPMShipment(appCtx.DB(), nil, nil) @@ -1413,7 +1432,7 @@ func (suite *PPMShipmentSuite) TestUpdatePPMShipment() { newFakeEstimatedIncentive := models.CentPointer(unit.Cents(2000000)) newFakeSITEstimatedCost := models.CentPointer(unit.Cents(62500)) - subtestData := setUpForTests(newFakeEstimatedIncentive, newFakeSITEstimatedCost, nil) + subtestData := setUpForTests(newFakeEstimatedIncentive, newFakeSITEstimatedCost, nil, nil) sitLocationDestination := models.SITLocationTypeDestination entryDate := time.Date(2020, time.March, 15, 0, 0, 0, 0, time.UTC) mtoShipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ @@ -1469,7 +1488,7 @@ func (suite *PPMShipmentSuite) TestUpdatePPMShipment() { newFakeEstimatedIncentive := models.CentPointer(unit.Cents(2000000)) newFakeSITEstimatedCost := models.CentPointer(unit.Cents(62500)) - subtestData := setUpForTests(newFakeEstimatedIncentive, newFakeSITEstimatedCost, nil) + subtestData := setUpForTests(newFakeEstimatedIncentive, newFakeSITEstimatedCost, nil, nil) sitLocationDestination := models.SITLocationTypeDestination entryDate := time.Date(2020, time.March, 15, 0, 0, 0, 0, time.UTC) streetAddress1 := "10642 N Second Ave" @@ -1524,7 +1543,7 @@ func (suite *PPMShipmentSuite) TestUpdatePPMShipment() { newFakeEstimatedIncentive := models.CentPointer(unit.Cents(2000000)) newFakeSITEstimatedCost := models.CentPointer(unit.Cents(62500)) - subtestData := setUpForTests(newFakeEstimatedIncentive, newFakeSITEstimatedCost, nil) + subtestData := setUpForTests(newFakeEstimatedIncentive, newFakeSITEstimatedCost, nil, nil) sitLocationDestination := models.SITLocationTypeDestination entryDate := time.Date(2020, time.March, 15, 0, 0, 0, 0, time.UTC) mtoShipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ diff --git a/pkg/services/ppmshipment/rules.go b/pkg/services/ppmshipment/rules.go index 47b48c34913..072746eeab6 100644 --- a/pkg/services/ppmshipment/rules.go +++ b/pkg/services/ppmshipment/rules.go @@ -55,6 +55,75 @@ func checkPPMShipmentID() ppmShipmentValidator { }) } +// helper function to check if the secondary address is empty, but the tertiary is not +func isPPMShipmentAddressCreateSequenceValid(ppmShipmentToCheck models.PPMShipment) bool { + bothPickupAddressesEmpty := (models.IsAddressEmpty(ppmShipmentToCheck.SecondaryPickupAddress) && models.IsAddressEmpty(ppmShipmentToCheck.TertiaryPickupAddress)) + bothDestinationAddressesEmpty := (models.IsAddressEmpty(ppmShipmentToCheck.SecondaryDestinationAddress) && models.IsAddressEmpty(ppmShipmentToCheck.TertiaryDestinationAddress)) + bothPickupAddressesNotEmpty := !bothPickupAddressesEmpty + bothDestinationAddressesNotEmpty := !bothDestinationAddressesEmpty + hasNoSecondaryHasTertiaryPickup := (models.IsAddressEmpty(ppmShipmentToCheck.SecondaryPickupAddress) && !models.IsAddressEmpty(ppmShipmentToCheck.TertiaryPickupAddress)) + hasNoSecondaryHasTertiaryDestination := (models.IsAddressEmpty(ppmShipmentToCheck.SecondaryDestinationAddress) && !models.IsAddressEmpty(ppmShipmentToCheck.TertiaryDestinationAddress)) + + // need an explicit case to capture when both are empty or not empty + if (bothPickupAddressesEmpty && bothDestinationAddressesEmpty) || (bothPickupAddressesNotEmpty && bothDestinationAddressesNotEmpty) { + return true + } + if hasNoSecondaryHasTertiaryPickup || hasNoSecondaryHasTertiaryDestination { + return false + } + return true +} + +/* Checks if a valid address sequence is being maintained. This will return false if the tertiary address is being updated while the secondary address remains empty +* + */ +func isPPMAddressUpdateSequenceValid(shipmentToUpdateWith *models.PPMShipment, currentShipment *models.PPMShipment) bool { + // if the incoming model has both fields, then we know the model will be updated with both secondary and tertiary addresses. therefore return true + if !models.IsAddressEmpty(shipmentToUpdateWith.SecondaryPickupAddress) && !models.IsAddressEmpty(shipmentToUpdateWith.TertiaryPickupAddress) { + return true + } + if !models.IsAddressEmpty(shipmentToUpdateWith.SecondaryDestinationAddress) && !models.IsAddressEmpty(shipmentToUpdateWith.TertiaryDestinationAddress) { + return true + } + if currentShipment.SecondaryPickupAddress == nil && shipmentToUpdateWith.TertiaryPickupAddress != nil { + return !hasTertiaryWithNoSecondaryAddress(currentShipment.SecondaryPickupAddress, shipmentToUpdateWith.TertiaryPickupAddress) + } + if currentShipment.SecondaryDestinationAddress == nil && shipmentToUpdateWith.TertiaryDestinationAddress != nil { + return !hasTertiaryWithNoSecondaryAddress(currentShipment.SecondaryDestinationAddress, shipmentToUpdateWith.TertiaryDestinationAddress) + } + // no addresses are being updated, so correct address sequence is maintained, return true + return true +} + +// helper function to check if the secondary address is empty, but the tertiary is not +func hasTertiaryWithNoSecondaryAddress(secondaryAddress *models.Address, tertiaryAddress *models.Address) bool { + return (models.IsAddressEmpty(secondaryAddress) && !models.IsAddressEmpty(tertiaryAddress)) +} + +func checkPPMShipmentSequenceValidForCreate() ppmShipmentValidator { + return ppmShipmentValidatorFunc(func(appCtx appcontext.AppContext, newer models.PPMShipment, _ *models.PPMShipment, _ *models.MTOShipment) error { + verrs := validate.NewErrors() + squenceIsValid := isPPMShipmentAddressCreateSequenceValid(newer) + if !squenceIsValid { + verrs.Add("error validating ppm shipment", "Shipment cannot have a third address without a second address present") + return verrs + } + return nil + }) +} + +func checkPPMShipmentSequenceValidForUpdate() ppmShipmentValidator { + return ppmShipmentValidatorFunc(func(appCtx appcontext.AppContext, newer models.PPMShipment, older *models.PPMShipment, _ *models.MTOShipment) error { + verrs := validate.NewErrors() + sequenceIsValid := isPPMAddressUpdateSequenceValid(&newer, older) + if !sequenceIsValid { + verrs.Add("error validating ppm shipment", "Shipment cannot have a third address without a second address present") + return verrs + } + return nil + }) +} + // checkRequiredFields checks that the required fields are included func checkRequiredFields() ppmShipmentValidator { return ppmShipmentValidatorFunc(func(_ appcontext.AppContext, newPPMShipment models.PPMShipment, _ *models.PPMShipment, _ *models.MTOShipment) error { diff --git a/pkg/services/ppmshipment/rules_test.go b/pkg/services/ppmshipment/rules_test.go index 65fc230cf89..6a7134742e1 100644 --- a/pkg/services/ppmshipment/rules_test.go +++ b/pkg/services/ppmshipment/rules_test.go @@ -7,6 +7,7 @@ import ( "github.com/gobuffalo/validate/v3" "github.com/gofrs/uuid" + "github.com/transcom/mymove/pkg/factory" "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/unit" ) @@ -86,6 +87,110 @@ func (suite *PPMShipmentSuite) TestValidationRules() { }) }) + suite.Run("checkPPMShipmentSequenceValidForUpdate add tertiary address without secondary Invalid", func() { + tertiaryDestinationAddress := factory.BuildAddress(suite.DB(), nil, nil) + + ppmShipmentToUpdateWith := models.PPMShipment{ + TertiaryDestinationAddress: &tertiaryDestinationAddress, + } + + olderPPMShipment := factory.BuildPPMShipment(suite.DB(), nil, nil) + + checker := checkPPMShipmentSequenceValidForUpdate() + err := checker.Validate(suite.AppContextForTest(), ppmShipmentToUpdateWith, &olderPPMShipment, nil) + suite.Error(err) + }) + + suite.Run("checkPPMShipmentSequenceValidForUpdate add secondary address Valid", func() { + secondaryPickupAddress := factory.BuildAddress(suite.DB(), nil, nil) + + ppmShipmentToUpdateWith := models.PPMShipment{ + SecondaryPickupAddress: &secondaryPickupAddress, + } + + olderPPMShipment := factory.BuildPPMShipment(suite.DB(), nil, nil) + + checker := checkPPMShipmentSequenceValidForUpdate() + err := checker.Validate(suite.AppContextForTest(), ppmShipmentToUpdateWith, &olderPPMShipment, nil) + suite.NoError(err) + }) + + suite.Run("checkPPMShipmentSequenceValidForUpdate remove secondary address Valid", func() { + secondaryPickupAddress := factory.BuildAddress(suite.DB(), nil, nil) + + ppmShipmentToUpdateWith := models.PPMShipment{ + SecondaryPickupAddress: &secondaryPickupAddress, + } + + olderPPMShipment := factory.BuildPPMShipment(suite.DB(), nil, nil) + + checker := checkPPMShipmentSequenceValidForUpdate() + err := checker.Validate(suite.AppContextForTest(), ppmShipmentToUpdateWith, &olderPPMShipment, nil) + suite.NoError(err) + }) + + suite.Run("checkPPMShipmentSequenceValidForUpdate Valid", func() { + tertiaryPickupAddress := factory.BuildAddress(suite.DB(), nil, nil) + + ppmShipmentToUpdateWith := models.PPMShipment{ + SecondaryPickupAddress: &tertiaryPickupAddress, + } + + olderPPMShipment := factory.BuildPPMShipment(suite.DB(), nil, nil) + + checker := checkPPMShipmentSequenceValidForUpdate() + err := checker.Validate(suite.AppContextForTest(), ppmShipmentToUpdateWith, &olderPPMShipment, nil) + suite.NoError(err) + }) + + suite.Run("checkPPMShipmentSequenceValidForCreate No Secondary Address With Tertiary Invalid", func() { + tertiaryPickupAddress := factory.BuildAddress(suite.DB(), nil, nil) + + newPPMShipment := factory.BuildPPMShipment(suite.DB(), nil, nil) + newPPMShipment.TertiaryPickupAddress = &tertiaryPickupAddress + + checker := checkPPMShipmentSequenceValidForCreate() + err := checker.Validate(suite.AppContextForTest(), newPPMShipment, nil, nil) + suite.Error(err) + }) + + suite.Run("checkPPMShipmentSequenceValidForCreate Primary Address Only Valid", func() { + + newPPMShipment := factory.BuildPPMShipment(suite.DB(), nil, nil) + + checker := checkPPMShipmentSequenceValidForCreate() + err := checker.Validate(suite.AppContextForTest(), newPPMShipment, nil, nil) + suite.NoError(err) + }) + + suite.Run("checkPPMShipmentSequenceValidForCreate with Secondary Address Only Valid", func() { + secondaryPickupAddress := factory.BuildAddress(suite.DB(), nil, nil) + + newPPMShipment := factory.BuildPPMShipment(suite.DB(), nil, nil) + newPPMShipment.SecondaryPickupAddress = &secondaryPickupAddress + + checker := checkPPMShipmentSequenceValidForCreate() + err := checker.Validate(suite.AppContextForTest(), newPPMShipment, nil, nil) + suite.NoError(err) + }) + + suite.Run("checkPPMShipmentSequenceValidForCreate AllFields Valid", func() { + secondaryPickupAddress := factory.BuildAddress(suite.DB(), nil, nil) + secondaryDestinationAddress := factory.BuildAddress(suite.DB(), nil, nil) + TertiaryDestinationAddress := factory.BuildAddress(suite.DB(), nil, nil) + tertiaryPickupAddress := factory.BuildAddress(suite.DB(), nil, nil) + + newPPMShipment := factory.BuildPPMShipment(suite.DB(), nil, nil) + newPPMShipment.SecondaryPickupAddress = &secondaryPickupAddress + newPPMShipment.TertiaryPickupAddress = &tertiaryPickupAddress + newPPMShipment.SecondaryDestinationAddress = &secondaryDestinationAddress + newPPMShipment.TertiaryDestinationAddress = &TertiaryDestinationAddress + + checker := checkPPMShipmentSequenceValidForCreate() + err := checker.Validate(suite.AppContextForTest(), newPPMShipment, nil, nil) + suite.NoError(err) + }) + suite.Run("checkPPMShipmentID", func() { suite.Run("success", func() { id := uuid.Must(uuid.NewV4()) diff --git a/pkg/services/shipment_summary_worksheet/shipment_summary_worksheet.go b/pkg/services/shipment_summary_worksheet/shipment_summary_worksheet.go index 5c9563ae125..6363d60e470 100644 --- a/pkg/services/shipment_summary_worksheet/shipment_summary_worksheet.go +++ b/pkg/services/shipment_summary_worksheet/shipment_summary_worksheet.go @@ -304,7 +304,7 @@ func (s SSWPPMComputer) FormatValuesShipmentSummaryWorksheetFormPage1(data model page1.SITEndDates = formattedSIT.EndDates page1.SITNumberAndTypes = formattedShipment.ShipmentNumberAndTypes - page1.MaxObligationGCC100 = FormatWeights(data.WeightAllotment.Entitlement) + " lbs; " + formattedShipment.EstimatedIncentive + page1.MaxObligationGCC100 = FormatWeights(data.WeightAllotment.Entitlement) + " lbs; " + formattedShipment.MaxIncentive page1.MaxObligationGCCMaxAdvance = formattedShipment.MaxAdvance page1.ActualObligationAdvance = formattedShipment.AdvanceAmountReceived page1.MaxObligationSIT = fmt.Sprintf("%02d Days in SIT", data.MaxSITStorageEntitlement) @@ -708,6 +708,11 @@ func (s SSWPPMComputer) FormatShipment(ppm models.PPMShipment, weightAllotment m formattedShipment.MaxAdvance = "Advance not available." formattedShipment.EstimatedIncentive = "No estimated incentive." } + if ppm.MaxIncentive != nil { + formattedShipment.MaxIncentive = FormatDollarFromCents(*ppm.MaxIncentive) + } else { + formattedShipment.MaxIncentive = "No max incentive." + } formattedShipmentTotalWeights := unit.Pound(0) formattedNumberAndTypes := *ppm.Shipment.ShipmentLocator + " PPM" formattedShipmentWeights := FormatPPMWeightEstimated(ppm) diff --git a/pkg/services/shipment_summary_worksheet/shipment_summary_worksheet_test.go b/pkg/services/shipment_summary_worksheet/shipment_summary_worksheet_test.go index 210aba3d2e8..137846bb46a 100644 --- a/pkg/services/shipment_summary_worksheet/shipment_summary_worksheet_test.go +++ b/pkg/services/shipment_summary_worksheet/shipment_summary_worksheet_test.go @@ -321,6 +321,7 @@ func (suite *ShipmentSummaryWorksheetServiceSuite) TestFormatValuesShipmentSumma cents := unit.Cents(1000) locator := "ABCDEF-01" estIncentive := unit.Cents(1000000) + maxIncentive := unit.Cents(2000000) PPMShipments := models.PPMShipment{ ExpectedDepartureDate: expectedPickupDate, ActualMoveDate: &actualPickupDate, @@ -328,6 +329,7 @@ func (suite *ShipmentSummaryWorksheetServiceSuite) TestFormatValuesShipmentSumma EstimatedWeight: &netWeight, AdvanceAmountRequested: ¢s, EstimatedIncentive: &estIncentive, + MaxIncentive: &maxIncentive, Shipment: models.MTOShipment{ ShipmentLocator: &locator, }, @@ -374,7 +376,7 @@ func (suite *ShipmentSummaryWorksheetServiceSuite) TestFormatValuesShipmentSumma suite.Equal("4,000 lbs - Estimated", sswPage1.ShipmentWeights) suite.Equal("Waiting On Customer", sswPage1.ShipmentCurrentShipmentStatuses) suite.Equal("17,500", sswPage1.TotalWeightAllotmentRepeat) - suite.Equal("15,000 lbs; $10,000.00", sswPage1.MaxObligationGCC100) + suite.Equal("15,000 lbs; $20,000.00", sswPage1.MaxObligationGCC100) suite.True(sswPage1.IsActualExpenseReimbursement) suite.Equal("Actual Expense Reimbursement", sswPage1.GCCIsActualExpenseReimbursement) @@ -1538,6 +1540,7 @@ func (suite *ShipmentSummaryWorksheetServiceSuite) TestFormatShipment() { exampleValue1 := unit.Cents(5000) exampleValue2 := unit.Cents(3000) exampleValue3 := unit.Cents(1000) + maxIncentive := unit.Cents(1000) exampleValue4 := models.PPMAdvanceStatusReceived exampleValue5 := true locator := "ABCDEF-01" @@ -1560,6 +1563,7 @@ func (suite *ShipmentSummaryWorksheetServiceSuite) TestFormatShipment() { shipment: models.PPMShipment{ FinalIncentive: &exampleValue1, // Example value EstimatedIncentive: &exampleValue2, // Example value + MaxIncentive: &maxIncentive, AdvanceAmountReceived: &exampleValue3, // Example value AdvanceStatus: &exampleValue4, HasRequestedAdvance: &exampleValue5, @@ -1569,6 +1573,7 @@ func (suite *ShipmentSummaryWorksheetServiceSuite) TestFormatShipment() { }, expectedResult: models.WorkSheetShipment{ FinalIncentive: "$50.00", // Example expected result + MaxIncentive: "$500.00", // Example expected result MaxAdvance: "$18.00", // Assuming formatMaxAdvance correctly formats EstimatedIncentive: "$30.00", // Example expected result AdvanceAmountReceived: "$10.00 Requested, Received", // Example expected result @@ -1581,6 +1586,7 @@ func (suite *ShipmentSummaryWorksheetServiceSuite) TestFormatShipment() { shipment: models.PPMShipment{ FinalIncentive: nil, EstimatedIncentive: &exampleValue2, // Example value + MaxIncentive: &maxIncentive, AdvanceAmountReceived: &exampleValue3, // Example value AdvanceStatus: &exampleValue4, HasRequestedAdvance: &exampleValue5, @@ -1590,6 +1596,7 @@ func (suite *ShipmentSummaryWorksheetServiceSuite) TestFormatShipment() { }, expectedResult: models.WorkSheetShipment{ FinalIncentive: "No final incentive.", + MaxIncentive: "$500.00", MaxAdvance: "$18.00", // Assuming formatMaxAdvance correctly formats EstimatedIncentive: "$30.00", // Example expected result AdvanceAmountReceived: "$10.00 Requested, Received", // Example expected result diff --git a/pkg/testdatagen/testharness/dispatch.go b/pkg/testdatagen/testharness/dispatch.go index a59ed1d6e11..58f7c202899 100644 --- a/pkg/testdatagen/testharness/dispatch.go +++ b/pkg/testdatagen/testharness/dispatch.go @@ -95,6 +95,9 @@ var actionDispatcher = map[string]actionFunc{ "HHGMoveWithServiceItemsAndPaymentRequestsAndFilesForTOO": func(appCtx appcontext.AppContext) testHarnessResponse { return MakeHHGMoveWithServiceItemsAndPaymentRequestsAndFilesForTOO(appCtx) }, + "HHGMoveWithIntlCratingServiceItemsTOO": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeHHGMoveWithIntlCratingServiceItemsTOO(appCtx) + }, "HHGMoveForTOOAfterActualPickupDate": func(appCtx appcontext.AppContext) testHarnessResponse { return MakeHHGMoveForTOOAfterActualPickupDate(appCtx) }, diff --git a/pkg/testdatagen/testharness/make_move.go b/pkg/testdatagen/testharness/make_move.go index bf90a8ed8ee..fd337bb029f 100644 --- a/pkg/testdatagen/testharness/make_move.go +++ b/pkg/testdatagen/testharness/make_move.go @@ -506,6 +506,229 @@ func MakeHHGMoveWithServiceItemsAndPaymentRequestsAndFilesForTOO(appCtx appconte return *newmove } +// MakeHHGMoveWithIntlCratingServiceItemsTOO is a function +// that creates an HHG move with international service items +// from the Prime for review by the TOO +func MakeHHGMoveWithIntlCratingServiceItemsTOO(appCtx appcontext.AppContext) models.Move { + userUploader := newUserUploader(appCtx) + primeUploader := newPrimeUploader(appCtx) + userInfo := newUserInfo("customer") + + user := factory.BuildUser(appCtx.DB(), []factory.Customization{ + { + Model: models.User{ + OktaEmail: userInfo.email, + Active: true, + }, + }, + }, nil) + customer := factory.BuildExtendedServiceMember(appCtx.DB(), []factory.Customization{ + { + Model: models.ServiceMember{ + PersonalEmail: &userInfo.email, + FirstName: &userInfo.firstName, + LastName: &userInfo.lastName, + CacValidated: true, + }, + }, + { + Model: user, + LinkOnly: true, + }, + }, nil) + dependentsAuthorized := true + entitlements := factory.BuildEntitlement(appCtx.DB(), []factory.Customization{ + { + Model: models.Entitlement{ + DependentsAuthorized: &dependentsAuthorized, + }, + }, + }, nil) + orders := factory.BuildOrder(appCtx.DB(), []factory.Customization{ + { + Model: customer, + LinkOnly: true, + }, + { + Model: entitlements, + LinkOnly: true, + }, + { + Model: models.UserUpload{}, + ExtendedParams: &factory.UserUploadExtendedParams{ + UserUploader: userUploader, + AppContext: appCtx, + }, + }, + }, nil) + mto := factory.BuildMove(appCtx.DB(), []factory.Customization{ + { + Model: orders, + LinkOnly: true, + }, + { + Model: models.Move{ + Status: models.MoveStatusSUBMITTED, + }, + }, + }, nil) + estimatedWeight := unit.Pound(1400) + actualWeight := unit.Pound(2000) + actualPickupDate := time.Now().AddDate(0, 0, 1) + + MTOShipment := factory.BuildMTOShipment(appCtx.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + PrimeEstimatedWeight: &estimatedWeight, + PrimeActualWeight: &actualWeight, + ShipmentType: models.MTOShipmentTypeHHG, + Status: models.MTOShipmentStatusSubmitted, + ActualPickupDate: &actualPickupDate, + }, + }, + { + Model: mto, + LinkOnly: true, + }, + }, nil) + + agentUserInfo := newUserInfo("agent") + factory.BuildMTOAgent(appCtx.DB(), []factory.Customization{ + { + Model: MTOShipment, + LinkOnly: true, + }, + { + Model: models.MTOAgent{ + FirstName: &agentUserInfo.firstName, + LastName: &agentUserInfo.lastName, + Email: &agentUserInfo.email, + MTOAgentType: models.MTOAgentReleasing, + }, + }, + }, nil) + + paymentRequest := factory.BuildPaymentRequest(appCtx.DB(), []factory.Customization{ + { + Model: models.PaymentRequest{ + IsFinal: false, + Status: models.PaymentRequestStatusPending, + }, + }, + { + Model: mto, + LinkOnly: true, + }, + }, nil) + + _ = factory.BuildMTOServiceItem(appCtx.DB(), []factory.Customization{ + { + Model: mto, + LinkOnly: true, + }, + { + Model: MTOShipment, + LinkOnly: true, + }, + { + Model: models.ReService{ + ID: uuid.FromStringOrNil("86203d72-7f7c-49ff-82f0-5b95e4958f60"), // ICRT - Domestic uncrating + }, + }, + }, nil) + + _ = factory.BuildMTOServiceItem(appCtx.DB(), []factory.Customization{ + { + Model: mto, + LinkOnly: true, + }, + { + Model: MTOShipment, + LinkOnly: true, + }, + { + Model: models.ReService{ + ID: uuid.FromStringOrNil("4132416b-b1aa-42e7-98f2-0ac0a03e8a31"), // IUCRT - Domestic uncrating + }, + }, + }, nil) + + _ = factory.BuildMTOServiceItem(appCtx.DB(), []factory.Customization{ + { + Model: mto, + LinkOnly: true, + }, + { + Model: MTOShipment, + LinkOnly: true, + }, + { + Model: models.ReService{ + ID: uuid.FromStringOrNil("86203d72-7f7c-49ff-82f0-5b95e4958f60"), // ICRT - Domestic uncrating + }, + }, + }, nil) + + _ = factory.BuildMTOServiceItem(appCtx.DB(), []factory.Customization{ + { + Model: mto, + LinkOnly: true, + }, + { + Model: MTOShipment, + LinkOnly: true, + }, + { + Model: models.ReService{ + ID: uuid.FromStringOrNil("4132416b-b1aa-42e7-98f2-0ac0a03e8a31"), // IUCRT - Domestic uncrating + }, + }, + }, nil) + + factory.BuildPrimeUpload(appCtx.DB(), []factory.Customization{ + { + Model: paymentRequest, + LinkOnly: true, + }, + }, nil) + posImage := factory.BuildProofOfServiceDoc(appCtx.DB(), []factory.Customization{ + { + Model: paymentRequest, + LinkOnly: true, + }, + }, nil) + primeContractor := uuid.FromStringOrNil("5db13bb4-6d29-4bdb-bc81-262f4513ecf6") + + // Creates custom test.jpg prime upload + file := testdatagen.Fixture("test.jpg") + _, verrs, err := primeUploader.CreatePrimeUploadForDocument(appCtx, &posImage.ID, primeContractor, uploader.File{File: file}, uploader.AllowedTypesPaymentRequest) + if verrs.HasAny() || err != nil { + appCtx.Logger().Error("errors encountered saving test.jpg prime upload", zap.Error(err)) + } + + // Creates custom test.png prime upload + file = testdatagen.Fixture("test.png") + _, verrs, err = primeUploader.CreatePrimeUploadForDocument(appCtx, &posImage.ID, primeContractor, uploader.File{File: file}, uploader.AllowedTypesPaymentRequest) + if verrs.HasAny() || err != nil { + appCtx.Logger().Error("errors encountered saving test.png prime upload", zap.Error(err)) + } + + // re-fetch the move so that we ensure we have exactly what is in + // the db + newmove, err := models.FetchMove(appCtx.DB(), &auth.Session{}, mto.ID) + if err != nil { + log.Panic(fmt.Errorf("failed to fetch move: %w", err)) + } + + // load payment requests so tests can confirm + err = appCtx.DB().Load(newmove, "PaymentRequests") + if err != nil { + log.Panic(fmt.Errorf("failed to fetch move payment requestse: %w", err)) + } + + return *newmove +} + // MakeHHGMoveForTOOAfterActualPickupDate is a function // that creates an HHG move with an actual pickup date in the past for diversion testing // copied almost verbatim from e2ebasic createHHGMoveWithServiceItemsAndPaymentRequestsAndFiles diff --git a/pkg/utils/utility.go b/pkg/utils/utility.go new file mode 100644 index 00000000000..af8ea208632 --- /dev/null +++ b/pkg/utils/utility.go @@ -0,0 +1,10 @@ +package utils + +import ( + "strings" +) + +// checks if string is null, empty, or whitespace +func IsNullOrWhiteSpace(s *string) bool { + return s == nil || len(strings.TrimSpace(*s)) == 0 +} diff --git a/pkg/utils/utility_test.go b/pkg/utils/utility_test.go new file mode 100644 index 00000000000..c7976aa4f5d --- /dev/null +++ b/pkg/utils/utility_test.go @@ -0,0 +1,34 @@ +package utils_test + +import ( + "github.com/transcom/mymove/pkg/testingsuite" + "github.com/transcom/mymove/pkg/utils" +) + +type UtilitySuite struct { + *testingsuite.PopTestSuite +} + +func (suite *UtilitySuite) TestStringIsNilEmptyOrWhitespace() { + suite.Run("nil string", func() { + actual := utils.IsNullOrWhiteSpace(nil) + suite.True(actual) + }) + + suite.Run("empty string", func() { + testString := "" + actual := utils.IsNullOrWhiteSpace(&testString) + suite.True(actual) + }) + + suite.Run("whitespace string", func() { + testString := " " + actual := utils.IsNullOrWhiteSpace(&testString) + suite.True(actual) + }) + suite.Run("valid string", func() { + testString := "hello" + actual := utils.IsNullOrWhiteSpace(&testString) + suite.False(actual) + }) +} diff --git a/playwright/tests/my/milmove/ppms/review.spec.js b/playwright/tests/my/milmove/ppms/review.spec.js index e7efa3730a4..1be8a7d6623 100644 --- a/playwright/tests/my/milmove/ppms/review.spec.js +++ b/playwright/tests/my/milmove/ppms/review.spec.js @@ -13,8 +13,8 @@ const fullPPMShipmentFields = [ ['Expected departure', '15 Mar 2020'], ['Origin ZIP', '90210'], ['Second origin ZIP', '90211'], - ['Destination ZIP', '30813'], - ['Second destination ZIP', '30814'], + ['Delivery Address ZIP', '30813'], + ['Second Delivery Address ZIP', '30814'], ['Closeout office', 'Creech AFB'], ['Storage expected? (SIT)', 'No'], ['Estimated weight', '4,000 lbs'], diff --git a/playwright/tests/my/mymove/hhg.spec.js b/playwright/tests/my/mymove/hhg.spec.js index bbd3811ad3a..e663aa5f0af 100644 --- a/playwright/tests/my/mymove/hhg.spec.js +++ b/playwright/tests/my/mymove/hhg.spec.js @@ -42,7 +42,7 @@ test.describe('HHG', () => { await customerPage.waitForPage.hhgShipment(); // Update form (adding pickup and delivery address) - const pickupAddress = await page.getByRole('group', { name: 'Pickup location' }); + const pickupAddress = await page.getByRole('group', { name: 'Pickup Address' }); await pickupAddress.getByLabel('Address 1').fill('7 Q St'); await pickupAddress.getByLabel('Address 2').clear(); await pickupAddress.getByLabel('City').fill('Atco'); @@ -56,7 +56,7 @@ test.describe('HHG', () => { await pickupAddress.getByLabel('State').nth(1).selectOption({ label: 'NJ' }); await pickupAddress.getByLabel('ZIP').nth(1).fill('08004'); - const deliveryAddress = await page.getByRole('group', { name: 'Delivery location' }); + const deliveryAddress = await page.getByRole('group', { name: 'Delivery Address' }); await deliveryAddress.getByText('Yes').nth(0).click(); await deliveryAddress.getByLabel('Address 1').nth(0).fill('9 W 2nd Ave'); await deliveryAddress.getByLabel('Address 2').nth(0).fill('P.O. Box 456'); @@ -147,7 +147,7 @@ test.describe('(MultiMove) HHG', () => { await customerPage.waitForPage.hhgShipment(); // Update form (adding pickup and delivery address) - const pickupAddress = await page.getByRole('group', { name: 'Pickup location' }); + const pickupAddress = await page.getByRole('group', { name: 'Pickup Address' }); await pickupAddress.getByLabel('Address 1').fill('7 Q St'); await pickupAddress.getByLabel('Address 2').clear(); await pickupAddress.getByLabel('City').fill('Atco'); @@ -161,7 +161,7 @@ test.describe('(MultiMove) HHG', () => { await pickupAddress.getByLabel('State').nth(1).selectOption({ label: 'NJ' }); await pickupAddress.getByLabel('ZIP').nth(1).fill('08004'); - const deliveryAddress = await page.getByRole('group', { name: 'Delivery location' }); + const deliveryAddress = await page.getByRole('group', { name: 'Delivery Address' }); await deliveryAddress.getByText('Yes').nth(0).click(); await deliveryAddress.getByLabel('Address 1').nth(0).fill('9 W 2nd Ave'); await deliveryAddress.getByLabel('Address 2').nth(0).fill('P.O. Box 456'); diff --git a/playwright/tests/office/primesimulator/primeSimulatorFlows.spec.js b/playwright/tests/office/primesimulator/primeSimulatorFlows.spec.js index a6cc84e9f04..d73291d8de4 100644 --- a/playwright/tests/office/primesimulator/primeSimulatorFlows.spec.js +++ b/playwright/tests/office/primesimulator/primeSimulatorFlows.spec.js @@ -101,7 +101,7 @@ test.describe('Prime simulator user', () => { await expect(page.getByText(`Actual Delivery Date:${formatNumericDate(actualDeliveryDate)}`)).toBeVisible(); await expect(page.getByText('Estimated Weight:7500')).toBeVisible(); await expect(page.getByText('Actual Weight:8000')).toBeVisible(); - await expect(page.getByText('Destination Address:142 E Barrel Hoop Circle, Joshua Tree, CA 92252')).toBeVisible(); + await expect(page.getByText('Delivery Address:142 E Barrel Hoop Circle, Joshua Tree, CA 92252')).toBeVisible(); }); test('is able to create payment requests for shipment-level service items', async ({ page, officePage }) => { @@ -160,7 +160,7 @@ test.describe('Prime simulator user', () => { await expect(page.getByText(`Actual Delivery Date:${formatNumericDate(actualDeliveryDate)}`)).toBeVisible(); await expect(page.getByText('Estimated Weight:7500')).toBeVisible(); await expect(page.getByText('Actual Weight:8000')).toBeVisible(); - await expect(page.getByText('Destination Address:142 E Barrel Hoop Circle, Joshua Tree, CA 92252')).toBeVisible(); + await expect(page.getByText('Delivery Address:142 E Barrel Hoop Circle, Joshua Tree, CA 92252')).toBeVisible(); // Can only create a payment request if there is a destination // waits for the create page to load diff --git a/playwright/tests/office/servicescounseling/servicesCounselingBoat.spec.js b/playwright/tests/office/servicescounseling/servicesCounselingBoat.spec.js index fd8f93b31b5..f5eebadb833 100644 --- a/playwright/tests/office/servicescounseling/servicesCounselingBoat.spec.js +++ b/playwright/tests/office/servicescounseling/servicesCounselingBoat.spec.js @@ -24,7 +24,7 @@ test.describe('Services counselor user', () => { await page.locator('#requestedPickupDate').fill(deliveryDate); await page.locator('#requestedPickupDate').blur(); - await page.getByText('Use current address').click(); + await page.getByText('Use pickup address').click(); await page.locator('#requestedDeliveryDate').fill('16 Mar 2022'); await page.locator('#requestedDeliveryDate').blur(); @@ -90,7 +90,7 @@ test.describe('Services counselor user', () => { await page.locator('#requestedPickupDate').fill(deliveryDate); await page.locator('#requestedPickupDate').blur(); - await page.getByText('Use current address').click(); + await page.getByText('Use pickup address').click(); await page.locator('#requestedDeliveryDate').fill('16 Mar 2022'); await page.locator('#requestedDeliveryDate').blur(); diff --git a/playwright/tests/office/servicescounseling/servicesCounselingFlows.spec.js b/playwright/tests/office/servicescounseling/servicesCounselingFlows.spec.js index 8107fbc138f..d40b196d999 100644 --- a/playwright/tests/office/servicescounseling/servicesCounselingFlows.spec.js +++ b/playwright/tests/office/servicescounseling/servicesCounselingFlows.spec.js @@ -74,13 +74,13 @@ test.describe('Services counselor user', () => { await page.locator('#requestedPickupDate').clear(); await page.locator('#requestedPickupDate').fill('16 Mar 2022'); await page.locator('#requestedPickupDate').blur(); - await page.getByText('Use current address').click(); + await page.getByText('Use pickup address').click(); await page.locator('#requestedDeliveryDate').clear(); await page.locator('#requestedDeliveryDate').fill('16 May 2022'); await page.locator('#requestedDeliveryDate').blur(); - await page.getByRole('group', { name: 'Delivery location' }).getByText('Yes').nth(1).click(); + await page.getByRole('group', { name: 'Delivery Address' }).getByText('Yes').nth(1).click(); await page.locator('input[name="delivery.address.streetAddress1"]').clear(); await page.locator('input[name="delivery.address.streetAddress1"]').fill('7 q st'); await page.locator('input[name="delivery.address.city"]').clear(); @@ -89,8 +89,8 @@ test.describe('Services counselor user', () => { await page.locator('input[name="delivery.address.postalCode"]').clear(); await page.locator('input[name="delivery.address.postalCode"]').fill('90210'); - // Select that we do not know the destination address yet - await page.getByRole('group', { name: 'Delivery location' }).getByText('No').nth(1).click(); + // Select that we do not know the delivery address yet + await page.getByRole('group', { name: 'Delivery Address' }).getByText('No').nth(1).click(); await expect(page.getByText('We can use the zip of their new duty location:')).toBeVisible(); await page.locator('[data-testid="submitForm"]').click(); @@ -242,10 +242,10 @@ test.describe('Services counselor user', () => { await page.locator('#requestedPickupDate').fill(deliveryDate); await page.locator('#requestedPickupDate').blur(); - await page.getByText('Use current address').click(); + await page.getByText('Use pickup address').click(); await page.locator('#requestedDeliveryDate').fill('16 Mar 2022'); await page.locator('#requestedDeliveryDate').blur(); - await page.getByRole('group', { name: 'Delivery location' }).getByText('Yes').click(); + await page.getByRole('group', { name: 'Delivery Address' }).getByText('Yes').click(); await page.locator('input[name="delivery.address.streetAddress1"]').fill('7 q st'); await page.locator('input[name="delivery.address.city"]').fill('city'); await page.locator('select[name="delivery.address.state"]').selectOption({ label: 'OH' }); @@ -340,12 +340,12 @@ test.describe('Services counselor user', () => { await page.locator('#requestedPickupDate').clear(); await page.locator('#requestedPickupDate').fill('16 Mar 2022'); await page.locator('#requestedPickupDate').blur(); - await page.getByText('Use current address').click(); + await page.getByText('Use pickup address').click(); await page.locator('#requestedDeliveryDate').clear(); await page.locator('#requestedDeliveryDate').fill('16 May 2022'); await page.locator('#requestedDeliveryDate').blur(); - await page.getByRole('group', { name: 'Delivery location' }).getByText('Yes').nth(1).click(); + await page.getByRole('group', { name: 'Delivery Address' }).getByText('Yes').nth(1).click(); await page.locator('input[name="delivery.address.streetAddress1"]').clear(); await page.locator('input[name="delivery.address.streetAddress1"]').fill('7 q st'); await page.locator('input[name="delivery.address.city"]').clear(); @@ -360,19 +360,19 @@ test.describe('Services counselor user', () => { await expect(page.locator('.usa-alert__text')).toContainText('Your changes were saved.'); }); - test('is able to update destination type if destination address is unknown', async ({ page, scPage }) => { + test('is able to update destination type if delivery address is unknown', async ({ page, scPage }) => { await page.locator('[data-testid="ShipmentContainer"] .usa-button').first().click(); await page.locator('#requestedPickupDate').clear(); await page.locator('#requestedPickupDate').fill('16 Mar 2022'); await page.locator('#requestedPickupDate').blur(); - await page.getByText('Use current address').click(); + await page.getByText('Use pickup address').click(); await page.locator('#requestedDeliveryDate').clear(); await page.locator('#requestedDeliveryDate').fill('16 May 2022'); await page.locator('#requestedDeliveryDate').blur(); - // Select that we do not know the destination address yet - await page.getByRole('group', { name: 'Delivery location' }).getByText('No').nth(1).click(); + // Select that we do not know the delivery address yet + await page.getByRole('group', { name: 'Delivery Address' }).getByText('No').nth(1).click(); await expect(page.locator('select[name="destinationType"]')).toBeVisible(); await expect(page.getByText('We can use the zip of their HOR, HOS or PLEAD:')).toBeVisible(); @@ -487,7 +487,7 @@ test.describe('Services counselor user', () => { await page.getByRole('button', { name: 'Continue' }).click(); }); - test('is able to edit/save destination address', async ({ page, scPage }) => { + test('is able to edit/save delivery address', async ({ page, scPage }) => { // Navigate to the "Review documents" page await expect(page.getByRole('button', { name: /Review documents/i })).toBeVisible(); await page.getByRole('button', { name: 'Review documents' }).click(); @@ -584,7 +584,7 @@ test.describe('Services counselor user', () => { await page.getByTestId('submitForm').click(); await expect(page.getByTestId('payGrade')).toContainText('E-1'); - await expect(page.getByTestId('ShipmentContainer').getByTestId('tag')).toContainText( + await expect(page.getByTestId('ShipmentContainer').getByTestId('actualReimbursementTag')).toContainText( 'actual expense reimbursement', ); diff --git a/playwright/tests/office/servicescounseling/servicesCounselingMobileHome.spec.js b/playwright/tests/office/servicescounseling/servicesCounselingMobileHome.spec.js index be161ca0594..3d673a9ea4b 100644 --- a/playwright/tests/office/servicescounseling/servicesCounselingMobileHome.spec.js +++ b/playwright/tests/office/servicescounseling/servicesCounselingMobileHome.spec.js @@ -100,7 +100,9 @@ test.describe('Services counselor user', () => { await page.locator('#requestedPickupDate').fill(pickupDateString); await page.locator('#requestedPickupDate').blur(); - await page.getByText('Use current address').click(); + await page.getByText('Use pickup address').click(); + await page.locator('#requestedDeliveryDate').fill('16 Mar 2022'); + await page.locator('#requestedDeliveryDate').blur(); await page.getByLabel('Counselor remarks').fill('Sample counselor remarks'); @@ -172,7 +174,7 @@ test.describe('Services counselor user', () => { await page.locator('#requestedDeliveryDate').blur(); // Update form (adding pickup and delivery address) - const pickupAddressGroup = await page.getByRole('group', { name: 'Pickup location' }); + const pickupAddressGroup = await page.getByRole('group', { name: 'Pickup Address' }); await pickupAddressGroup.getByText('Yes').click(); await pickupAddressGroup.getByLabel('Address 1').nth(0).fill(pickupAddress.Address1); await pickupAddressGroup.getByLabel('Address 2').nth(0).clear(); @@ -196,7 +198,7 @@ test.describe('Services counselor user', () => { await page.locator(`[name='pickup.agent.phone']`).fill(releasingAgent.phone); await page.locator(`[name='pickup.agent.email']`).fill(releasingAgent.email); - const deliveryAddressGroup = await page.getByRole('group', { name: 'Delivery location' }); + const deliveryAddressGroup = await page.getByRole('group', { name: 'Delivery Address' }); await deliveryAddressGroup.getByText('Yes').nth(0).click(); await deliveryAddressGroup.getByLabel('Address 1').nth(0).fill(deliveryAddress.Address1); await deliveryAddressGroup.getByLabel('Address 2').nth(0).fill(deliveryAddress.Address2); diff --git a/playwright/tests/office/servicescounseling/servicesCounselingTestFixture.js b/playwright/tests/office/servicescounseling/servicesCounselingTestFixture.js index 78b60200a20..a6807fe2f7a 100644 --- a/playwright/tests/office/servicescounseling/servicesCounselingTestFixture.js +++ b/playwright/tests/office/servicescounseling/servicesCounselingTestFixture.js @@ -133,7 +133,7 @@ export class ServiceCounselorPage extends OfficePage { await this.waitForPage.addNTSShipment(); await this.page.getByLabel('Requested pickup date').fill('16 Mar 2022'); await this.page.getByLabel('Requested pickup date').blur(); - await this.page.getByText('Use current address').click(); + await this.page.getByText('Use pickup address').click(); await this.page.getByLabel('Counselor remarks').fill('Sample counselor remarks'); @@ -169,8 +169,8 @@ export class ServiceCounselorPage extends OfficePage { await this.page.getByLabel('Requested delivery date').fill('20 Mar 2022'); await this.page.getByLabel('Requested delivery date').blur(); - // Delivery location - const deliveryLocation = await this.page.getByRole('group', { name: 'Delivery location' }); + // Delivery Address + const deliveryLocation = await this.page.getByRole('group', { name: 'Delivery Address' }); await deliveryLocation.getByLabel('Address 1').fill('448 Washington Blvd NE'); await deliveryLocation.getByLabel('Address 2').fill('Apt D3'); await deliveryLocation.getByLabel('City').fill('Another City'); diff --git a/playwright/tests/office/txo/tioFlows.spec.js b/playwright/tests/office/txo/tioFlows.spec.js index a21466a0858..53d6bdd03df 100644 --- a/playwright/tests/office/txo/tioFlows.spec.js +++ b/playwright/tests/office/txo/tioFlows.spec.js @@ -204,7 +204,7 @@ test.describe('TIO user', () => { await page.getByTestId('searchTextSubmit').click(); await expect(page.getByText('Results')).toBeVisible(); - await expect(page.getByTestId('dodID-0')).toContainText(testMove.Orders.ServiceMember.edipi); + await expect(page.getByTestId('edipi-0')).toContainText(testMove.Orders.ServiceMember.edipi); }); test('can search for moves using Customer Name', async ({ page }) => { const CustomerName = `${testMove.Orders.ServiceMember.last_name}, ${testMove.Orders.ServiceMember.first_name}`; diff --git a/playwright/tests/office/txo/tooFlows.spec.js b/playwright/tests/office/txo/tooFlows.spec.js index 8ac83166971..99cf0dfa06d 100644 --- a/playwright/tests/office/txo/tooFlows.spec.js +++ b/playwright/tests/office/txo/tooFlows.spec.js @@ -18,6 +18,8 @@ const SearchTerms = ['SITEXT', '8796353598', 'Spacemen']; const StatusFilterOptions = ['Draft', 'New Move', 'Needs Counseling', 'Service counseling completed', 'Move approved']; +const alaskaEnabled = process.env.FEATURE_FLAG_ENABLE_ALASKA; + test.describe('TOO user', () => { /** @type {TooFlowPage} */ let tooFlowPage; @@ -49,7 +51,7 @@ test.describe('TOO user', () => { await page.getByTestId('searchTextSubmit').click(); await expect(page.getByText('Results (1)')).toBeVisible(); - await expect(page.getByTestId('dodID-0')).toContainText(testMove.Orders.ServiceMember.edipi); + await expect(page.getByTestId('edipi-0')).toContainText(testMove.Orders.ServiceMember.edipi); }); test('can search for moves using Customer Name', async ({ page }) => { const CustomerName = `${testMove.Orders.ServiceMember.last_name}, ${testMove.Orders.ServiceMember.first_name}`; @@ -581,6 +583,126 @@ test.describe('TOO user', () => { }); }); + test.describe('with International HHG Moves', () => { + test.skip(alaskaEnabled === 'false', 'Skip if Alaska FF is disabled.'); + test('is able to approve and reject international crating/uncrating service items', async ({ + officePage, + page, + }) => { + const move = await officePage.testHarness.buildHHGMoveWithIntlCratingServiceItemsTOO(); + await officePage.signInAsNewTOOUser(); + tooFlowPage = new TooFlowPage(officePage, move); + await tooFlowPage.waitForLoading(); + await officePage.tooNavigateToMove(tooFlowPage.moveLocator); + + // Edit the shipment address to AK + await page.locator('[data-testid="ShipmentContainer"] .usa-button').first().click(); + await page.locator('select[name="delivery.address.state"]').selectOption({ label: 'AK' }); + await page.locator('[data-testid="submitForm"]').click(); + await expect(page.locator('[data-testid="submitForm"]')).not.toBeEnabled(); + await tooFlowPage.waitForPage.moveDetails(); + + await tooFlowPage.waitForLoading(); + await tooFlowPage.approveAllShipments(); + + await page.getByTestId('MoveTaskOrder-Tab').click(); + await tooFlowPage.waitForLoading(); + expect(page.url()).toContain(`/moves/${tooFlowPage.moveLocator}/mto`); + + // Wait for page to load to deal with flakiness resulting from Service Item tables loading + await tooFlowPage.page.waitForLoadState(); + + // Move Task Order page + await expect(page.getByTestId('ShipmentContainer')).toHaveCount(1); + + /** + * @function + * @description This test approves and rejects service items, which moves them from one table to another + * and expects the counts of each table to increment/decrement by one item each time + * This function gets the service items for a given table to help count them + * @param {import("playwright-core").Locator} table + * @returns {import("playwright-core").Locator} + */ + const getServiceItemsInTable = (table) => { + return table.getByRole('rowgroup').nth(1).getByRole('row'); + }; + + const requestedServiceItemsTable = page.getByTestId('RequestedServiceItemsTable'); + let requestedServiceItemCount = await getServiceItemsInTable(requestedServiceItemsTable).count(); + const approvedServiceItemsTable = page.getByTestId('ApprovedServiceItemsTable'); + let approvedServiceItemCount = await getServiceItemsInTable(approvedServiceItemsTable).count(); + const rejectedServiceItemsTable = page.getByTestId('RejectedServiceItemsTable'); + let rejectedServiceItemCount = await getServiceItemsInTable(rejectedServiceItemsTable).count(); + + await expect(page.getByText('Requested Service Items', { exact: false })).toBeVisible(); + await expect(getServiceItemsInTable(requestedServiceItemsTable).nth(1)).toBeVisible(); + + await expect(page.getByTestId('modal')).not.toBeVisible(); + + // Approve a requested service item + expect((await getServiceItemsInTable(requestedServiceItemsTable).count()) > 0); + // ICRT + await requestedServiceItemsTable.getByRole('button', { name: 'Accept' }).first().click(); + await tooFlowPage.waitForLoading(); + + await expect(getServiceItemsInTable(approvedServiceItemsTable)).toHaveCount(approvedServiceItemCount + 1); + approvedServiceItemCount = await getServiceItemsInTable(approvedServiceItemsTable).count(); + + await expect(getServiceItemsInTable(requestedServiceItemsTable)).toHaveCount(requestedServiceItemCount - 1); + requestedServiceItemCount = await getServiceItemsInTable(requestedServiceItemsTable).count(); + + // IUCRT + await requestedServiceItemsTable.getByRole('button', { name: 'Accept' }).first().click(); + await tooFlowPage.waitForLoading(); + + await expect(getServiceItemsInTable(approvedServiceItemsTable)).toHaveCount(approvedServiceItemCount + 1); + approvedServiceItemCount = await getServiceItemsInTable(approvedServiceItemsTable).count(); + + await expect(getServiceItemsInTable(requestedServiceItemsTable)).toHaveCount(requestedServiceItemCount - 1); + requestedServiceItemCount = await getServiceItemsInTable(requestedServiceItemsTable).count(); + + // Reject a requested service item + await expect(page.getByText('Requested Service Items', { exact: false })).toBeVisible(); + expect((await getServiceItemsInTable(requestedServiceItemsTable).count()) > 0); + // ICRT + await requestedServiceItemsTable.getByRole('button', { name: 'Reject' }).first().click(); + + await expect(page.getByTestId('modal')).toBeVisible(); + let modal = page.getByTestId('modal'); + + await expect(modal.getByRole('button', { name: 'Submit' })).toBeDisabled(); + await modal.getByRole('textbox').fill('my very valid reason'); + await modal.getByRole('button', { name: 'Submit' }).click(); + + await expect(page.getByTestId('modal')).not.toBeVisible(); + + await expect(page.getByText('Rejected Service Items', { exact: false })).toBeVisible(); + await expect(getServiceItemsInTable(rejectedServiceItemsTable)).toHaveCount(rejectedServiceItemCount + 1); + rejectedServiceItemCount = await getServiceItemsInTable(rejectedServiceItemsTable).count(); + + await expect(getServiceItemsInTable(requestedServiceItemsTable)).toHaveCount(requestedServiceItemCount - 1); + requestedServiceItemCount = await getServiceItemsInTable(requestedServiceItemsTable).count(); + + // IUCRT + await requestedServiceItemsTable.getByRole('button', { name: 'Reject' }).first().click(); + + await expect(page.getByTestId('modal')).toBeVisible(); + modal = page.getByTestId('modal'); + + await expect(modal.getByRole('button', { name: 'Submit' })).toBeDisabled(); + await modal.getByRole('textbox').fill('my very valid reason'); + await modal.getByRole('button', { name: 'Submit' }).click(); + + await expect(page.getByTestId('modal')).not.toBeVisible(); + + await expect(page.getByText('Rejected Service Items', { exact: false })).toBeVisible(); + await expect(getServiceItemsInTable(rejectedServiceItemsTable)).toHaveCount(rejectedServiceItemCount + 1); + rejectedServiceItemCount = await getServiceItemsInTable(rejectedServiceItemsTable).count(); + + await expect(getServiceItemsInTable(requestedServiceItemsTable)).toHaveCount(requestedServiceItemCount - 1); + }); + }); + test.describe('with HHG Moves after actual pickup date', () => { test.beforeEach(async ({ officePage }) => { const move = await officePage.testHarness.buildHHGMoveForTOOAfterActualPickupDate(); @@ -724,12 +846,12 @@ test.describe('TOO user', () => { await page.getByRole('button', { name: 'Edit shipment' }).click(); await expect( - page.getByTestId('alert').getByText('Request needs review. See delivery location to proceed.'), + page.getByTestId('alert').getByText('Request needs review. See delivery address to proceed.'), ).toBeVisible(); await expect( page .getByTestId('alert') - .getByText('Pending delivery location change request needs review. Review request to proceed.'), + .getByText('Pending delivery address change request needs review. Review request to proceed.'), ).toBeVisible(); // click to trigger review modal @@ -745,7 +867,7 @@ test.describe('TOO user', () => { await expect(page.getByText('Changes sent to contractor.')).toBeVisible(); - const destinationAddress = page.getByRole('group', { name: 'Delivery location' }); + const destinationAddress = page.getByRole('group', { name: 'Delivery Address' }); await expect(destinationAddress.getByLabel('Address 1')).toHaveValue('123 Any Street'); await expect(destinationAddress.getByLabel('Address 2')).toHaveValue('P.O. Box 12345'); await expect(destinationAddress.getByLabel('City')).toHaveValue('Beverly Hills'); @@ -781,12 +903,12 @@ test.describe('TOO user', () => { await page.getByRole('button', { name: 'Edit shipment' }).click(); await expect( - page.getByTestId('alert').getByText('Request needs review. See delivery location to proceed.'), + page.getByTestId('alert').getByText('Request needs review. See delivery address to proceed.'), ).toBeVisible(); await expect( page .getByTestId('alert') - .getByText('Pending delivery location change request needs review. Review request to proceed.'), + .getByText('Pending delivery address change request needs review. Review request to proceed.'), ).toBeVisible(); await page.getByRole('button', { name: 'Review request' }).click(); @@ -796,14 +918,14 @@ test.describe('TOO user', () => { await expect(page.getByTestId('modal')).not.toBeVisible(); await expect(page.getByText('Changes sent to contractor.')).toBeVisible(); - const destinationAddress = page.getByRole('group', { name: 'Delivery location' }); + const destinationAddress = page.getByRole('group', { name: 'Delivery Address' }); await expect(destinationAddress.getByLabel('Address 1')).toHaveValue('123 Any Street'); await expect(destinationAddress.getByLabel('Address 2')).toHaveValue('P.O. Box 12345'); await expect(destinationAddress.getByLabel('City')).toHaveValue('Beverly Hills'); await expect(destinationAddress.getByLabel('State')).toHaveValue('CA'); await expect(destinationAddress.getByLabel('ZIP')).toHaveValue('90210'); - // Save the approved destination address change + // Save the approved delivery address change await page.getByRole('button', { name: 'Save' }).click(); await expect(page.getByText('Update request details')).not.toBeVisible(); diff --git a/playwright/tests/office/txo/tooFlowsBoat.spec.js b/playwright/tests/office/txo/tooFlowsBoat.spec.js index cccb206ade3..02b78026803 100644 --- a/playwright/tests/office/txo/tooFlowsBoat.spec.js +++ b/playwright/tests/office/txo/tooFlowsBoat.spec.js @@ -38,7 +38,7 @@ test.describe('TOO user', () => { await page.locator('#requestedPickupDate').fill(deliveryDate); await page.locator('#requestedPickupDate').blur(); - await page.getByText('Use current address').click(); + await page.getByText('Use pickup address').click(); await page.locator('#requestedDeliveryDate').fill('16 Mar 2022'); await page.locator('#requestedDeliveryDate').blur(); @@ -103,7 +103,7 @@ test.describe('TOO user', () => { await page.locator('#requestedPickupDate').fill(deliveryDate); await page.locator('#requestedPickupDate').blur(); - await page.getByText('Use current address').click(); + await page.getByText('Use pickup address').click(); await page.locator('#requestedDeliveryDate').fill('19 Mar 2022'); await page.locator('#requestedDeliveryDate').blur(); await page.getByRole('button', { name: 'Save' }).click(); diff --git a/playwright/tests/office/txo/tooFlowsMobileHome.spec.js b/playwright/tests/office/txo/tooFlowsMobileHome.spec.js index a888c64e5e1..a5795880760 100644 --- a/playwright/tests/office/txo/tooFlowsMobileHome.spec.js +++ b/playwright/tests/office/txo/tooFlowsMobileHome.spec.js @@ -110,7 +110,7 @@ test.describe('TOO user', () => { await page.locator('#requestedPickupDate').fill(deliveryDate); await page.locator('#requestedPickupDate').blur(); - await page.getByText('Use current address').click(); + await page.getByText('Use pickup address').click(); await page.locator('#requestedDeliveryDate').fill('16 Mar 2022'); await page.locator('#requestedDeliveryDate').blur(); @@ -177,7 +177,7 @@ test.describe('TOO user', () => { await page.locator('#requestedDeliveryDate').blur(); // Update form (adding pickup and delivery address) - const pickupAddressGroup = await page.getByRole('group', { name: 'Pickup location' }); + const pickupAddressGroup = await page.getByRole('group', { name: 'Pickup Address' }); await pickupAddressGroup.getByText('Yes').click(); await pickupAddressGroup.getByLabel('Address 1').nth(0).fill(pickupAddress.Address1); await pickupAddressGroup.getByLabel('Address 2').nth(0).clear(); @@ -201,7 +201,7 @@ test.describe('TOO user', () => { await page.locator(`[name='pickup.agent.phone']`).fill(releasingAgent.phone); await page.locator(`[name='pickup.agent.email']`).fill(releasingAgent.email); - const deliveryAddressGroup = await page.getByRole('group', { name: 'Delivery location' }); + const deliveryAddressGroup = await page.getByRole('group', { name: 'Delivery Address' }); await deliveryAddressGroup.getByText('Yes').nth(0).click(); await deliveryAddressGroup.getByLabel('Address 1').nth(0).fill(deliveryAddress.Address1); await deliveryAddressGroup.getByLabel('Address 2').nth(0).fill(deliveryAddress.Address2); diff --git a/playwright/tests/office/txo/tooFlowsNTS.spec.js b/playwright/tests/office/txo/tooFlowsNTS.spec.js index 9c478f47847..bd02a540575 100644 --- a/playwright/tests/office/txo/tooFlowsNTS.spec.js +++ b/playwright/tests/office/txo/tooFlowsNTS.spec.js @@ -59,7 +59,7 @@ test.describe('TOO user', () => { // Basic info await page.locator('#requestedPickupDate').clear(); await page.locator('#requestedPickupDate').fill('16 Mar 2022'); - await page.getByText('Use current address').click(); + await page.getByText('Use pickup address').click(); // Storage facility info await page.locator('#facilityName').fill('Sample Facility Name'); @@ -155,7 +155,7 @@ test.describe('TOO user', () => { lastShipment = page.locator('[data-testid="ShipmentContainer"]').last(); // pickup address header await expect(lastShipment.locator('[class*="ShipmentAddresses_mtoShipmentAddresses"]')).toContainText( - 'Pickup address', + 'Pickup Address', ); // facility address header await expect(lastShipment.locator('[class*="ShipmentAddresses_mtoShipmentAddresses"]')).toContainText( @@ -257,7 +257,7 @@ test.describe('TOO user', () => { await expect(lastShipment.locator('h2')).toContainText('Non-temp storage'); // pickup address header await expect(lastShipment.locator('[class*="ShipmentAddresses_mtoShipmentAddresses"]')).toContainText( - 'Pickup address', + 'Pickup Address', ); // facility address header await expect(lastShipment.locator('[class*="ShipmentAddresses_mtoShipmentAddresses"]')).toContainText( diff --git a/playwright/tests/office/txo/tooFlowsNTSR.spec.js b/playwright/tests/office/txo/tooFlowsNTSR.spec.js index 21f2da043be..354b5474645 100644 --- a/playwright/tests/office/txo/tooFlowsNTSR.spec.js +++ b/playwright/tests/office/txo/tooFlowsNTSR.spec.js @@ -171,7 +171,7 @@ test.describe('TOO user', () => { lastShipment = page.locator('[data-testid="ShipmentContainer"]').last(); // delivery address header await expect(lastShipment.locator('[class*="ShipmentAddresses_mtoShipmentAddresses"]')).toContainText( - 'Delivery address', + 'Delivery Address', ); // facility address header await expect(lastShipment.locator('[class*="ShipmentAddresses_mtoShipmentAddresses"]')).toContainText( @@ -237,7 +237,7 @@ test.describe('TOO user', () => { await expect(lastShipment.locator('h2')).toContainText('Non-temp storage'); // delivery address header await expect(lastShipment.locator('[class*="ShipmentAddresses_mtoShipmentAddresses"]')).toContainText( - 'Delivery address', + 'Delivery Address', ); // facility address header await expect(lastShipment.locator('[class*="ShipmentAddresses_mtoShipmentAddresses"]')).toContainText( diff --git a/playwright/tests/utils/my/waitForCustomerPage.js b/playwright/tests/utils/my/waitForCustomerPage.js index 20fa227d9b0..cef69d297ac 100644 --- a/playwright/tests/utils/my/waitForCustomerPage.js +++ b/playwright/tests/utils/my/waitForCustomerPage.js @@ -58,7 +58,7 @@ export class WaitForCustomerPage extends WaitForPage { */ async onboardingCurrentAddress() { await this.runAccessibilityAudit(); - await base.expect(this.page.getByRole('heading', { name: 'Current address' })).toBeVisible(); + await base.expect(this.page.getByRole('heading', { name: 'Current Address' })).toBeVisible(); await this.runAccessibilityAudit(); } diff --git a/playwright/tests/utils/testharness.js b/playwright/tests/utils/testharness.js index 4340f8a3e1e..385224d3571 100644 --- a/playwright/tests/utils/testharness.js +++ b/playwright/tests/utils/testharness.js @@ -186,7 +186,7 @@ export class TestHarness { } /** - * * Use testharness to build a move with an hhg shipment in SIT without destination address + * * Use testharness to build a move with an hhg shipment in SIT without delivery address * @returns {Promise} */ @@ -267,6 +267,14 @@ export class TestHarness { return this.buildDefault('HHGMoveWithServiceItemsAndPaymentRequestsAndFilesForTOO'); } + /** + * Use testharness to build hhg move with international crating service items for TOO + * @returns {Promise} + */ + async buildHHGMoveWithIntlCratingServiceItemsTOO() { + return this.buildDefault('HHGMoveWithIntlCratingServiceItemsTOO'); + } + /** * Use testharness to build hhg move for TOO with actualPickupDate in the past * @returns {Promise} diff --git a/src/components/Customer/BoatShipment/BoatShipmentConfirmationModal/BoatShipmentConfirmationModal.jsx b/src/components/Customer/BoatShipment/BoatShipmentConfirmationModal/BoatShipmentConfirmationModal.jsx index fee1a4533a8..428be14583b 100644 --- a/src/components/Customer/BoatShipment/BoatShipmentConfirmationModal/BoatShipmentConfirmationModal.jsx +++ b/src/components/Customer/BoatShipment/BoatShipmentConfirmationModal/BoatShipmentConfirmationModal.jsx @@ -40,7 +40,7 @@ const boatConfirmationMessage = (isDimensionsMeetReq, boatShipmentType, isEditPa message = (

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

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

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

); break; diff --git a/src/components/Customer/BoatShipment/BoatShipmentForm/BoatShipmentForm.jsx b/src/components/Customer/BoatShipment/BoatShipmentForm/BoatShipmentForm.jsx index 3e5b6697b40..c6a8848c41a 100644 --- a/src/components/Customer/BoatShipment/BoatShipmentForm/BoatShipmentForm.jsx +++ b/src/components/Customer/BoatShipment/BoatShipmentForm/BoatShipmentForm.jsx @@ -337,7 +337,7 @@ const BoatShipmentForm = ({ mtoShipment, onBack, onSubmit }) => { their individual dimensions -
  • Access info for your origin or destination address/marina
  • +
  • Access info for your pickup or delivery address/marina
  • diff --git a/src/components/Customer/EditOrdersForm/EditOrdersForm.jsx b/src/components/Customer/EditOrdersForm/EditOrdersForm.jsx index bcbf1a86e3f..b2bc1bf1a4a 100644 --- a/src/components/Customer/EditOrdersForm/EditOrdersForm.jsx +++ b/src/components/Customer/EditOrdersForm/EditOrdersForm.jsx @@ -211,7 +211,7 @@ const EditOrdersForm = ({ name="new_duty_location" label="HOR, PLEAD or HOS" displayAddress={false} - hint="Enter the option closest to your destination. Your move counselor will identify if there might be a cost to you." + hint="Enter the option closest to your delivery address. Your move counselor will identify if there might be a cost to you." placeholder="Enter a city or ZIP" metaOverride={newDutyMeta} /> diff --git a/src/components/Customer/MtoShipmentForm/MtoShipmentForm.jsx b/src/components/Customer/MtoShipmentForm/MtoShipmentForm.jsx index a8ef7796371..dc62c251404 100644 --- a/src/components/Customer/MtoShipmentForm/MtoShipmentForm.jsx +++ b/src/components/Customer/MtoShipmentForm/MtoShipmentForm.jsx @@ -347,7 +347,7 @@ class MtoShipmentForm extends Component { ( <> @@ -360,7 +360,7 @@ class MtoShipmentForm extends Component { id="useCurrentResidenceCheckbox" /> {fields} -

    Second pickup location

    +

    Second Pickup Address

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

    Third pickup location

    +

    Third Pickup Address

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

    Destination info

    } + {showPickupFields &&

    Delivery Address info

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

    Third pickup location

    +

    Third Pickup Address

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

    Destination

    +

    Delivery Address

    ( <> -

    Please input your destination address.

    +

    Please input your delivery address.

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

    Second delivery location

    +

    Second Delivery Address

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

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

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

    Third delivery location

    +

    Third Delivery Address

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

    Second pickup location

    +

    Second Pickup Address

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

    Second destination address

    +

    Second Delivery Address

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

    @@ -169,7 +169,7 @@ const AboutForm = ({ mtoShipment, onBack, onSubmit }) => { label="Yes" name="hasSecondaryDestinationAddress" value="true" - title="Yes, there is a second destination location" + title="Yes, there is a second delivery address" checked={values.hasSecondaryDestinationAddress === 'true'} /> { label="No" name="hasSecondaryDestinationAddress" value="false" - title="No, there is not a second destination location" + title="No, there is not a second delivery address" checked={values.hasSecondaryDestinationAddress !== 'true'} />
    diff --git a/src/components/Customer/PPM/Closeout/AboutForm/AboutForm.test.jsx b/src/components/Customer/PPM/Closeout/AboutForm/AboutForm.test.jsx index 476a3defdc3..57b35c1906e 100644 --- a/src/components/Customer/PPM/Closeout/AboutForm/AboutForm.test.jsx +++ b/src/components/Customer/PPM/Closeout/AboutForm/AboutForm.test.jsx @@ -241,8 +241,8 @@ describe('AboutForm component', () => { await userEvent.type(input, '123 Street'); await expect(screen.getByRole('button', { name: 'Save & Continue' })).toBeEnabled(); - // 'Optional' labelHint on address display. expecting a total of 9(3 for pickup address, 3 destination address, 3 w2 address). - // This is to verify Required labelHints are displayed correctly for PPM doc uploading for the destination address + // 'Optional' labelHint on address display. expecting a total of 9(3 for pickup address, 3 delivery address, 3 w2 address). + // This is to verify Required labelHints are displayed correctly for PPM doc uploading for the delivery address // street 1 is now OPTIONAL for onboarding but required for PPM doc upload. If this fails it means addtional labelHints // have been introduced elsewhere within the control. const hints = document.getElementsByClassName('usa-hint'); diff --git a/src/components/Customer/Review/ShipmentCard/BoatShipmentCard/BoatShipmentCard.test.jsx b/src/components/Customer/Review/ShipmentCard/BoatShipmentCard/BoatShipmentCard.test.jsx index 19515bfc5a0..6640f518f26 100644 --- a/src/components/Customer/Review/ShipmentCard/BoatShipmentCard/BoatShipmentCard.test.jsx +++ b/src/components/Customer/Review/ShipmentCard/BoatShipmentCard/BoatShipmentCard.test.jsx @@ -78,10 +78,10 @@ describe('BoatShipmentCard component', () => { const expectedRows = [ ['Shipment Method', 'BTA'], ['Requested pickup date', '01 Jan 2020'], - ['Pickup location', '17 8th St New York, NY 11111'], + ['Pickup Address', '17 8th St New York, NY 11111'], ['Releasing agent', 'Jo Xi (555) 555-5555 jo.xi@email.com'], ['Requested delivery date', '01 Mar 2020'], - ['Destination', '17 8th St New York, NY 73523'], + ['Delivery Address', '17 8th St New York, NY 73523'], ['Receiving agent', 'Dorothy Lagomarsino (999) 999-9999 dorothy.lagomarsino@email.com'], ['Boat year', '2020'], ['Boat make', 'Test Make'], diff --git a/src/components/Customer/Review/ShipmentCard/DeliveryDisplay.jsx b/src/components/Customer/Review/ShipmentCard/DeliveryDisplay.jsx index f77bae80c32..c5b909b4a47 100644 --- a/src/components/Customer/Review/ShipmentCard/DeliveryDisplay.jsx +++ b/src/components/Customer/Review/ShipmentCard/DeliveryDisplay.jsx @@ -34,18 +34,18 @@ const DeliveryDisplay = ({
    {formatCustomerDate(requestedDeliveryDate)}
    -
    Destination
    +
    Delivery Address
    {formatCustomerDestination(destinationLocation, destinationZIP)}
    {secondaryDeliveryAddress && (
    -
    Second Destination
    +
    Second Delivery Address
    {formatCustomerDestination(secondaryDeliveryAddress)}
    )} {isTertiaryAddressEnabled && secondaryDeliveryAddress && tertiaryDeliveryAddress && (
    -
    Third Destination
    +
    Third Delivery Address
    {formatCustomerDestination(tertiaryDeliveryAddress)}
    )} diff --git a/src/components/Customer/Review/ShipmentCard/HHGShipmentCard/HHGShipmentCard.test.jsx b/src/components/Customer/Review/ShipmentCard/HHGShipmentCard/HHGShipmentCard.test.jsx index 61ff9fd5636..818af2132f1 100644 --- a/src/components/Customer/Review/ShipmentCard/HHGShipmentCard/HHGShipmentCard.test.jsx +++ b/src/components/Customer/Review/ShipmentCard/HHGShipmentCard/HHGShipmentCard.test.jsx @@ -104,10 +104,10 @@ describe('HHGShipmentCard component', () => { const wrapper = mountHHGShipmentCard(); const tableHeaders = [ 'Requested pickup date', - 'Pickup location', + 'Pickup Address', 'Releasing agent', 'Requested delivery date', - 'Destination', + 'Delivery Address', 'Receiving agent', 'Remarks', ]; @@ -140,7 +140,7 @@ describe('HHGShipmentCard component', () => { it('should render without releasing/receiving agents and remarks', () => { const wrapper = mountHHGShipmentCard({ ...defaultProps, releasingAgent: null, receivingAgent: null, remarks: '' }); - const tableHeaders = ['Requested pickup date', 'Pickup location', 'Requested delivery date', 'Destination']; + const tableHeaders = ['Requested pickup date', 'Pickup Address', 'Requested delivery date', 'Delivery Address']; const { streetAddress1, city, state, postalCode } = defaultProps.pickupLocation; const tableData = [ formatCustomerDate(defaultProps.requestedPickupDate), @@ -153,33 +153,33 @@ describe('HHGShipmentCard component', () => { expect(wrapper.find('.remarksCell').length).toBe(0); }); - it('should not render a secondary pickup location if not provided one', async () => { + it('should not render a secondary Pickup Address if not provided one', async () => { render(); - const secondPickupLocation = await screen.queryByText('Second pickup location'); + const secondPickupLocation = await screen.queryByText('Second Pickup Address'); expect(secondPickupLocation).not.toBeInTheDocument(); }); - it('should not render a secondary destination location if not provided one', async () => { + it('should not render a secondary delivery address if not provided one', async () => { render(); - const secondDestination = await screen.queryByText('Second Destination'); + const secondDestination = await screen.queryByText('Second Delivery Address'); expect(secondDestination).not.toBeInTheDocument(); }); - it('should render a secondary pickup location if provided one', async () => { + it('should render a secondary Pickup Address if provided one', async () => { render(); - const secondPickupLocation = await screen.getByText('Second pickup location'); + const secondPickupLocation = await screen.getByText('Second Pickup Address'); expect(secondPickupLocation).toBeInTheDocument(); const secondPickupLocationInformation = await screen.getByText(/Some Other Street Name/); expect(secondPickupLocationInformation).toBeInTheDocument(); }); - it('should render a secondary destination location if provided one', async () => { + it('should render a secondary delivery address if provided one', async () => { render(); - const secondDestination = await screen.getByText('Second Destination'); + const secondDestination = await screen.getByText('Second Delivery Address'); expect(secondDestination).toBeInTheDocument(); const secondDesintationInformation = await screen.getByText(/Some Street Name/); expect(secondDesintationInformation).toBeInTheDocument(); @@ -298,10 +298,10 @@ describe('HHGShipmentCard component can be reused for UB shipment card', () => { const wrapper = mountHHGShipmentCardForUBShipment(); const tableHeaders = [ 'Requested pickup date', - 'Pickup location', + 'Pickup Address', 'Releasing agent', 'Requested delivery date', - 'Destination', + 'Delivery Address', 'Receiving agent', 'Remarks', ]; @@ -339,7 +339,7 @@ describe('HHGShipmentCard component can be reused for UB shipment card', () => { receivingAgent: null, remarks: '', }); - const tableHeaders = ['Requested pickup date', 'Pickup location', 'Requested delivery date', 'Destination']; + const tableHeaders = ['Requested pickup date', 'Pickup Address', 'Requested delivery date', 'Delivery Address']; const { streetAddress1, city, state, postalCode } = ubProps.pickupLocation; const tableData = [ formatCustomerDate(ubProps.requestedPickupDate), @@ -352,33 +352,33 @@ describe('HHGShipmentCard component can be reused for UB shipment card', () => { expect(wrapper.find('.remarksCell').length).toBe(0); }); - it('should not render a secondary pickup location on UB shipment card if not provided one', async () => { + it('should not render a secondary Pickup Address on UB shipment card if not provided one', async () => { render(); - const secondPickupLocation = await screen.queryByText('Second pickup location'); + const secondPickupLocation = await screen.queryByText('Second Pickup Address'); expect(secondPickupLocation).not.toBeInTheDocument(); }); - it('should not render a secondary destination location on UB shipment card if not provided one', async () => { + it('should not render a secondary delivery address on UB shipment card if not provided one', async () => { render(); - const secondDestination = await screen.queryByText('Second Destination'); + const secondDestination = await screen.queryByText('Second Delivery Address'); expect(secondDestination).not.toBeInTheDocument(); }); - it('should render a UB shipment card secondary pickup location if provided one', async () => { + it('should render a UB shipment card secondary Pickup Address if provided one', async () => { render(); - const secondPickupLocation = await screen.getByText('Second pickup location'); + const secondPickupLocation = await screen.getByText('Second Pickup Address'); expect(secondPickupLocation).toBeInTheDocument(); const secondPickupLocationInformation = await screen.getByText(/Some Other Street Name/); expect(secondPickupLocationInformation).toBeInTheDocument(); }); - it('should render a UB shipment card secondary destination location if provided one', async () => { + it('should render a UB shipment card secondary delivery address if provided one', async () => { render(); - const secondDestination = await screen.getByText('Second Destination'); + const secondDestination = await screen.getByText('Second Delivery Address'); expect(secondDestination).toBeInTheDocument(); const secondDesintationInformation = await screen.getByText(/Some Street Name/); expect(secondDesintationInformation).toBeInTheDocument(); diff --git a/src/components/Customer/Review/ShipmentCard/MobileHomeShipmentCard/MobileHomeShipmentCard.test.jsx b/src/components/Customer/Review/ShipmentCard/MobileHomeShipmentCard/MobileHomeShipmentCard.test.jsx index 1da840afe9a..6c787861d14 100644 --- a/src/components/Customer/Review/ShipmentCard/MobileHomeShipmentCard/MobileHomeShipmentCard.test.jsx +++ b/src/components/Customer/Review/ShipmentCard/MobileHomeShipmentCard/MobileHomeShipmentCard.test.jsx @@ -71,10 +71,10 @@ describe('MobileHomeShipmentCard component', () => { const expectedRows = [ ['Requested pickup date', '01 Jan 2020'], - ['Pickup location', '17 8th St New York, NY 11111'], + ['Pickup Address', '17 8th St New York, NY 11111'], ['Releasing agent', 'Super Mario (555) 555-5555 superMario@gmail.com'], ['Requested delivery date', '01 Mar 2020'], - ['Destination', '17 8th St New York, NY 73523'], + ['Delivery Address', '17 8th St New York, NY 73523'], ['Receiving agent', 'Princess Peach (999) 999-9999 princessPeach@gmail.com'], ['Mobile Home year', '2020'], ['Mobile Home make', 'Test Make'], diff --git a/src/components/Customer/Review/ShipmentCard/NTSRShipmentCard/NTSRShipmentCard.test.jsx b/src/components/Customer/Review/ShipmentCard/NTSRShipmentCard/NTSRShipmentCard.test.jsx index 539ebee276f..4a08a029138 100644 --- a/src/components/Customer/Review/ShipmentCard/NTSRShipmentCard/NTSRShipmentCard.test.jsx +++ b/src/components/Customer/Review/ShipmentCard/NTSRShipmentCard/NTSRShipmentCard.test.jsx @@ -72,7 +72,7 @@ function mountNTSRShipmentCard(props) { describe('NTSRShipmentCard component', () => { it('renders component with all fields', () => { const wrapper = mountNTSRShipmentCard(); - const tableHeaders = ['Requested delivery date', 'Destination', 'Receiving agent', 'Remarks']; + const tableHeaders = ['Requested delivery date', 'Delivery Address', 'Receiving agent', 'Remarks']; const { firstName: receivingFirstName, lastName: receivingLastName, @@ -92,7 +92,7 @@ describe('NTSRShipmentCard component', () => { it('should render without releasing/receiving agents and remarks', () => { const wrapper = mountNTSRShipmentCard({ ...defaultProps, releasingAgent: null, remarks: '' }); - const tableHeaders = ['Requested delivery date', 'Destination']; + const tableHeaders = ['Requested delivery date', 'Delivery Address']; const tableData = [formatCustomerDate(defaultProps.requestedDeliveryDate), defaultProps.destinationZIP]; tableHeaders.forEach((label, index) => expect(wrapper.find('dt').at(index).text()).toBe(label)); tableData.forEach((label, index) => expect(wrapper.find('dd').at(index).text()).toBe(label)); @@ -104,17 +104,17 @@ describe('NTSRShipmentCard component', () => { expect(screen.getByRole('heading', { level: 3 })).toHaveTextContent(`${defaultProps.marketCode}NTS-release`); }); - it('should not render a secondary destination location if not provided one', async () => { + it('should not render a secondary delivery address if not provided one', async () => { render(); - const secondDestination = await screen.queryByText('Second Destination'); + const secondDestination = await screen.queryByText('Second Delivery Address'); expect(secondDestination).not.toBeInTheDocument(); }); - it('should render a secondary destination location if provided one', async () => { + it('should render a secondary delivery address if provided one', async () => { render(); - const secondDestination = await screen.getByText('Second Destination'); + const secondDestination = await screen.getByText('Second Delivery Address'); expect(secondDestination).toBeInTheDocument(); const secondDesintationInformation = await screen.getByText(/Some Street Name/); expect(secondDesintationInformation).toBeInTheDocument(); diff --git a/src/components/Customer/Review/ShipmentCard/NTSShipmentCard/NTSShipmentCard.test.jsx b/src/components/Customer/Review/ShipmentCard/NTSShipmentCard/NTSShipmentCard.test.jsx index 7b790dfff27..92b065177bf 100644 --- a/src/components/Customer/Review/ShipmentCard/NTSShipmentCard/NTSShipmentCard.test.jsx +++ b/src/components/Customer/Review/ShipmentCard/NTSShipmentCard/NTSShipmentCard.test.jsx @@ -78,7 +78,7 @@ const secondaryPickupAddress = { describe('NTSShipmentCard component', () => { it('renders component with all fields', () => { const wrapper = mountNTSShipmentCard(); - const tableHeaders = ['Requested pickup date', 'Pickup location', 'Releasing agent', 'Remarks']; + const tableHeaders = ['Requested pickup date', 'Pickup Address', 'Releasing agent', 'Remarks']; const { streetAddress1, city, state, postalCode } = defaultProps.pickupLocation; const { firstName: releasingFirstName, @@ -104,7 +104,7 @@ describe('NTSShipmentCard component', () => { it('should render without releasing/receiving agents and remarks', () => { const wrapper = mountNTSShipmentCard({ ...defaultProps, releasingAgent: null, remarks: '' }); - const tableHeaders = ['Requested pickup date', 'Pickup location']; + const tableHeaders = ['Requested pickup date', 'Pickup Address']; const { streetAddress1, city, state, postalCode } = defaultProps.pickupLocation; const tableData = [ formatCustomerDate(defaultProps.requestedPickupDate), @@ -115,17 +115,17 @@ describe('NTSShipmentCard component', () => { expect(wrapper.find('.remarksCell').at(0).text()).toBe('—'); }); - it('should not render a secondary pickup location if not provided one', async () => { + it('should not render a secondary Pickup Address if not provided one', async () => { render(); - const secondPickupLocation = await screen.queryByText('Second pickup location'); + const secondPickupLocation = await screen.queryByText('Second Pickup Address'); expect(secondPickupLocation).not.toBeInTheDocument(); }); - it('should render a secondary pickup location if provided one', async () => { + it('should render a secondary Pickup Address if provided one', async () => { render(); - const secondPickupLocation = await screen.getByText('Second pickup location'); + const secondPickupLocation = await screen.getByText('Second Pickup Address'); expect(secondPickupLocation).toBeInTheDocument(); const secondPickupLocationInformation = await screen.getByText(/Some Other Street Name/); expect(secondPickupLocationInformation).toBeInTheDocument(); diff --git a/src/components/Customer/Review/ShipmentCard/PPMShipmentCard/PPMShipmentCard.jsx b/src/components/Customer/Review/ShipmentCard/PPMShipmentCard/PPMShipmentCard.jsx index d4f3ad20b47..3e15ad6e82a 100644 --- a/src/components/Customer/Review/ShipmentCard/PPMShipmentCard/PPMShipmentCard.jsx +++ b/src/components/Customer/Review/ShipmentCard/PPMShipmentCard/PPMShipmentCard.jsx @@ -114,34 +114,34 @@ const PPMShipmentCard = ({
    {formatCustomerDate(expectedDepartureDate)}
    -
    Origin address
    +
    Pickup Address
    {pickupAddress ? formatCustomerContactFullAddress(pickupAddress) : '—'}
    {secondaryPickupAddress && (
    -
    Second origin address
    +
    Second Pickup Address
    {formatCustomerContactFullAddress(secondaryPickupAddress)}
    )} {isTertiaryAddressEnabled && tertiaryPickupAddress && secondaryPickupAddress && (
    -
    Third origin address
    +
    Third Pickup Address
    {formatCustomerContactFullAddress(tertiaryPickupAddress)}
    )}
    -
    Destination address
    +
    Delivery Address
    {destinationAddress ? formatCustomerContactFullAddress(destinationAddress) : '—'}
    {secondaryDestinationAddress && (
    -
    Second destination address
    +
    Second Delivery Address
    {formatCustomerContactFullAddress(secondaryDestinationAddress)}
    )} {isTertiaryAddressEnabled && tertiaryDestinationAddress && secondaryDestinationAddress && (
    -
    Third destination address
    +
    Third Delivery Address
    {formatCustomerContactFullAddress(tertiaryDestinationAddress)}
    )} diff --git a/src/components/Customer/Review/ShipmentCard/PPMShipmentCard/PPMShipmentCard.test.jsx b/src/components/Customer/Review/ShipmentCard/PPMShipmentCard/PPMShipmentCard.test.jsx index 8b5b33e652a..5b491b427f1 100644 --- a/src/components/Customer/Review/ShipmentCard/PPMShipmentCard/PPMShipmentCard.test.jsx +++ b/src/components/Customer/Review/ShipmentCard/PPMShipmentCard/PPMShipmentCard.test.jsx @@ -151,10 +151,10 @@ describe('PPMShipmentCard component', () => { const expectedRows = [ ['Expected departure', '01 Jan 2020'], - ['Origin address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 10001'], - ['Second origin address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 10002'], - ['Destination address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 11111'], - ['Second destination address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 22222'], + ['Pickup Address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 10001'], + ['Second Pickup Address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 10002'], + ['Delivery Address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 11111'], + ['Second Delivery Address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 22222'], ['Storage expected? (SIT)', 'Yes'], ['Estimated weight', '5,999 lbs'], ['Pro-gear', 'Yes, 1,250 lbs'], @@ -193,8 +193,8 @@ describe('PPMShipmentCard component', () => { const expectedRows = [ ['Expected departure', '01 Jan 2020'], - ['Origin address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 10001'], - ['Destination address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 11111'], + ['Pickup Address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 10001'], + ['Delivery Address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 11111'], ['Storage expected? (SIT)', 'No'], ['Estimated weight', '0 lbs'], ['Pro-gear', 'No'], @@ -248,10 +248,10 @@ describe('PPMShipmentCard component', () => { const expectedRows = [ ['Expected departure', '01 Jan 2020'], - ['Origin address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 10001'], - ['Second origin address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 10002'], - ['Destination address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 11111'], - ['Second destination address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 22222'], + ['Pickup Address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 10001'], + ['Second Pickup Address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 10002'], + ['Delivery Address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 11111'], + ['Second Delivery Address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 22222'], ['Closeout office', move.closeoutOffice.name], ['Storage expected? (SIT)', 'Yes'], ['Estimated weight', '5,999 lbs'], @@ -282,10 +282,10 @@ describe('PPMShipmentCard component', () => { const expectedRows = [ ['Expected departure', '01 Jan 2020'], - ['Origin address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 10001'], - ['Second origin address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 10002'], - ['Destination address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 11111'], - ['Second destination address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 22222'], + ['Pickup Address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 10001'], + ['Second Pickup Address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 10002'], + ['Delivery Address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 11111'], + ['Second Delivery Address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 22222'], ['Closeout office', move.closeoutOffice.name], ['Storage expected? (SIT)', 'Yes'], ['Estimated weight', '5,999 lbs'], diff --git a/src/components/Customer/Review/ShipmentCard/PickupDisplay.jsx b/src/components/Customer/Review/ShipmentCard/PickupDisplay.jsx index 717e30798d8..56cfe0246e3 100644 --- a/src/components/Customer/Review/ShipmentCard/PickupDisplay.jsx +++ b/src/components/Customer/Review/ShipmentCard/PickupDisplay.jsx @@ -33,7 +33,7 @@ const PickupDisplay = ({ {pickupLocation && (
    -
    Pickup location
    +
    Pickup Address
    {pickupLocation.streetAddress1} {pickupLocation.streetAddress2}
    @@ -43,7 +43,7 @@ const PickupDisplay = ({ )} {secondaryPickupAddress && (
    -
    Second pickup location
    +
    Second Pickup Address
    {secondaryPickupAddress.streetAddress1} {secondaryPickupAddress.streetAddress2}
    @@ -53,7 +53,7 @@ const PickupDisplay = ({ )} {isTertiaryAddressEnabled && tertiaryPickupAddress && secondaryPickupAddress && (
    -
    Third pickup location
    +
    Third Pickup Address
    {tertiaryPickupAddress.streetAddress1} {tertiaryPickupAddress.streetAddress2}
    diff --git a/src/components/Customer/Review/TableDivider.jsx b/src/components/Customer/Review/TableDivider.jsx index fe1f2fc7b90..c0a255892d3 100644 --- a/src/components/Customer/Review/TableDivider.jsx +++ b/src/components/Customer/Review/TableDivider.jsx @@ -3,9 +3,9 @@ import { string } from 'prop-types'; import styles from './Review.module.scss'; -const TableDivider = ({ className }) => ( +export const TableDivider = ({ className }) => ( - + ); diff --git a/src/components/Customer/Review/TableDivider.test.jsx b/src/components/Customer/Review/TableDivider.test.jsx new file mode 100644 index 00000000000..a590f0642dc --- /dev/null +++ b/src/components/Customer/Review/TableDivider.test.jsx @@ -0,0 +1,12 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; + +import { TableDivider } from 'components/Customer/Review/TableDivider'; + +describe('TableDivider', () => { + it('verify table divider display', async () => { + render(); + + expect(await screen.findByTestId('tableDivider')).toBeInTheDocument(); + }); +}); diff --git a/src/components/Customer/SelectableCard.module.scss b/src/components/Customer/SelectableCard.module.scss index 7f3f19c98ff..c1e179a4a08 100644 --- a/src/components/Customer/SelectableCard.module.scss +++ b/src/components/Customer/SelectableCard.module.scss @@ -3,11 +3,11 @@ @import '../../shared/styles/colors'; @import '../../shared/styles/_variables'; -p+.cardContainer { +p + .cardContainer { margin-top: 0px; } -.cardContainer+.cardContainer { +.cardContainer + .cardContainer { @include u-margin-top('105'); } @@ -90,6 +90,7 @@ p+.cardContainer { box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0); @include u-padding-bottom('205'); padding-left: 50px; + padding-right: 40px; } @media (max-width: $tablet) { diff --git a/src/components/Customer/modals/MoveInfoModal/MoveInfoModal.jsx b/src/components/Customer/modals/MoveInfoModal/MoveInfoModal.jsx index d2de5a777ce..36237caac67 100644 --- a/src/components/Customer/modals/MoveInfoModal/MoveInfoModal.jsx +++ b/src/components/Customer/modals/MoveInfoModal/MoveInfoModal.jsx @@ -4,25 +4,25 @@ import { Button } from '@trussworks/react-uswds'; import Modal, { ModalTitle, ModalClose, ModalActions, connectModal } from 'components/Modal/Modal'; -export const MoveInfoModal = ({ closeModal, enablePPM }) => ( - +export const MoveInfoModal = ({ closeModal, enablePPM, enableUB, hasOconusDutyLocation }) => ( + -

    More info about shipments

    +

    More info about shipments

    HHG: Professional movers pack and ship your things, the government pays

    -

    The moving company works out details with you, but handles everything.

    +

    The moving company works out details with you, but handles everything.

    Pros
    -
      +
      • Everything is packed and moved for you
      • Expert movers care for your things
      • Anything damaged in professional shipments will be replaced
      Cons
      -
        +
        • Can only move on weekdays
        • May have to work around availability of movers
        @@ -31,22 +31,47 @@ export const MoveInfoModal = ({ closeModal, enablePPM }) => (

        PPM: You get your things packed and moved, the government pays you

        -

        You pack and move your own things, or arrange for someone else do it for you.

        +

        You pack and move your own things, or arrange for someone else do it for you.

        Pros
        -
          +
          • Keep your things with you at all times
          • Get paid for the weight you move
          • Flexible dates, routes, timing
          • You can hire movers, equipment, or portable storage
          Cons
          -
            +
            • You pack and move everything
            • You’re responsible if your things get damaged — no compensation
            • The more you own, the more you have to do
            )} + {enableUB && hasOconusDutyLocation && ( + <> +

            + + UB: Professional movers pack and ship your more essential personal property, the government pays + +

            +

            The moving company works out details with you, but handles everything.

            +
            Pros
            +
              +
            • Everything is packed and moved for you
            • +
            • Expert movers care for your things
            • +
            • Anything damaged in professional shipments will be replaced
            • +
            • Essential items are packed as a separate shipment
            • +
            • Shorter allowable transit time than a standard HHG shipment; should arrive sooner at your destination
            • +
            +
            Cons
            +
              +
            • Can only move on weekdays
            • +
            • May have to work around availability of movers
            • +
            • Your UB shipment has its own weight limitation
            • +
            • Only certain kinds of personal property are allowed in a UB shipment (check with your counselor)
            • +
            + + )} + default: ({ id, filename, contentType, url, createdAt, rotation, filePath, onError }) => { + if (filePath === '404') { + onError('content error happening'); + return
            nothing to see here
            ; + } + return ( +
            +
            + {filename} Uploaded on {createdAt} +
            +
            id: {id || 'undefined'}
            +
            fileName: {filename || 'undefined'}
            +
            contentType: {contentType || 'undefined'}
            +
            url: {url || 'undefined'}
            +
            createdAt: {createdAt || 'undefined'}
            +
            rotation: {rotation || 'undefined'}
            +
            +
              + {mockFiles.map((file) => ( +
            • + {file.filename} - Added on {file.createdAt} +
            • + ))} +
            +
            +
            + +
            -
    - ), + ); + }, })); describe('DocumentViewer component', () => { @@ -180,6 +196,46 @@ describe('DocumentViewer component', () => { expect(screen.getByText('id: undefined')).toBeInTheDocument(); }); + describe('regarding content errors', () => { + const errorMessageText = 'If your document does not display, please refresh your browser.'; + const downloadLinkText = 'Download file'; + it('no error message normally', async () => { + render( + + + , + ); + expect(screen.queryByText(errorMessageText)).toBeNull(); + }); + + it('download link normally', async () => { + render( + + + , + ); + expect(screen.getByText(downloadLinkText)).toBeVisible(); + }); + + it('show message on content error', async () => { + render( + + + , + ); + expect(screen.getByText(errorMessageText)).toBeVisible(); + }); + + it('download link on content error', async () => { + render( + + + , + ); + expect(screen.getByText(downloadLinkText)).toBeVisible(); + }); + }); + describe('when clicking download Download All Files button', () => { it('downloads a bulk packet', async () => { const mockResponse = { diff --git a/src/components/Office/AddressUpdatePreview/AddressUpdatePreview.jsx b/src/components/Office/AddressUpdatePreview/AddressUpdatePreview.jsx index f4f567a9c7a..74a2ac1e06b 100644 --- a/src/components/Office/AddressUpdatePreview/AddressUpdatePreview.jsx +++ b/src/components/Office/AddressUpdatePreview/AddressUpdatePreview.jsx @@ -15,10 +15,10 @@ const AddressUpdatePreview = ({ deliveryAddressUpdate }) => { const newSitMileage = deliveryAddressUpdate.newSitDistanceBetween; return (
    -

    Delivery location

    +

    Delivery Address

    - If approved, the requested update to the delivery location will change one or all of the following: + If approved, the requested update to the delivery address will change one or all of the following: Service area. Mileage bracket for direct delivery. @@ -41,7 +41,7 @@ const AddressUpdatePreview = ({ deliveryAddressUpdate }) => { testID="address-change-preview" > } /> diff --git a/src/components/Office/AddressUpdatePreview/AddressUpdatePreview.test.jsx b/src/components/Office/AddressUpdatePreview/AddressUpdatePreview.test.jsx index 96f91c561c1..c8578937e06 100644 --- a/src/components/Office/AddressUpdatePreview/AddressUpdatePreview.test.jsx +++ b/src/components/Office/AddressUpdatePreview/AddressUpdatePreview.test.jsx @@ -70,10 +70,10 @@ describe('AddressUpdatePreview', () => { it('renders all of the address preview information', async () => { render(); // Heading and alert present - expect(screen.getByRole('heading', { name: 'Delivery location' })).toBeInTheDocument(); + expect(screen.getByRole('heading', { name: 'Delivery Address' })).toBeInTheDocument(); expect(screen.getByTestId('alert')).toBeInTheDocument(); expect(screen.getByTestId('alert')).toHaveTextContent( - 'If approved, the requested update to the delivery location will change one or all of the following:' + + 'If approved, the requested update to the delivery address will change one or all of the following:' + 'Service area.' + 'Mileage bracket for direct delivery.' + 'ZIP3 resulting in Domestic Shorthaul (DSH) changing to Domestic Linehaul (DLH) or vice versa.' + @@ -87,11 +87,11 @@ describe('AddressUpdatePreview', () => { const addresses = screen.getAllByTestId('two-line-address'); expect(addresses).toHaveLength(2); // Original Address - expect(addressChangePreview).toHaveTextContent('Original delivery location'); + expect(addressChangePreview).toHaveTextContent('Original Delivery Address'); expect(addresses[0]).toHaveTextContent('987 Any Avenue'); expect(addresses[0]).toHaveTextContent('Fairfield, CA 94535'); // New Address - expect(addressChangePreview).toHaveTextContent('Requested delivery location'); + expect(addressChangePreview).toHaveTextContent('Requested Delivery Address'); expect(addresses[1]).toHaveTextContent('123 Any Street'); expect(addresses[1]).toHaveTextContent('Beverly Hills, CA 90210'); // Request details (contractor remarks) @@ -105,7 +105,7 @@ describe('AddressUpdatePreview', () => { it('renders the destination SIT alert when shipment contains dest SIT service items', () => { render(); // Heading and alert present - expect(screen.getByRole('heading', { name: 'Delivery location' })).toBeInTheDocument(); + expect(screen.getByRole('heading', { name: 'Delivery Address' })).toBeInTheDocument(); expect(screen.getByTestId('destSitAlert')).toBeInTheDocument(); expect(screen.getByTestId('destSitAlert')).toHaveTextContent( 'Approval of this address change request will result in SIT Delivery > 50 Miles.' + diff --git a/src/components/Office/CustomerContactInfoForm/CustomerContactInfoForm.jsx b/src/components/Office/CustomerContactInfoForm/CustomerContactInfoForm.jsx index f54dbcc2e7b..d69e2fe94bd 100644 --- a/src/components/Office/CustomerContactInfoForm/CustomerContactInfoForm.jsx +++ b/src/components/Office/CustomerContactInfoForm/CustomerContactInfoForm.jsx @@ -64,7 +64,7 @@ const CustomerContactInfoForm = ({ initialValues, onSubmit, onBack }) => { )} /> -

    Current Address

    +

    Pickup Address

    Backup Address

    diff --git a/src/components/Office/CustomerContactInfoForm/CustomerContactInfoForm.test.jsx b/src/components/Office/CustomerContactInfoForm/CustomerContactInfoForm.test.jsx index 99566b44b8e..59e6c7d5a12 100644 --- a/src/components/Office/CustomerContactInfoForm/CustomerContactInfoForm.test.jsx +++ b/src/components/Office/CustomerContactInfoForm/CustomerContactInfoForm.test.jsx @@ -79,7 +79,7 @@ describe('CustomerContactInfoForm Component', () => { expect(screen.getAllByLabelText('Email')[0]).toBeInstanceOf(HTMLInputElement); expect(screen.getAllByLabelText('Email')[0]).toBeRequired(); - expect(screen.getByText('Current Address')).toBeInstanceOf(HTMLHeadingElement); + expect(screen.getByText('Pickup Address')).toBeInstanceOf(HTMLHeadingElement); expect(screen.getByDisplayValue('123 Happy St')).toBeInstanceOf(HTMLInputElement); expect(screen.getByDisplayValue('Unit 4')).toBeInstanceOf(HTMLInputElement); expect(screen.getByDisplayValue('Missoula')).toBeInstanceOf(HTMLInputElement); diff --git a/src/components/Office/DefinitionLists/BoatShipmentInfoList.jsx b/src/components/Office/DefinitionLists/BoatShipmentInfoList.jsx index f73083a3009..2ec7baf0443 100644 --- a/src/components/Office/DefinitionLists/BoatShipmentInfoList.jsx +++ b/src/components/Office/DefinitionLists/BoatShipmentInfoList.jsx @@ -200,7 +200,7 @@ const ShipmentInfoList = ({ const pickupAddressElementFlags = getDisplayFlags('pickupAddress'); const pickupAddressElement = (
    -
    Origin address
    +
    Pickup Address
    {(pickupAddress && formatAddress(pickupAddress)) || getMissingOrDash('pickupAddress')}
    @@ -210,7 +210,7 @@ const ShipmentInfoList = ({ const secondaryPickupAddressElementFlags = getDisplayFlags('secondaryPickupAddress'); const secondaryPickupAddressElement = (
    -
    Second pickup address
    +
    Second Pickup Address
    {secondaryPickupAddress ? formatAddress(secondaryPickupAddress) : '—'}
    @@ -220,7 +220,7 @@ const ShipmentInfoList = ({ const tertiaryPickupAddressElementFlags = getDisplayFlags('tertiaryPickupAddress'); const tertiaryPickupAddressElement = (
    -
    Third pickup address
    +
    Third Pickup Address
    {tertiaryPickupAddress ? formatAddress(tertiaryPickupAddress) : '—'}
    ); @@ -236,7 +236,7 @@ const ShipmentInfoList = ({ const destinationAddressElementFlags = getDisplayFlags('destinationAddress'); const destinationAddressElement = (
    -
    Destination address
    +
    Delivery Address
    {deliveryAddressUpdate?.status === ADDRESS_UPDATE_STATUS.REQUESTED ? 'Review required' @@ -248,7 +248,7 @@ const ShipmentInfoList = ({ const secondaryDeliveryAddressElementFlags = getDisplayFlags('secondaryDeliveryAddress'); const secondaryDeliveryAddressElement = (
    -
    Second destination address
    +
    Second Delivery Address
    {secondaryDeliveryAddress ? formatAddress(secondaryDeliveryAddress) : '—'}
    @@ -258,7 +258,7 @@ const ShipmentInfoList = ({ const tertiaryDeliveryAddressElementFlags = getDisplayFlags('tertiaryDeliveryAddress'); const tertiaryDeliveryAddressElement = (
    -
    Third destination address
    +
    Third Delivery Address
    {tertiaryDeliveryAddress ? formatAddress(tertiaryDeliveryAddress) : '—'}
    diff --git a/src/components/Office/DefinitionLists/BoatShipmentInfoList.test.jsx b/src/components/Office/DefinitionLists/BoatShipmentInfoList.test.jsx index c1c3287438e..84dfb949028 100644 --- a/src/components/Office/DefinitionLists/BoatShipmentInfoList.test.jsx +++ b/src/components/Office/DefinitionLists/BoatShipmentInfoList.test.jsx @@ -58,8 +58,8 @@ const shipment = { const labels = { requestedPickupDate: 'Requested pickup date', - pickupAddress: 'Origin address', - destinationAddress: 'Destination address', + pickupAddress: 'Pickup Address', + destinationAddress: 'Delivery Address', mtoAgents: ['Releasing agent', 'Receiving agent'], counselorRemarks: 'Counselor remarks', customerRemarks: 'Customer remarks', diff --git a/src/components/Office/DefinitionLists/CustomerInfoList.jsx b/src/components/Office/DefinitionLists/CustomerInfoList.jsx index 16a698529f4..66a466dd324 100644 --- a/src/components/Office/DefinitionLists/CustomerInfoList.jsx +++ b/src/components/Office/DefinitionLists/CustomerInfoList.jsx @@ -40,7 +40,7 @@ const CustomerInfoList = ({ customerInfo }) => {
    {customerInfo.email}
    -
    Current address
    +
    Pickup Address
    {customerInfo.currentAddress?.streetAddress1 ? formatCustomerContactFullAddress(customerInfo.currentAddress) diff --git a/src/components/Office/DefinitionLists/CustomerInfoList.test.jsx b/src/components/Office/DefinitionLists/CustomerInfoList.test.jsx index d957be52b4f..7222df4d12f 100644 --- a/src/components/Office/DefinitionLists/CustomerInfoList.test.jsx +++ b/src/components/Office/DefinitionLists/CustomerInfoList.test.jsx @@ -45,7 +45,7 @@ describe('CustomerInfoList', () => { }); }); - it('renders formatted current address', () => { + it('renders formatted pickup address', () => { render(); expect(screen.getByText('812 S 129th St, San Antonio, TX 78234')).toBeInTheDocument(); }); diff --git a/src/components/Office/DefinitionLists/MobileHomeShipmentInfoList.jsx b/src/components/Office/DefinitionLists/MobileHomeShipmentInfoList.jsx index b8630805eeb..356c506e9c6 100644 --- a/src/components/Office/DefinitionLists/MobileHomeShipmentInfoList.jsx +++ b/src/components/Office/DefinitionLists/MobileHomeShipmentInfoList.jsx @@ -186,7 +186,7 @@ const ShipmentInfoList = ({ const pickupAddressElementFlags = getDisplayFlags('pickupAddress'); const pickupAddressElement = (
    -
    Origin address
    +
    Pickup Address
    {(pickupAddress && formatAddress(pickupAddress)) || getMissingOrDash('pickupAddress')}
    @@ -196,7 +196,7 @@ const ShipmentInfoList = ({ const secondaryPickupAddressElementFlags = getDisplayFlags('secondaryPickupAddress'); const secondaryPickupAddressElement = (
    -
    Second pickup address
    +
    Second Pickup Address
    {secondaryPickupAddress ? formatAddress(secondaryPickupAddress) : '—'}
    @@ -206,7 +206,7 @@ const ShipmentInfoList = ({ const tertiaryPickupAddressElementFlags = getDisplayFlags('tertiaryPickupAddress'); const tertiaryPickupAddressElement = (
    -
    Third pickup address
    +
    Third Pickup Address
    {tertiaryPickupAddress ? formatAddress(tertiaryPickupAddress) : '—'}
    ); @@ -222,7 +222,7 @@ const ShipmentInfoList = ({ const destinationAddressElementFlags = getDisplayFlags('destinationAddress'); const destinationAddressElement = (
    -
    Destination address
    +
    Delivery Address
    {deliveryAddressUpdate?.status === ADDRESS_UPDATE_STATUS.REQUESTED ? 'Review required' @@ -234,7 +234,7 @@ const ShipmentInfoList = ({ const secondaryDeliveryAddressElementFlags = getDisplayFlags('secondaryDeliveryAddress'); const secondaryDeliveryAddressElement = (
    -
    Second destination address
    +
    Second Delivery Address
    {secondaryDeliveryAddress ? formatAddress(secondaryDeliveryAddress) : '—'}
    @@ -244,7 +244,7 @@ const ShipmentInfoList = ({ const tertiaryDeliveryAddressElementFlags = getDisplayFlags('tertiaryDeliveryAddress'); const tertiaryDeliveryAddressElement = (
    -
    Third destination address
    +
    Third Delivery Address
    {tertiaryDeliveryAddress ? formatAddress(tertiaryDeliveryAddress) : '—'}
    diff --git a/src/components/Office/DefinitionLists/MobileHomeShipmentInfoList.test.jsx b/src/components/Office/DefinitionLists/MobileHomeShipmentInfoList.test.jsx index 3d50715bc35..b7cda59bc81 100644 --- a/src/components/Office/DefinitionLists/MobileHomeShipmentInfoList.test.jsx +++ b/src/components/Office/DefinitionLists/MobileHomeShipmentInfoList.test.jsx @@ -54,8 +54,8 @@ const shipment = { const labels = { requestedPickupDate: 'Requested pickup date', - pickupAddress: 'Origin address', - destinationAddress: 'Destination address', + pickupAddress: 'Pickup Address', + destinationAddress: 'Delivery Address', mtoAgents: ['Releasing agent', 'Receiving agent'], counselorRemarks: 'Counselor remarks', customerRemarks: 'Customer remarks', diff --git a/src/components/Office/DefinitionLists/NTSRShipmentInfoList.jsx b/src/components/Office/DefinitionLists/NTSRShipmentInfoList.jsx index 648ba0a56e7..ffecea1983c 100644 --- a/src/components/Office/DefinitionLists/NTSRShipmentInfoList.jsx +++ b/src/components/Office/DefinitionLists/NTSRShipmentInfoList.jsx @@ -211,7 +211,7 @@ const NTSRShipmentInfoList = ({ const destinationAddressElementFlags = getDisplayFlags('destinationAddress'); const destinationAddressElement = (
    -
    Delivery address
    +
    Delivery Address
    {deliveryAddressUpdate?.status === ADDRESS_UPDATE_STATUS.REQUESTED ? 'Review required' @@ -231,7 +231,7 @@ const NTSRShipmentInfoList = ({ const secondaryDeliveryAddressElementFlags = getDisplayFlags('secondaryDeliveryAddress'); const secondaryDeliveryAddressElement = (
    -
    Second delivery address
    +
    Second Delivery Address
    {secondaryDeliveryAddress ? formatAddress(secondaryDeliveryAddress) : '—'}
    @@ -241,7 +241,7 @@ const NTSRShipmentInfoList = ({ const tertiaryDeliveryAddressElementFlags = getDisplayFlags('tertiaryDeliveryAddress'); const tertiaryDeliveryAddressElement = (
    -
    Third delivery address
    +
    Third Delivery Address
    {tertiaryDeliveryAddress ? formatAddress(tertiaryDeliveryAddress) : '—'}
    diff --git a/src/components/Office/DefinitionLists/NTSRShipmentInfoList.test.jsx b/src/components/Office/DefinitionLists/NTSRShipmentInfoList.test.jsx index c435244fbc5..3e0ad044c21 100644 --- a/src/components/Office/DefinitionLists/NTSRShipmentInfoList.test.jsx +++ b/src/components/Office/DefinitionLists/NTSRShipmentInfoList.test.jsx @@ -224,8 +224,8 @@ describe('NTSR Shipment Info', () => { }); }); - describe('NTSR Shipment Info List Destination Address Request', () => { - it('renders Review required instead of destination address when the Prime has submitted a destination address change', async () => { + describe('NTSR Shipment Info List Delivery Address Request', () => { + it('renders Review required instead of delivery address when the Prime has submitted a delivery address change', async () => { render( -
    Pickup address
    +
    Pickup Address
    {formatAddress(pickupAddress)}
    ); @@ -151,7 +151,7 @@ const NTSShipmentInfoList = ({ const secondaryPickupAddressElementFlags = getDisplayFlags('secondaryPickupAddress'); const secondaryPickupAddressElement = (
    -
    Second pickup address
    +
    Second Pickup Address
    {secondaryPickupAddress ? formatAddress(secondaryPickupAddress) : '—'}
    @@ -161,7 +161,7 @@ const NTSShipmentInfoList = ({ const tertiaryPickupAddressElementFlags = getDisplayFlags('tertiaryPickupAddress'); const tertiaryPickupAddressElement = (
    -
    Third pickup address
    +
    Third Pickup Address
    {tertiaryPickupAddress ? formatAddress(tertiaryPickupAddress) : '—'}
    ); diff --git a/src/components/Office/DefinitionLists/OrdersList.jsx b/src/components/Office/DefinitionLists/OrdersList.jsx index 4b49c27a906..46ec027d40e 100644 --- a/src/components/Office/DefinitionLists/OrdersList.jsx +++ b/src/components/Office/DefinitionLists/OrdersList.jsx @@ -96,6 +96,14 @@ const OrdersList = ({ ordersInfo, showMissingWarnings }) => {
    Orders type detail
    {ordersTypeDetailReadable(ordersInfo.ordersTypeDetail, missingText)}
    +
    +
    Orders document(s)
    +
    {!ordersInfo.ordersDocuments ? missingText : 'File(s) Uploaded'}
    +
    ( ordersNumber: text('ordersInfo.ordersNumber', '999999999'), ordersType: text('ordersInfo.ordersType', ORDERS_TYPE.PERMANENT_CHANGE_OF_STATION), ordersTypeDetail: text('ordersInfo.ordersTypeDetail', 'HHG_PERMITTED'), + ordersDocuments: array('ordersInfo.ordersDocuments', [ + { + 'c0a22a98-a806-47a2-ab54-2dac938667b3': { + bytes: 2202009, + contentType: 'application/pdf', + createdAt: '2024-10-23T16:31:21.085Z', + filename: 'testFile.pdf', + id: 'c0a22a98-a806-47a2-ab54-2dac938667b3', + status: 'PROCESSING', + updatedAt: '2024-10-23T16:31:21.085Z', + uploadType: 'USER', + url: '/storage/USER/uploads/c0a22a98-a806-47a2-ab54-2dac938667b3?contentType=application%2Fpdf', + }, + }, + ]), tacMDC: text('ordersInfo.tacMDC', '9999'), sacSDN: text('ordersInfo.sacSDN', '999 999999 999'), NTSsac: text('ordersInfo.NTSsac', '999 999999 999'), @@ -45,6 +60,21 @@ export const AsServiceCounselor = () => ( ordersNumber: '', ordersType: '', ordersTypeDetail: '', + ordersDocuments: array('ordersInfo.ordersDocuments', [ + { + 'c0a22a98-a806-47a2-ab54-2dac938667b3': { + bytes: 2202009, + contentType: 'application/pdf', + createdAt: '2024-10-23T16:31:21.085Z', + filename: 'testFile.pdf', + id: 'c0a22a98-a806-47a2-ab54-2dac938667b3', + status: 'PROCESSING', + updatedAt: '2024-10-23T16:31:21.085Z', + uploadType: 'USER', + url: '/storage/USER/uploads/c0a22a98-a806-47a2-ab54-2dac938667b3?contentType=application%2Fpdf', + }, + }, + ]), tacMDC: '', sacSDN: '', NTSsac: '', @@ -68,6 +98,7 @@ export const AsServiceCounselorProcessingRetirement = () => ( ordersNumber: '', ordersType: 'RETIREMENT', ordersTypeDetail: '', + ordersDocuments: null, tacMDC: '', sacSDN: '', NTSsac: '', @@ -91,6 +122,7 @@ export const AsServiceCounselorProcessingSeparation = () => ( ordersNumber: '', ordersType: 'SEPARATION', ordersTypeDetail: '', + ordersDocuments: null, tacMDC: '', sacSDN: '', NTSsac: '', @@ -113,6 +145,21 @@ export const AsTOO = () => ( ordersNumber: '', ordersType: '', ordersTypeDetail: '', + ordersDocuments: array('ordersInfo.ordersDocuments', [ + { + 'c0a22a98-a806-47a2-ab54-2dac938667b3': { + bytes: 2202009, + contentType: 'application/pdf', + createdAt: '2024-10-23T16:31:21.085Z', + filename: 'testFile.pdf', + id: 'c0a22a98-a806-47a2-ab54-2dac938667b3', + status: 'PROCESSING', + updatedAt: '2024-10-23T16:31:21.085Z', + uploadType: 'USER', + url: '/storage/USER/uploads/c0a22a98-a806-47a2-ab54-2dac938667b3?contentType=application%2Fpdf', + }, + }, + ]), tacMDC: '', sacSDN: '', NTSsac: '', @@ -135,6 +182,7 @@ export const AsTOOProcessingRetirement = () => ( ordersNumber: '', ordersType: 'RETIREMENT', ordersTypeDetail: '', + ordersDocuments: null, tacMDC: '', sacSDN: '', NTSsac: '', @@ -157,6 +205,7 @@ export const AsTOOProcessingSeparation = () => ( ordersNumber: '', ordersType: 'SEPARATION', ordersTypeDetail: '', + ordersDocuments: null, tacMDC: '', sacSDN: '', NTSsac: '', diff --git a/src/components/Office/DefinitionLists/OrdersList.test.jsx b/src/components/Office/DefinitionLists/OrdersList.test.jsx index cf442cf241e..586c0d1bfab 100644 --- a/src/components/Office/DefinitionLists/OrdersList.test.jsx +++ b/src/components/Office/DefinitionLists/OrdersList.test.jsx @@ -12,6 +12,21 @@ const ordersInfo = { ordersNumber: '999999999', ordersType: 'PERMANENT_CHANGE_OF_STATION', ordersTypeDetail: 'HHG_PERMITTED', + ordersDocuments: [ + { + 'c0a22a98-a806-47a2-ab54-2dac938667b3': { + bytes: 2202009, + contentType: 'application/pdf', + createdAt: '2024-10-23T16:31:21.085Z', + filename: 'testFile.pdf', + id: 'c0a22a98-a806-47a2-ab54-2dac938667b3', + status: 'PROCESSING', + updatedAt: '2024-10-23T16:31:21.085Z', + uploadType: 'USER', + url: '/storage/USER/uploads/c0a22a98-a806-47a2-ab54-2dac938667b3?contentType=application%2Fpdf', + }, + }, + ], tacMDC: '9999', sacSDN: '999 999999 999', payGrade: 'E_7', @@ -27,6 +42,7 @@ const expectedRenderedOrdersInfo = { ordersNumber: '999999999', ordersType: 'Permanent Change Of Station (PCS)', ordersTypeDetail: 'Shipment of HHG Permitted', + ordersDocuments: 'File(s) Uploaded', tacMDC: '9999', sacSDN: '999 999999 999', payGrade: 'E-7', @@ -41,6 +57,7 @@ const ordersInfoMissing = { ordersNumber: '', ordersType: '', ordersTypeDetail: '', + ordersDocuments: null, tacMDC: '', sacSDN: '999 999999 999', payGrade: '', @@ -60,6 +77,7 @@ describe('OrdersList', () => { expect(screen.getByTestId('ordersNumber').textContent).toEqual('Missing'); expect(screen.getByTestId('ordersType').textContent).toEqual('Missing'); expect(screen.getByTestId('ordersTypeDetail').textContent).toEqual('Missing'); + expect(screen.getByTestId('ordersDocuments').textContent).toEqual('Missing'); expect(screen.getByTestId('tacMDC').textContent).toEqual('Missing'); expect(screen.getByTestId('payGrade').textContent).toEqual('Missing'); }); @@ -70,6 +88,7 @@ describe('OrdersList', () => { expect(screen.getByTestId('ordersNumber').textContent).toEqual('—'); expect(screen.getByTestId('ordersType').textContent).toEqual('—'); expect(screen.getByTestId('ordersTypeDetail').textContent).toEqual('—'); + expect(screen.getByTestId('ordersDocuments').textContent).toEqual('—'); expect(screen.getByTestId('tacMDC').textContent).toEqual('—'); expect(screen.getByTestId('payGrade').textContent).toEqual('—'); }); diff --git a/src/components/Office/DefinitionLists/PPMShipmentInfoList.jsx b/src/components/Office/DefinitionLists/PPMShipmentInfoList.jsx index 0d6f3d2036f..cbdea94f4e5 100644 --- a/src/components/Office/DefinitionLists/PPMShipmentInfoList.jsx +++ b/src/components/Office/DefinitionLists/PPMShipmentInfoList.jsx @@ -107,7 +107,7 @@ const PPMShipmentInfoList = ({ const pickupAddressElementFlags = getDisplayFlags('pickupAddress'); const pickupAddressElement = (
    -
    Pickup address
    +
    Pickup Address
    {pickupAddress ? formatAddress(pickupAddress) : '-'}
    ); @@ -115,7 +115,7 @@ const PPMShipmentInfoList = ({ const secondaryPickupAddressElementFlags = getDisplayFlags('secondaryPickupAddress'); const secondaryPickupAddressElement = (
    -
    Second pickup address
    +
    Second Pickup Address
    {secondaryPickupAddress ? formatAddress(secondaryPickupAddress) : '—'}
    @@ -125,7 +125,7 @@ const PPMShipmentInfoList = ({ const tertiaryPickupAddressElementFlags = getDisplayFlags('tertiaryPickupAddress'); const tertiaryPickupAddressElement = (
    -
    Third pickup address
    +
    Third Pickup Address
    {tertiaryPickupAddress ? formatAddress(tertiaryPickupAddress) : '—'}
    ); @@ -133,7 +133,7 @@ const PPMShipmentInfoList = ({ const destinationAddressElementFlags = getDisplayFlags('destinationAddress'); const destinationAddressElement = (
    -
    Destination address
    +
    Delivery Address
    {destinationAddress ? formatAddress(destinationAddress) : '-'}
    ); @@ -141,7 +141,7 @@ const PPMShipmentInfoList = ({ const secondaryDestinationAddressElementFlags = getDisplayFlags('secondaryDestinationAddress'); const secondaryDestinationAddressElement = (
    -
    Second destination address
    +
    Second Delivery Address
    {secondaryDestinationAddress ? formatAddress(secondaryDestinationAddress) : '—'}
    @@ -151,7 +151,7 @@ const PPMShipmentInfoList = ({ const tertiaryDestinationAddressElementFlags = getDisplayFlags('tertiaryDestinationAddress'); const tertiaryDestinationAddressElement = (
    -
    Third destination address
    +
    Third Delivery Address
    {tertiaryDestinationAddress ? formatAddress(tertiaryDestinationAddress) : '—'}
    diff --git a/src/components/Office/DefinitionLists/ShipmentInfoList.jsx b/src/components/Office/DefinitionLists/ShipmentInfoList.jsx index 2dbd3313e5f..834d22bdd57 100644 --- a/src/components/Office/DefinitionLists/ShipmentInfoList.jsx +++ b/src/components/Office/DefinitionLists/ShipmentInfoList.jsx @@ -175,7 +175,7 @@ const ShipmentInfoList = ({ const pickupAddressElementFlags = getDisplayFlags('pickupAddress'); const pickupAddressElement = (
    -
    Origin address
    +
    Pickup Address
    {(pickupAddress && formatAddress(pickupAddress)) || getMissingOrDash('pickupAddress')}
    @@ -185,7 +185,7 @@ const ShipmentInfoList = ({ const secondaryPickupAddressElementFlags = getDisplayFlags('secondaryPickupAddress'); const secondaryPickupAddressElement = (
    -
    Second pickup address
    +
    Second Pickup Address
    {secondaryPickupAddress ? formatAddress(secondaryPickupAddress) : '—'}
    @@ -195,7 +195,7 @@ const ShipmentInfoList = ({ const tertiaryPickupAddressElementFlags = getDisplayFlags('tertiaryPickupAddress'); const tertiaryPickupAddressElement = (
    -
    Third pickup address
    +
    Third Pickup Address
    {tertiaryPickupAddress ? formatAddress(tertiaryPickupAddress) : '—'}
    ); @@ -211,7 +211,7 @@ const ShipmentInfoList = ({ const destinationAddressElementFlags = getDisplayFlags('destinationAddress'); const destinationAddressElement = (
    -
    Destination address
    +
    Delivery Address
    {deliveryAddressUpdate?.status === ADDRESS_UPDATE_STATUS.REQUESTED ? 'Review required' @@ -223,7 +223,7 @@ const ShipmentInfoList = ({ const secondaryDeliveryAddressElementFlags = getDisplayFlags('secondaryDeliveryAddress'); const secondaryDeliveryAddressElement = (
    -
    Second destination address
    +
    Second Delivery Address
    {secondaryDeliveryAddress ? formatAddress(secondaryDeliveryAddress) : '—'}
    @@ -233,7 +233,7 @@ const ShipmentInfoList = ({ const tertiaryDeliveryAddressElementFlags = getDisplayFlags('tertiaryDeliveryAddress'); const tertiaryDeliveryAddressElement = (
    -
    Third destination address
    +
    Third Delivery Address
    {tertiaryDeliveryAddress ? formatAddress(tertiaryDeliveryAddress) : '—'}
    diff --git a/src/components/Office/DefinitionLists/ShipmentInfoList.test.jsx b/src/components/Office/DefinitionLists/ShipmentInfoList.test.jsx index 5e3a47024ae..5f31f32fc9d 100644 --- a/src/components/Office/DefinitionLists/ShipmentInfoList.test.jsx +++ b/src/components/Office/DefinitionLists/ShipmentInfoList.test.jsx @@ -71,12 +71,12 @@ const info = { const labels = { requestedPickupDate: 'Requested pickup date', - pickupAddress: 'Origin address', - secondaryPickupAddress: 'Second pickup address', - tertiaryPickupAddress: 'Third pickup address', - destinationAddress: 'Destination address', - secondaryDeliveryAddress: 'Second destination address', - tertiaryDeliveryAddress: 'Third destination address', + pickupAddress: 'Pickup Address', + secondaryPickupAddress: 'Second Pickup Address', + tertiaryPickupAddress: 'Third Pickup Address', + destinationAddress: 'Delivery Address', + secondaryDeliveryAddress: 'Second Delivery Address', + tertiaryDeliveryAddress: 'Third Delivery Address', mtoAgents: ['Releasing agent', 'Receiving agent'], counselorRemarks: 'Counselor remarks', customerRemarks: 'Customer remarks', @@ -198,7 +198,7 @@ describe('Shipment Info List', () => { expect(within(customerRemarks.parentElement).getByText(info.customerRemarks)).toBeInTheDocument(); }); - it('renders Review required instead of destination address when the Prime has submitted a destination address change', async () => { + it('renders Review required instead of delivery address when the Prime has submitted a delivery address change', async () => { render( { ); const destinationAddress = screen.getByText(labels.destinationAddress); - // The destination address will not render the address field + // The delivery address will not render the address field // when the Prime requests a dest add update expect( within(destinationAddress.parentElement).queryByText(info.destinationAddress.streetAddress1, { diff --git a/src/components/Office/EvaluationReportShipmentDisplay/EvaluationReportShipmentDisplay.jsx b/src/components/Office/EvaluationReportShipmentDisplay/EvaluationReportShipmentDisplay.jsx index e562a3d8521..4192870fb32 100644 --- a/src/components/Office/EvaluationReportShipmentDisplay/EvaluationReportShipmentDisplay.jsx +++ b/src/components/Office/EvaluationReportShipmentDisplay/EvaluationReportShipmentDisplay.jsx @@ -66,7 +66,7 @@ const EvaluationReportShipmentDisplay = ({
    {isExpanded && displayInfo.shipmentType === SHIPMENT_OPTIONS.NTS && (
    -
    Pickup address
    +
    Pickup Address
    {displayInfo?.storageFacility ? displayInfo.storageFacility.facilityName : ''}
    @@ -77,7 +77,7 @@ const EvaluationReportShipmentDisplay = ({
    {displayInfo?.storageFacility ? displayInfo.storageFacility.facilityName : ''}
    -
    Delivery address
    +
    Delivery Address
    )} {isExpanded && ( diff --git a/src/components/Office/PPM/PPMHeaderSummary/EditPPMHeaderSummaryModal.jsx b/src/components/Office/PPM/PPMHeaderSummary/EditPPMHeaderSummaryModal.jsx index 44104a35a8e..1d373d90fba 100644 --- a/src/components/Office/PPM/PPMHeaderSummary/EditPPMHeaderSummaryModal.jsx +++ b/src/components/Office/PPM/PPMHeaderSummary/EditPPMHeaderSummaryModal.jsx @@ -103,7 +103,7 @@ const EditPPMHeaderSummaryModal = ({ sectionType, sectionInfo, onClose, onSubmit {editItemName === 'destinationAddress' && ( diff --git a/src/components/Office/PPM/PPMHeaderSummary/EditPPMHeaderSummaryModal.test.jsx b/src/components/Office/PPM/PPMHeaderSummary/EditPPMHeaderSummaryModal.test.jsx index bc76dbe243c..e2b31172a05 100644 --- a/src/components/Office/PPM/PPMHeaderSummary/EditPPMHeaderSummaryModal.test.jsx +++ b/src/components/Office/PPM/PPMHeaderSummary/EditPPMHeaderSummaryModal.test.jsx @@ -82,7 +82,7 @@ describe('EditPPMHeaderSummaryModal', () => { expect(screen.getByLabelText('Close')).toBeInstanceOf(HTMLButtonElement); }); - it('renders destination address', async () => { + it('renders delivery address', async () => { await act(async () => { render( { }); expect(await screen.findByRole('heading', { level: 3, name: 'Edit Shipment Info' })).toBeInTheDocument(); - expect(screen.getByText('Destination Address')).toBeInTheDocument(); + expect(screen.getByText('Delivery Address')).toBeInTheDocument(); expect(screen.getByRole('button', { name: 'Save' })).toBeInTheDocument(); expect(screen.getByRole('button', { name: 'Cancel' })).toBeInTheDocument(); expect(screen.getByLabelText('Close')).toBeInstanceOf(HTMLButtonElement); diff --git a/src/components/Office/PaymentRequestDetails/PaymentRequestDetails.test.jsx b/src/components/Office/PaymentRequestDetails/PaymentRequestDetails.test.jsx index 367f36684d2..1515e326c94 100644 --- a/src/components/Office/PaymentRequestDetails/PaymentRequestDetails.test.jsx +++ b/src/components/Office/PaymentRequestDetails/PaymentRequestDetails.test.jsx @@ -233,7 +233,7 @@ describe('PaymentRequestDetails', () => { expect(serviceItemStatuses.at(1).text().includes('Rejected')).toBeTruthy(); }); - it('does not render the Departure Date, Pickup Address, and Destination Address', async () => { + it('does not render the Departure Date, Pickup Address, and Delivery Address', async () => { expect(wrapper.find({ 'data-testid': 'pickup-to-destination' }).length).toBe(0); expect(wrapper.find({ 'data-testid': 'departure-date' }).length).toBe(0); }); @@ -253,7 +253,7 @@ describe('PaymentRequestDetails', () => { expect(wrapper.text().includes('Basic service items (1 item)')).toBeTruthy(); }); - it('does not render the Departure Date, Pickup Address, and Destination Address', async () => { + it('does not render the Departure Date, Pickup Address, and Delivery Address', async () => { expect(wrapper.find({ 'data-testid': 'pickup-to-destination' }).length).toBe(0); expect(wrapper.find({ 'data-testid': 'departure-date' }).length).toBe(0); }); @@ -274,7 +274,7 @@ describe('PaymentRequestDetails', () => { expect(wrapper.text().includes('HHG (6 items)')).toBeTruthy(); }); - it('does renders the Departure Date, Pickup Address, and Destination Address', async () => { + it('does renders the Departure Date, Pickup Address, and Delivery Address', async () => { expect(wrapper.find({ 'data-testid': 'pickup-to-destination' })).toBeTruthy(); expect( wrapper.find({ 'data-testid': 'pickup-to-destination' }).at(0).text().includes('Fairfield, CA 94535'), @@ -346,7 +346,7 @@ describe('PaymentRequestDetails', () => { expect(wrapper.text().includes('Non-temp storage release (5 items)')).toBeTruthy(); }); - it('does renders the Departure Date, Pickup Address, and Destination Address', async () => { + it('does renders the Departure Date, Pickup Address, and Delivery Address', async () => { expect(wrapper.find({ 'data-testid': 'pickup-to-destination' })).toBeTruthy(); expect( wrapper.find({ 'data-testid': 'pickup-to-destination' }).at(0).text().includes('Princeton, NJ 08540'), diff --git a/src/components/Office/RequestedServiceItemsTable/RequestedServiceItemsTable.test.jsx b/src/components/Office/RequestedServiceItemsTable/RequestedServiceItemsTable.test.jsx index e5ff405aa68..b18f4ef24ac 100644 --- a/src/components/Office/RequestedServiceItemsTable/RequestedServiceItemsTable.test.jsx +++ b/src/components/Office/RequestedServiceItemsTable/RequestedServiceItemsTable.test.jsx @@ -70,13 +70,13 @@ const testDetails = (wrapper) => { expect(detailTypes.at(1).text()).toBe('Item size:'); expect(detailDefinitions.at(1).text()).toBe('7"x2"x3.5"'); - expect(detailTypes.at(3).text()).toBe('Original pickup address:'); + expect(detailTypes.at(3).text()).toBe('Original Pickup Address:'); expect(detailDefinitions.at(3).text().includes('-')).toBe(true); - expect(detailTypes.at(4).text()).toBe('Actual pickup address:'); + expect(detailTypes.at(4).text()).toBe('Actual Pickup Address:'); expect(detailDefinitions.at(4).text().includes('-')).toBe(true); expect(detailTypes.at(5).text()).toBe('Delivery miles into SIT:'); expect(detailDefinitions.at(5).text().includes('-')).toBe(true); - expect(detailTypes.at(6).text()).toBe('Original delivery address:'); + expect(detailTypes.at(6).text()).toBe('Original Delivery Address:'); expect(detailDefinitions.at(6).text().includes('-')).toBe(true); expect(detailTypes.at(7).text()).toBe('SIT entry date:'); expect(detailDefinitions.at(7).text().includes('-')).toBe(true); diff --git a/src/components/Office/RequestedShipments/ApprovedRequestedShipments.jsx b/src/components/Office/RequestedShipments/ApprovedRequestedShipments.jsx index 2bb1a12d7f7..4f271725818 100644 --- a/src/components/Office/RequestedShipments/ApprovedRequestedShipments.jsx +++ b/src/components/Office/RequestedShipments/ApprovedRequestedShipments.jsx @@ -21,7 +21,7 @@ import { permissionTypes } from 'constants/permissions'; import { isBooleanFlagEnabled } from 'utils/featureFlags'; // nts defaults show preferred pickup date and pickup address, flagged items when collapsed -// ntsr defaults shows preferred delivery date, storage facility address, destination address, flagged items when collapsed +// ntsr defaults shows preferred delivery date, storage facility address, delivery address, flagged items when collapsed // Different things show when collapsed depending on if the shipment is an external vendor or not. const showWhenCollapsedWithExternalVendor = { HHG_INTO_NTS_DOMESTIC: ['serviceOrderNumber', 'requestedDeliveryDate'], diff --git a/src/components/Office/RequestedShipments/RequestedShipments.test.jsx b/src/components/Office/RequestedShipments/RequestedShipments.test.jsx index e27ed46f3d0..581de21b50c 100644 --- a/src/components/Office/RequestedShipments/RequestedShipments.test.jsx +++ b/src/components/Office/RequestedShipments/RequestedShipments.test.jsx @@ -211,7 +211,7 @@ describe('RequestedShipments', () => { expect(screen.getAllByTestId('checkbox').length).toEqual(5); }); - it('uses the duty location postal code if there is no destination address', () => { + it('uses the duty location postal code if there is no delivery address', () => { render(submittedRequestedShipmentsComponent); const destination = shipments[0].destinationAddress; expect(screen.getAllByTestId('destinationAddress').at(0)).toHaveTextContent( diff --git a/src/components/Office/RequestedShipments/RequestedShipmentsTestData.js b/src/components/Office/RequestedShipments/RequestedShipmentsTestData.js index ec2b24766db..b7cec16b39f 100644 --- a/src/components/Office/RequestedShipments/RequestedShipmentsTestData.js +++ b/src/components/Office/RequestedShipments/RequestedShipmentsTestData.js @@ -335,6 +335,21 @@ export const ordersInfo = { ordersNumber: 'ORDER3', ordersType: ORDERS_TYPE.PERMANENT_CHANGE_OF_STATION, ordersTypeDetail: 'TBD', + ordersDocuments: [ + { + 'c0a22a98-a806-47a2-ab54-2dac938667b3': { + bytes: 2202009, + contentType: 'application/pdf', + createdAt: '2024-10-23T16:31:21.085Z', + filename: 'testFile.pdf', + id: 'c0a22a98-a806-47a2-ab54-2dac938667b3', + status: 'PROCESSING', + updatedAt: '2024-10-23T16:31:21.085Z', + uploadType: 'USER', + url: '/storage/USER/uploads/c0a22a98-a806-47a2-ab54-2dac938667b3?contentType=application%2Fpdf', + }, + }, + ], tacMDC: '', sacSDN: '', }; diff --git a/src/components/Office/RequestedShipments/SubmittedRequestedShipments.jsx b/src/components/Office/RequestedShipments/SubmittedRequestedShipments.jsx index 95a98e21427..4ff6f90dc19 100644 --- a/src/components/Office/RequestedShipments/SubmittedRequestedShipments.jsx +++ b/src/components/Office/RequestedShipments/SubmittedRequestedShipments.jsx @@ -28,7 +28,7 @@ import { setFlashMessage as setFlashMessageAction } from 'store/flash/actions'; import { isBooleanFlagEnabled } from 'utils/featureFlags'; // nts defaults show preferred pickup date and pickup address, flagged items when collapsed -// ntsr defaults shows preferred delivery date, storage facility address, destination address, flagged items when collapsed +// ntsr defaults shows preferred delivery date, storage facility address, delivery address, flagged items when collapsed // Different things show when collapsed depending on if the shipment is an external vendor or not. const showWhenCollapsedWithExternalVendor = { HHG_INTO_NTS_DOMESTIC: ['serviceOrderNumber', 'requestedDeliveryDate'], diff --git a/src/components/Office/ServiceItemDetails/ServiceItemDetails.jsx b/src/components/Office/ServiceItemDetails/ServiceItemDetails.jsx index 5e94658f335..c6be9ec77af 100644 --- a/src/components/Office/ServiceItemDetails/ServiceItemDetails.jsx +++ b/src/components/Office/ServiceItemDetails/ServiceItemDetails.jsx @@ -52,7 +52,7 @@ const generateDestinationSITDetailSection = (id, serviceRequestDocUploads, detai
    {code === 'DDFSIT' ? generateDetailText({ - 'Original delivery address': originalDeliveryAddress + 'Original Delivery Address': originalDeliveryAddress ? formatCityStateAndPostalCode(originalDeliveryAddress) : '-', 'SIT entry date': details.sitEntryDate ? formatDateWithUTC(details.sitEntryDate, 'DD MMM YYYY') : '-', @@ -62,7 +62,7 @@ const generateDestinationSITDetailSection = (id, serviceRequestDocUploads, detai <> {generateDetailText( { - 'Original delivery address': originalDeliveryAddress + 'Original Delivery Address': originalDeliveryAddress ? formatCityStateAndPostalCode(originalDeliveryAddress) : '-', "Add'l SIT Start Date": details.sitEntryDate @@ -90,10 +90,10 @@ const generateDestinationSITDetailSection = (id, serviceRequestDocUploads, detai {code === 'DDSFSC' ? generateDetailText( { - 'Original delivery address': originalDeliveryAddress + 'Original Delivery Address': originalDeliveryAddress ? formatCityStateAndPostalCode(originalDeliveryAddress) : '-', - 'Final delivery address': + 'Final Delivery Address': details.sitDestinationFinalAddress && details.status !== 'SUBMITTED' ? formatCityStateAndPostalCode(details.sitDestinationFinalAddress) : '-', @@ -106,10 +106,10 @@ const generateDestinationSITDetailSection = (id, serviceRequestDocUploads, detai <> {generateDetailText( { - 'Original delivery address': originalDeliveryAddress + 'Original Delivery Address': originalDeliveryAddress ? formatCityStateAndPostalCode(originalDeliveryAddress) : '-', - 'Final delivery address': + 'Final Delivery Address': details.sitDestinationFinalAddress && details.status !== 'SUBMITTED' ? formatCityStateAndPostalCode(details.sitDestinationFinalAddress) : '-', @@ -194,7 +194,7 @@ const ServiceItemDetails = ({ id, code, details, serviceRequestDocs, shipment, s
    {generateDetailText( { - 'Original pickup address': details.sitOriginHHGOriginalAddress + 'Original Pickup Address': details.sitOriginHHGOriginalAddress ? formatCityStateAndPostalCode(details.sitOriginHHGOriginalAddress) : '-', 'SIT entry date': details.sitEntryDate ? formatDateWithUTC(details.sitEntryDate, 'DD MMM YYYY') : '-', @@ -233,7 +233,7 @@ const ServiceItemDetails = ({ id, code, details, serviceRequestDocs, shipment, s
    {generateDetailText( { - 'Original pickup address': details.sitOriginHHGOriginalAddress + 'Original Pickup Address': details.sitOriginHHGOriginalAddress ? formatCityStateAndPostalCode(details.sitOriginHHGOriginalAddress) : '-', "Add'l SIT Start Date": details.sitEntryDate @@ -278,10 +278,10 @@ const ServiceItemDetails = ({ id, code, details, serviceRequestDocs, shipment, s
    {generateDetailText( { - 'Original pickup address': details.sitOriginHHGOriginalAddress + 'Original Pickup Address': details.sitOriginHHGOriginalAddress ? formatCityStateAndPostalCode(details.sitOriginHHGOriginalAddress) : '-', - 'Actual pickup address': details.sitOriginHHGActualAddress + 'Actual Pickup Address': details.sitOriginHHGActualAddress ? formatCityStateAndPostalCode(details.sitOriginHHGActualAddress) : '-', 'Delivery miles into SIT': details.sitDeliveryMiles ? details.sitDeliveryMiles : '-', @@ -313,10 +313,10 @@ const ServiceItemDetails = ({ id, code, details, serviceRequestDocs, shipment, s
    {generateDetailText( { - 'Original pickup address': details.sitOriginHHGOriginalAddress + 'Original Pickup Address': details.sitOriginHHGOriginalAddress ? formatCityStateAndPostalCode(details.sitOriginHHGOriginalAddress) : '-', - 'Actual pickup address': details.sitOriginHHGActualAddress + 'Actual Pickup Address': details.sitOriginHHGActualAddress ? formatCityStateAndPostalCode(details.sitOriginHHGActualAddress) : '-', 'Delivery miles into SIT': details.sitDeliveryMiles ? details.sitDeliveryMiles : '-', @@ -509,6 +509,85 @@ const ServiceItemDetails = ({ id, code, details, serviceRequestDocs, shipment, s ); break; } + case 'ICRT': { + const { description, itemDimensions, crateDimensions, market, externalCrate } = details; + const itemDimensionFormat = `${convertFromThousandthInchToInch( + itemDimensions?.length, + )}"x${convertFromThousandthInchToInch(itemDimensions?.width)}"x${convertFromThousandthInchToInch( + itemDimensions?.height, + )}"`; + const crateDimensionFormat = `${convertFromThousandthInchToInch( + crateDimensions?.length, + )}"x${convertFromThousandthInchToInch(crateDimensions?.width)}"x${convertFromThousandthInchToInch( + crateDimensions?.height, + )}"`; + detailSection = ( +
    +
    + {description && generateDetailText({ Description: description }, id)} + {itemDimensions && generateDetailText({ 'Item size': itemDimensionFormat }, id)} + {crateDimensions && generateDetailText({ 'Crate size': crateDimensionFormat }, id)} + {externalCrate && generateDetailText({ 'External crate': 'Yes' }, id)} + {market && generateDetailText({ Market: market }, id)} + {generateDetailText({ Reason: details.reason ? details.reason : '-' })} + {details.rejectionReason && + generateDetailText({ 'Rejection reason': details.rejectionReason }, id, 'margin-top-2')} + {!isEmpty(serviceRequestDocUploads) ? ( +
    +

    Download service item documentation:

    + {serviceRequestDocUploads.map((file) => ( + + ))} +
    + ) : null} +
    +
    + ); + break; + } + case 'IUCRT': { + const { description, itemDimensions, crateDimensions, market } = details; + const itemDimensionFormat = `${convertFromThousandthInchToInch( + itemDimensions?.length, + )}"x${convertFromThousandthInchToInch(itemDimensions?.width)}"x${convertFromThousandthInchToInch( + itemDimensions?.height, + )}"`; + const crateDimensionFormat = `${convertFromThousandthInchToInch( + crateDimensions?.length, + )}"x${convertFromThousandthInchToInch(crateDimensions?.width)}"x${convertFromThousandthInchToInch( + crateDimensions?.height, + )}"`; + detailSection = ( +
    +
    + {description && generateDetailText({ Description: description }, id)} + {itemDimensions && generateDetailText({ 'Item size': itemDimensionFormat }, id)} + {crateDimensions && generateDetailText({ 'Crate size': crateDimensionFormat }, id)} + {market && generateDetailText({ Market: market }, id)} + {generateDetailText({ Reason: details.reason ? details.reason : '-' })} + {details.rejectionReason && + generateDetailText({ 'Rejection reason': details.rejectionReason }, id, 'margin-top-2')} + {!isEmpty(serviceRequestDocUploads) ? ( +
    +

    Download service item documentation:

    + {serviceRequestDocUploads.map((file) => ( + + ))} +
    + ) : null} +
    +
    + ); + break; + } default: detailSection = (
    diff --git a/src/components/Office/ServiceItemDetails/ServiceItemDetails.test.jsx b/src/components/Office/ServiceItemDetails/ServiceItemDetails.test.jsx index f4ecd4bfccd..d9267d7e572 100644 --- a/src/components/Office/ServiceItemDetails/ServiceItemDetails.test.jsx +++ b/src/components/Office/ServiceItemDetails/ServiceItemDetails.test.jsx @@ -145,7 +145,7 @@ describe('ServiceItemDetails Domestic Destination SIT', () => { serviceRequestDocs={serviceRequestDocs} />, ); - expect(screen.getByText('Original delivery address:')).toBeInTheDocument(); + expect(screen.getByText('Original Delivery Address:')).toBeInTheDocument(); expect(screen.getByText('Destination Original Tampa, FL 33621')).toBeInTheDocument(); expect(screen.getByText("Add'l SIT Start Date:")).toBeInTheDocument(); @@ -172,10 +172,10 @@ describe('ServiceItemDetails Domestic Destination SIT', () => { it('renders DDDSIT details', () => { render(); - expect(screen.getByText('Original delivery address:')).toBeInTheDocument(); + expect(screen.getByText('Original Delivery Address:')).toBeInTheDocument(); expect(screen.getByText('Destination Original Tampa, FL 33621')).toBeInTheDocument(); - expect(screen.getByText('Final delivery address:')).toBeInTheDocument(); + expect(screen.getByText('Final Delivery Address:')).toBeInTheDocument(); expect(screen.getByText('Destination Final MacDill, FL 33621')).toBeInTheDocument(); expect(screen.getByText('Delivery miles out of SIT:')).toBeInTheDocument(); @@ -203,20 +203,20 @@ describe('ServiceItemDetails Domestic Destination SIT', () => { />, ); - expect(screen.getByText('Final delivery address:')).toBeInTheDocument(); + expect(screen.getByText('Final Delivery Address:')).toBeInTheDocument(); expect(screen.getByText('-')).toBeInTheDocument(); }); it('renders DDFSIT details', () => { render(); - expect(screen.getByText('Original delivery address:')).toBeInTheDocument(); + expect(screen.getByText('Original Delivery Address:')).toBeInTheDocument(); expect(screen.getByText('Destination Original Tampa, FL 33621')).toBeInTheDocument(); }); it('renders DDSFSC details', () => { render(); - expect(screen.getByText('Original delivery address:')).toBeInTheDocument(); + expect(screen.getByText('Original Delivery Address:')).toBeInTheDocument(); expect(screen.getByText('Destination Original Tampa, FL 33621')).toBeInTheDocument(); - expect(screen.getByText('Final delivery address:')).toBeInTheDocument(); + expect(screen.getByText('Final Delivery Address:')).toBeInTheDocument(); expect(screen.getByText('Destination Final MacDill, FL 33621')).toBeInTheDocument(); expect(screen.getByText('Delivery miles out of SIT:')).toBeInTheDocument(); @@ -232,7 +232,7 @@ describe('ServiceItemDetails Domestic Destination SIT', () => { />, ); - expect(screen.getByText('Final delivery address:')).toBeInTheDocument(); + expect(screen.getByText('Final Delivery Address:')).toBeInTheDocument(); expect(screen.getByText('-')).toBeInTheDocument(); }); }); @@ -250,7 +250,7 @@ describe('ServiceItemDetails Domestic Origin SIT', () => { />, ); - expect(screen.getByText('Original pickup address:')).toBeInTheDocument(); + expect(screen.getByText('Original Pickup Address:')).toBeInTheDocument(); expect(screen.getByText('Origin Original Tampa, FL 33621')).toBeInTheDocument(); expect(screen.getByText("Add'l SIT Start Date:")).toBeInTheDocument(); @@ -275,10 +275,10 @@ describe('ServiceItemDetails Domestic Origin SIT', () => { it(`renders DOPSIT details`, () => { render(); - expect(screen.getByText('Original pickup address:')).toBeInTheDocument(); + expect(screen.getByText('Original Pickup Address:')).toBeInTheDocument(); expect(screen.getByText('Origin Original Tampa, FL 33621')).toBeInTheDocument(); - expect(screen.getByText('Actual pickup address:')).toBeInTheDocument(); + expect(screen.getByText('Actual Pickup Address:')).toBeInTheDocument(); expect(screen.getByText('Origin Actual MacDill, FL 33621')).toBeInTheDocument(); expect(screen.getByText('Delivery miles into SIT:')).toBeInTheDocument(); @@ -288,10 +288,10 @@ describe('ServiceItemDetails Domestic Origin SIT', () => { it(`renders DOSFSC details`, () => { render(); - expect(screen.getByText('Original pickup address:')).toBeInTheDocument(); + expect(screen.getByText('Original Pickup Address:')).toBeInTheDocument(); expect(screen.getByText('Origin Original Tampa, FL 33621')).toBeInTheDocument(); - expect(screen.getByText('Actual pickup address:')).toBeInTheDocument(); + expect(screen.getByText('Actual Pickup Address:')).toBeInTheDocument(); expect(screen.getByText('Origin Actual MacDill, FL 33621')).toBeInTheDocument(); expect(screen.getByText('Delivery miles into SIT:')).toBeInTheDocument(); @@ -303,7 +303,7 @@ describe('ServiceItemDetails for DOFSIT', () => { it('renders SIT entry date, ZIP, original pickup address, and reason', () => { render(); - expect(screen.getByText('Original pickup address:')).toBeInTheDocument(); + expect(screen.getByText('Original Pickup Address:')).toBeInTheDocument(); expect(screen.getByText('Origin Original Tampa, FL 33621')).toBeInTheDocument(); expect(screen.getByText('SIT entry date:')).toBeInTheDocument(); expect(screen.getByText('11 Mar 2024')).toBeInTheDocument(); @@ -344,6 +344,116 @@ describe('ServiceItemDetails Crating', () => { }); }); +describe('ServiceItemDetails International Crating & International Uncrating', () => { + const icrtDetails = { + description: 'some description', + reason: 'some reason', + itemDimensions: { length: 1000, width: 2500, height: 3000 }, + crateDimensions: { length: 2000, width: 3500, height: 4000 }, + market: 'OCONUS', + externalCrate: true, + }; + + const iucrtDetails = { + description: 'some description', + reason: 'some reason', + itemDimensions: { length: 1000, width: 2500, height: 3000 }, + crateDimensions: { length: 2000, width: 3500, height: 4000 }, + market: 'CONUS', + externalCrate: null, + }; + + it('renders description and dimensions - ICRT', () => { + render(); + + expect(screen.getByText('some description')).toBeInTheDocument(); + expect(screen.getByText('Item size:')).toBeInTheDocument(); + expect(screen.getByText('1"x2.5"x3"')).toBeInTheDocument(); + expect(screen.getByText('Crate size:')).toBeInTheDocument(); + expect(screen.getByText('2"x3.5"x4"')).toBeInTheDocument(); + expect(screen.getByText('Market:')).toBeInTheDocument(); + expect(screen.getByText('OCONUS')).toBeInTheDocument(); + expect(screen.getByText('External crate:')).toBeInTheDocument(); + expect(screen.getByText('Yes')).toBeInTheDocument(); + expect(screen.getByText('Reason:')).toBeInTheDocument(); + expect(screen.getByText('some reason')).toBeInTheDocument(); + expect(screen.getByText('Download service item documentation:')).toBeInTheDocument(); + const downloadLink = screen.getByText('receipt.pdf'); + expect(downloadLink).toBeInstanceOf(HTMLAnchorElement); + }); + + it('renders description and dimensions - IUCRT', () => { + render(); + + expect(screen.getByText('some description')).toBeInTheDocument(); + expect(screen.getByText('Item size:')).toBeInTheDocument(); + expect(screen.getByText('1"x2.5"x3"')).toBeInTheDocument(); + expect(screen.getByText('Crate size:')).toBeInTheDocument(); + expect(screen.getByText('2"x3.5"x4"')).toBeInTheDocument(); + expect(screen.getByText('Market:')).toBeInTheDocument(); + expect(screen.getByText('CONUS')).toBeInTheDocument(); + expect(screen.getByText('Reason:')).toBeInTheDocument(); + expect(screen.getByText('some reason')).toBeInTheDocument(); + expect(screen.getByText('Download service item documentation:')).toBeInTheDocument(); + const downloadLink = screen.getByText('receipt.pdf'); + expect(downloadLink).toBeInstanceOf(HTMLAnchorElement); + }); + + it('renders rejected description and dimensions - ICRT', () => { + render( + , + ); + + expect(screen.getByText('some description')).toBeInTheDocument(); + expect(screen.getByText('Item size:')).toBeInTheDocument(); + expect(screen.getByText('1"x2.5"x3"')).toBeInTheDocument(); + expect(screen.getByText('Crate size:')).toBeInTheDocument(); + expect(screen.getByText('2"x3.5"x4"')).toBeInTheDocument(); + expect(screen.getByText('Market:')).toBeInTheDocument(); + expect(screen.getByText('OCONUS')).toBeInTheDocument(); + expect(screen.getByText('External crate:')).toBeInTheDocument(); + expect(screen.getByText('Yes')).toBeInTheDocument(); + expect(screen.getByText('Reason:')).toBeInTheDocument(); + expect(screen.getByText('some reason')).toBeInTheDocument(); + expect(screen.getByText('Rejection reason:')).toBeInTheDocument(); + expect(screen.getByText('some rejection reason')).toBeInTheDocument(); + expect(screen.getByText('Download service item documentation:')).toBeInTheDocument(); + const downloadLink = screen.getByText('receipt.pdf'); + expect(downloadLink).toBeInstanceOf(HTMLAnchorElement); + }); + + it('renders rejected description and dimensions - IUCRT', () => { + render( + , + ); + + expect(screen.getByText('some description')).toBeInTheDocument(); + expect(screen.getByText('Item size:')).toBeInTheDocument(); + expect(screen.getByText('1"x2.5"x3"')).toBeInTheDocument(); + expect(screen.getByText('Crate size:')).toBeInTheDocument(); + expect(screen.getByText('2"x3.5"x4"')).toBeInTheDocument(); + expect(screen.getByText('Market:')).toBeInTheDocument(); + expect(screen.getByText('CONUS')).toBeInTheDocument(); + expect(screen.getByText('Reason:')).toBeInTheDocument(); + expect(screen.getByText('some reason')).toBeInTheDocument(); + expect(screen.getByText('Rejection reason:')).toBeInTheDocument(); + expect(screen.getByText('some rejection reason')).toBeInTheDocument(); + expect(screen.getByText('Download service item documentation:')).toBeInTheDocument(); + const downloadLink = screen.getByText('receipt.pdf'); + expect(downloadLink).toBeInstanceOf(HTMLAnchorElement); + }); +}); + describe('ServiceItemDetails Domestic Shuttling', () => { it.each([['DOSHUT'], ['DDSHUT']])('renders formatted estimated weight and reason', (code) => { render(); diff --git a/src/components/Office/ServiceItemsTable/ServiceItemsTable.jsx b/src/components/Office/ServiceItemsTable/ServiceItemsTable.jsx index 9fca8e8b3b9..1b8aeb3383c 100644 --- a/src/components/Office/ServiceItemsTable/ServiceItemsTable.jsx +++ b/src/components/Office/ServiceItemsTable/ServiceItemsTable.jsx @@ -201,7 +201,7 @@ const ServiceItemsTable = ({ if ( (serviceItem.code === 'DLH' || serviceItem.code === 'DSH') && serviceItem.details.rejectionReason === - 'Automatically rejected due to change in destination address affecting the ZIP code qualification for short haul / line haul.' + 'Automatically rejected due to change in delivery address affecting the ZIP code qualification for short haul / line haul.' ) { rejectedDSHorDLHServiceItem = true; } @@ -212,7 +212,7 @@ const ServiceItemsTable = ({
    {serviceItem.serviceItem} - {code === 'DCRT' && serviceItem.details.standaloneCrate && ' - Standalone'} + {(code === 'DCRT' || code === 'ICRT') && serviceItem.details.standaloneCrate && ' - Standalone'} {ALLOWED_RESUBMISSION_SI_CODES.includes(code) && resubmittedToolTip.isResubmitted ? ( { expect(wrapper.find('dd').at(2).text()).toBe('10"x2.5"x5"'); }); + it('renders details for international crating (ICRT)', () => { + const serviceItems = [ + { + id: 'abc123', + createdAt: '2020-11-20', + serviceItem: 'International Crating', + code: 'ICRT', + details: { + description: 'grandfather clock', + itemDimensions: { length: 7000, width: 2000, height: 3500 }, + crateDimensions: { length: 10000, width: 2500, height: 5000 }, + market: 'OCONUS', + externalCrate: true, + standaloneCrate: true, + }, + }, + ]; + + const wrapper = mount( + + + , + ); + + expect(wrapper.find('td').at(0).text()).toContain('International Crating - Standalone'); + expect(wrapper.find('td').at(0).text()).toContain('Date requested: 20 Nov 2020'); + expect(wrapper.find('dt').at(0).text()).toBe('Description:'); + expect(wrapper.find('dd').at(0).text()).toBe('grandfather clock'); + expect(wrapper.find('dt').at(1).text()).toBe('Item size:'); + expect(wrapper.find('dd').at(1).text()).toBe('7"x2"x3.5"'); + expect(wrapper.find('dt').at(2).text()).toBe('Crate size:'); + expect(wrapper.find('dd').at(2).text()).toBe('10"x2.5"x5"'); + expect(wrapper.find('dt').at(3).text()).toBe('External crate:'); + expect(wrapper.find('dd').at(3).text()).toBe('Yes'); + expect(wrapper.find('dt').at(4).text()).toBe('Market:'); + expect(wrapper.find('dd').at(4).text()).toBe('OCONUS'); + expect(wrapper.find('dt').at(5).text()).toBe('Reason:'); + expect(wrapper.find('dd').at(5).text()).toBe('-'); + }); + + it('renders details for international crating (IUCRT)', () => { + const serviceItems = [ + { + id: 'abc123', + createdAt: '2020-11-20', + serviceItem: 'International Crating', + code: 'ICRT', + details: { + description: 'grandfather clock', + itemDimensions: { length: 7000, width: 2000, height: 3500 }, + crateDimensions: { length: 10000, width: 2500, height: 5000 }, + market: 'OCONUS', + externalCrate: null, + }, + }, + ]; + + const wrapper = mount( + + + , + ); + + expect(wrapper.find('td').at(0).text()).toContain('Date requested: 20 Nov 2020'); + expect(wrapper.find('dt').at(0).text()).toBe('Description:'); + expect(wrapper.find('dd').at(0).text()).toBe('grandfather clock'); + expect(wrapper.find('dt').at(1).text()).toBe('Item size:'); + expect(wrapper.find('dd').at(1).text()).toBe('7"x2"x3.5"'); + expect(wrapper.find('dt').at(2).text()).toBe('Crate size:'); + expect(wrapper.find('dd').at(2).text()).toBe('10"x2.5"x5"'); + expect(wrapper.find('dt').at(3).text()).toBe('Market:'); + expect(wrapper.find('dd').at(3).text()).toBe('OCONUS'); + expect(wrapper.find('dt').at(4).text()).toBe('Reason:'); + expect(wrapper.find('dd').at(4).text()).toBe('-'); + }); + it('renders with authorized price for MS item', () => { const serviceItems = [ { @@ -197,7 +281,7 @@ describe('ServiceItemsTable', () => { ); expect(wrapper.find('table').exists()).toBe(true); - expect(wrapper.find('dt').at(0).text()).toBe('Original delivery address:'); + expect(wrapper.find('dt').at(0).text()).toBe('Original Delivery Address:'); expect(wrapper.find('dd').at(0).text()).toBe('Destination Original Tampa, FL 33621'); expect(wrapper.find('dt').at(1).text()).toBe('SIT entry date:'); @@ -247,7 +331,7 @@ describe('ServiceItemsTable', () => { /> , ); - expect(wrapper.find('dt').at(0).contains('Original pickup address')).toBe(true); + expect(wrapper.find('dt').at(0).contains('Original Pickup Address')).toBe(true); expect(wrapper.find('dd').at(0).contains('Origin Original Tampa, FL 33621')).toBe(true); expect(wrapper.find('dt').at(1).contains('SIT entry date')).toBe(true); @@ -382,7 +466,7 @@ describe('ServiceItemsTable', () => { expect(wrapper.find('button[data-testid="rejectTextButton"]').length).toBeFalsy(); }); - it('does not show accept button when DSH is rejected as a result of destination address change', () => { + it('does not show accept button when DSH is rejected as a result of delivery address change', () => { const serviceItems = [ { id: 'dsh123', @@ -392,7 +476,7 @@ describe('ServiceItemsTable', () => { code: 'DSH', details: { rejectionReason: - 'Automatically rejected due to change in destination address affecting the ZIP code qualification for short haul / line haul.', + 'Automatically rejected due to change in delivery address affecting the ZIP code qualification for short haul / line haul.', }, }, ]; @@ -415,7 +499,7 @@ describe('ServiceItemsTable', () => { expect(approveTextButton.at(0).contains('Approve')).toBe(false); }); - it('does not show accept button when DLH is rejected as a result of destination address change', () => { + it('does not show accept button when DLH is rejected as a result of delivery address change', () => { const serviceItems = [ { id: 'dlh123', @@ -425,7 +509,7 @@ describe('ServiceItemsTable', () => { code: 'DLH', details: { rejectionReason: - 'Automatically rejected due to change in destination address affecting the ZIP code qualification for short haul / line haul.', + 'Automatically rejected due to change in delivery address affecting the ZIP code qualification for short haul / line haul.', }, }, ]; @@ -448,7 +532,7 @@ describe('ServiceItemsTable', () => { expect(approveTextButton.at(0).contains('Approve')).toBe(false); }); - it('shows accept button when DSH is rejected but NOT as a result of destination address change', () => { + it('shows accept button when DSH is rejected but NOT as a result of delivery address change', () => { const serviceItems = [ { id: 'dsh123', @@ -458,7 +542,7 @@ describe('ServiceItemsTable', () => { code: 'DSH', details: { rejectionReason: - 'Any reason other than "Automatically rejected due to change in destination address affecting the ZIP code qualification for short haul / line haul."', + 'Any reason other than "Automatically rejected due to change in delivery address affecting the ZIP code qualification for short haul / line haul."', }, }, ]; @@ -481,7 +565,7 @@ describe('ServiceItemsTable', () => { expect(approveTextButton.at(0).contains('Approve')).toBe(true); }); - it('shows accept button when DLH is rejected but NOT as a result of destination address change', () => { + it('shows accept button when DLH is rejected but NOT as a result of delivery address change', () => { const serviceItems = [ { id: 'dlh123', @@ -491,7 +575,7 @@ describe('ServiceItemsTable', () => { code: 'DLH', details: { rejectionReason: - 'Any reason other than "Automatically rejected due to change in destination address affecting the ZIP code qualification for short haul / line haul."', + 'Any reason other than "Automatically rejected due to change in delivery address affecting the ZIP code qualification for short haul / line haul."', }, }, ]; @@ -715,7 +799,7 @@ describe('ServiceItemsTable', () => { code: 'DLH', details: { rejectionReason: - 'Any reason other than "Automatically rejected due to change in destination address affecting the ZIP code qualification for short haul / line haul."', + 'Any reason other than "Automatically rejected due to change in delivery address affecting the ZIP code qualification for short haul / line haul."', }, }, ]; diff --git a/src/components/Office/ShipmentAddressUpdateReviewRequestModal/ShipmentAddressUpdateReviewRequestModal.test.jsx b/src/components/Office/ShipmentAddressUpdateReviewRequestModal/ShipmentAddressUpdateReviewRequestModal.test.jsx index 0d38179e2a9..57fd114712f 100644 --- a/src/components/Office/ShipmentAddressUpdateReviewRequestModal/ShipmentAddressUpdateReviewRequestModal.test.jsx +++ b/src/components/Office/ShipmentAddressUpdateReviewRequestModal/ShipmentAddressUpdateReviewRequestModal.test.jsx @@ -147,7 +147,7 @@ describe('ShipmentAddressUpdateReviewRequestModal', () => { expect(screen.getByRole('heading', { level: 2, name: 'Review request' })).toBeInTheDocument(); // Address update preview component - expect(screen.getByRole('heading', { level: 3, name: 'Delivery location' })).toBeInTheDocument(); + expect(screen.getByRole('heading', { level: 3, name: 'Delivery Address' })).toBeInTheDocument(); // Form expect(screen.getByRole('heading', { level: 4, name: 'Review Request' })).toBeInTheDocument(); diff --git a/src/components/Office/ShipmentAddresses/ShipmentAddresses.jsx b/src/components/Office/ShipmentAddresses/ShipmentAddresses.jsx index affedc57f01..34b8f0c791d 100644 --- a/src/components/Office/ShipmentAddresses/ShipmentAddresses.jsx +++ b/src/components/Office/ShipmentAddresses/ShipmentAddresses.jsx @@ -34,12 +34,12 @@ const ShipmentAddresses = ({ destinationHeader = ''; break; case SHIPMENT_OPTIONS.NTS: - pickupHeader = 'Pickup address'; + pickupHeader = 'Pickup Address'; destinationHeader = 'Facility address'; break; case SHIPMENT_OPTIONS.NTSR: pickupHeader = 'Facility address'; - destinationHeader = 'Delivery address'; + destinationHeader = 'Delivery Address'; break; default: pickupHeader = "Customer's addresses"; diff --git a/src/components/Office/ShipmentAddresses/ShipmentAddresses.test.jsx b/src/components/Office/ShipmentAddresses/ShipmentAddresses.test.jsx index 4e437845828..9153061aafa 100644 --- a/src/components/Office/ShipmentAddresses/ShipmentAddresses.test.jsx +++ b/src/components/Office/ShipmentAddresses/ShipmentAddresses.test.jsx @@ -192,7 +192,7 @@ describe('ShipmentAddresses', () => { shipmentInfo: { ...testProps.shipmentInfo, shipmentType: SHIPMENT_OPTIONS.NTS }, }; render(); - expect(screen.getByText('Pickup address')).toBeInTheDocument(); + expect(screen.getByText('Pickup Address')).toBeInTheDocument(); expect(screen.getByText('Facility address')).toBeInTheDocument(); }); @@ -203,7 +203,7 @@ describe('ShipmentAddresses', () => { }; render(); expect(screen.getByText('Facility address')).toBeInTheDocument(); - expect(screen.getByText('Delivery address')).toBeInTheDocument(); + expect(screen.getByText('Delivery Address')).toBeInTheDocument(); }); it('shows correct headings for PPM', () => { diff --git a/src/components/Office/ShipmentApprovalPreview/ShipmentApprovalPreview.test.jsx b/src/components/Office/ShipmentApprovalPreview/ShipmentApprovalPreview.test.jsx index b4089546579..b3e3cb0d7cf 100644 --- a/src/components/Office/ShipmentApprovalPreview/ShipmentApprovalPreview.test.jsx +++ b/src/components/Office/ShipmentApprovalPreview/ShipmentApprovalPreview.test.jsx @@ -219,6 +219,21 @@ const ordersInfo = { ordersNumber: 'ORDER3', ordersType: 'PERMANENT_CHANGE_OF_STATION', ordersTypeDetail: 'TBD', + ordersDocuments: [ + { + 'c0a22a98-a806-47a2-ab54-2dac938667b3': { + bytes: 2202009, + contentType: 'application/pdf', + createdAt: '2024-10-23T16:31:21.085Z', + filename: 'testFile.pdf', + id: 'c0a22a98-a806-47a2-ab54-2dac938667b3', + status: 'PROCESSING', + updatedAt: '2024-10-23T16:31:21.085Z', + uploadType: 'USER', + url: '/storage/USER/uploads/c0a22a98-a806-47a2-ab54-2dac938667b3?contentType=application%2Fpdf', + }, + }, + ], tacMDC: '', sacSDN: '', }; @@ -358,7 +373,7 @@ describe('Shipment preview modal', () => { expect(cancelClicked).toHaveBeenCalledTimes(2); }); - it('renders a postal only destination address', () => { + it('renders a postal only delivery address', () => { const wrapper = mount(
    - {displayInfo.isActualExpenseReimbursement && actual expense reimbursement} + {displayInfo.isActualExpenseReimbursement && ( + actual expense reimbursement + )} {displayInfo.isDiversion && diversion} {(displayInfo.shipmentStatus === shipmentStatuses.CANCELED || displayInfo.status === shipmentStatuses.CANCELED || @@ -108,9 +110,10 @@ const ShipmentDisplay = ({ cancellation requested )} {displayInfo.usesExternalVendor && external vendor} - {(displayInfo.ppmShipment?.status === ppmShipmentStatuses.CLOSEOUT_COMPLETE || - displayInfo.ppmShipment?.status === ppmShipmentStatuses.WAITING_ON_CUSTOMER) && ( - packet ready for download + {displayInfo.ppmShipment?.status && ( + + {ppmShipmentStatusLabels[displayInfo.ppmShipment?.status]} + )}
    diff --git a/src/components/Office/ShipmentDisplay/ShipmentDisplay.test.jsx b/src/components/Office/ShipmentDisplay/ShipmentDisplay.test.jsx index 7fcb0812516..ccc4d8bb1b1 100644 --- a/src/components/Office/ShipmentDisplay/ShipmentDisplay.test.jsx +++ b/src/components/Office/ShipmentDisplay/ShipmentDisplay.test.jsx @@ -296,7 +296,7 @@ describe('Shipment Container', () => { /> , ); - expect(screen.getByTestId('tag', { name: 'packet ready for download' })).toBeInTheDocument(); + expect(screen.getByTestId('ppmStatusTag')).toBeInTheDocument(); }); it('renders with canceled tag', () => { render(); @@ -316,7 +316,7 @@ describe('Shipment Container', () => { /> , ); - expect(screen.getByTestId('tag', { name: 'packet ready for download' })).toBeInTheDocument(); + expect(screen.getByTestId('ppmStatusTag')).toBeInTheDocument(); }); it('rejected', () => { render( @@ -332,10 +332,10 @@ describe('Shipment Container', () => { /> , ); - expect(screen.getByTestId('tag', { name: 'packet ready for download' })).toBeInTheDocument(); + expect(screen.getByTestId('ppmStatusTag')).toBeInTheDocument(); }); }); - it('renders the Actual Expense Reimbursement tag', () => { + it('renders the Actual Expense Reimbursement & PPM status tags', () => { render( { /> , ); - expect(screen.getByTestId('tag', { name: 'actual expense reimbursement' })).toBeInTheDocument(); + expect(screen.getByTestId('actualReimbursementTag')).toBeInTheDocument(); + expect(screen.getByTestId('ppmStatusTag')).toBeInTheDocument(); }); }); }); diff --git a/src/components/Office/ShipmentForm/ShipmentForm.jsx b/src/components/Office/ShipmentForm/ShipmentForm.jsx index 5d24e7e5e16..ddef94dd6e4 100644 --- a/src/components/Office/ShipmentForm/ShipmentForm.jsx +++ b/src/components/Office/ShipmentForm/ShipmentForm.jsx @@ -845,7 +845,7 @@ const ShipmentForm = (props) => { )} {deliveryAddressUpdateRequested && ( - Request needs review. See delivery location to proceed. + Request needs review. See delivery address to proceed. )} @@ -933,19 +933,19 @@ const ShipmentForm = (props) => { <> ( <>

    What address are the movers picking up from?

    {fields} -

    Second pickup location

    +

    Second Pickup Address

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

    @@ -956,7 +956,7 @@ const ShipmentForm = (props) => { label="Yes" name="hasSecondaryPickup" value="yes" - title="Yes, I have a second pickup location" + title="Yes, I have a second pickup address" checked={hasSecondaryPickup === 'yes'} /> { label="No" name="hasSecondaryPickup" value="no" - title="No, I do not have a second pickup location" + title="No, I do not have a second pickup address" checked={hasSecondaryPickup !== 'yes'} />
    @@ -976,7 +976,7 @@ const ShipmentForm = (props) => { {isTertiaryAddressEnabled && ( <> -

    Third pickup location

    +

    Third Pickup Address

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

    @@ -987,7 +987,7 @@ const ShipmentForm = (props) => { label="Yes" name="hasTertiaryPickup" value="yes" - title="Yes, I have a third pickup location" + title="Yes, I have a third pickup address" checked={hasTertiaryPickup === 'yes'} /> { label="No" name="hasTertiaryPickup" value="no" - title="No, I do not have a third pickup location" + title="No, I do not have a third pickup address" checked={hasTertiaryPickup !== 'yes'} />
    @@ -1059,7 +1059,7 @@ const ShipmentForm = (props) => { {deliveryAddressUpdateRequested && ( - Pending delivery location change request needs review.{' '} + Pending delivery address change request needs review.{' '}
    @@ -1426,7 +1426,7 @@ const ShipmentForm = (props) => { {isTertiaryAddressEnabled && ( <> -

    Third pickup address

    +

    Third Pickup Address

    Will you move any belongings from a third address? (Must be near the pickup @@ -1440,7 +1440,7 @@ const ShipmentForm = (props) => { label="Yes" name="hasTertiaryPickup" value="true" - title="Yes, there is a third pickup location" + title="Yes, there is a third pickup address" checked={hasTertiaryPickup === 'true'} /> { label="No" name="hasTertiaryPickup" value="false" - title="No, there is not a third pickup location" + title="No, there is not a third pickup address" checked={hasTertiaryPickup !== 'true'} />

    @@ -1470,7 +1470,7 @@ const ShipmentForm = (props) => { render={(fields) => ( <> {fields} -

    Second delivery address

    +

    Second Delivery Address

    Will you move any belongings to a second address? (Must be near the delivery address. @@ -1504,7 +1504,7 @@ const ShipmentForm = (props) => { {isTertiaryAddressEnabled && ( <> -

    Third delivery address

    +

    Third Delivery Address

    Will you move any belongings to a third address? (Must be near the delivery @@ -1518,7 +1518,7 @@ const ShipmentForm = (props) => { label="Yes" name="hasTertiaryDestination" value="true" - title="Yes, I have a third delivery location" + title="Yes, I have a third delivery address" checked={hasTertiaryDestination === 'true'} /> { label="No" name="hasTertiaryDestination" value="false" - title="No, I do not have a third delivery location" + title="No, I do not have a third delivery address" checked={hasTertiaryDestination !== 'true'} />

    diff --git a/src/components/Office/ShipmentForm/ShipmentForm.test.jsx b/src/components/Office/ShipmentForm/ShipmentForm.test.jsx index 72afb50cd9e..6cbfe8e866f 100644 --- a/src/components/Office/ShipmentForm/ShipmentForm.test.jsx +++ b/src/components/Office/ShipmentForm/ShipmentForm.test.jsx @@ -398,8 +398,8 @@ describe('ShipmentForm component', () => { expect(screen.getByLabelText('Requested pickup date')).toBeInstanceOf(HTMLInputElement); - expect(screen.getByText('Pickup location')).toBeInstanceOf(HTMLLegendElement); - expect(screen.getByLabelText('Use current address')).toBeInstanceOf(HTMLInputElement); + expect(screen.getByText('Pickup Address')).toBeInstanceOf(HTMLLegendElement); + expect(screen.getByLabelText('Use pickup address')).toBeInstanceOf(HTMLInputElement); expect(screen.getByLabelText(/Address 1/)).toBeInstanceOf(HTMLInputElement); expect(screen.getByLabelText(/Address 2/)).toBeInstanceOf(HTMLInputElement); expect(screen.getByLabelText('City')).toBeInstanceOf(HTMLInputElement); @@ -414,7 +414,7 @@ describe('ShipmentForm component', () => { expect(screen.getByLabelText('Requested delivery date')).toBeInstanceOf(HTMLInputElement); - const deliveryLocationSectionHeadings = screen.getAllByText('Delivery location'); + const deliveryLocationSectionHeadings = screen.getAllByText('Delivery Address'); expect(deliveryLocationSectionHeadings).toHaveLength(2); expect(deliveryLocationSectionHeadings[0]).toBeInstanceOf(HTMLParagraphElement); expect(deliveryLocationSectionHeadings[1]).toBeInstanceOf(HTMLLegendElement); @@ -468,7 +468,7 @@ describe('ShipmentForm component', () => { renderWithRouter(); await act(async () => { - await user.click(screen.getByLabelText('Use current address')); + await user.click(screen.getByLabelText('Use pickup address')); }); expect((await screen.findAllByLabelText('Address 1'))[0]).toHaveValue( @@ -578,7 +578,7 @@ describe('ShipmentForm component', () => { ); expect(await screen.findByLabelText('Requested pickup date')).toHaveValue('01 Mar 2020'); - expect(screen.getByLabelText('Use current address')).not.toBeChecked(); + expect(screen.getByLabelText('Use pickup address')).not.toBeChecked(); expect(screen.getAllByLabelText('Address 1')[0]).toHaveValue('812 S 129th St'); expect(screen.getAllByLabelText(/Address 2/)[0]).toHaveValue(''); expect(screen.getAllByLabelText('City')[0]).toHaveValue('San Antonio'); @@ -626,7 +626,7 @@ describe('ShipmentForm component', () => { ); expect(await screen.findByLabelText('Requested pickup date')).toHaveValue('01 Mar 2020'); - expect(screen.getByLabelText('Use current address')).not.toBeChecked(); + expect(screen.getByLabelText('Use pickup address')).not.toBeChecked(); expect(screen.getAllByLabelText('Address 1')[0]).toHaveValue('812 S 129th St'); expect(screen.getAllByLabelText(/Address 2/)[0]).toHaveValue(''); expect(screen.getAllByLabelText('City')[0]).toHaveValue('San Antonio'); @@ -674,9 +674,9 @@ describe('ShipmentForm component', () => { const alerts = await screen.findAllByTestId('alert'); expect(alerts).toHaveLength(2); // Should have 2 alerts shown due to the address update request - expect(alerts[0]).toHaveTextContent('Request needs review. See delivery location to proceed.'); + expect(alerts[0]).toHaveTextContent('Request needs review. See delivery address to proceed.'); expect(alerts[1]).toHaveTextContent( - 'Pending delivery location change request needs review. Review request to proceed.', + 'Pending delivery address change request needs review. Review request to proceed.', ); }; @@ -804,8 +804,8 @@ describe('ShipmentForm component', () => { expect(screen.getByLabelText('Requested pickup date')).toBeInstanceOf(HTMLInputElement); - expect(screen.getByText('Pickup location')).toBeInstanceOf(HTMLLegendElement); - expect(screen.getByLabelText('Use current address')).toBeInstanceOf(HTMLInputElement); + expect(screen.getByText('Pickup Address')).toBeInstanceOf(HTMLLegendElement); + expect(screen.getByLabelText('Use pickup address')).toBeInstanceOf(HTMLInputElement); expect(screen.getByLabelText(/Address 1/)).toBeInstanceOf(HTMLInputElement); expect(screen.getByLabelText(/Address 2/)).toBeInstanceOf(HTMLInputElement); expect(screen.getByLabelText('City')).toBeInstanceOf(HTMLInputElement); @@ -818,7 +818,7 @@ describe('ShipmentForm component', () => { expect(screen.getByLabelText('Phone')).toHaveAttribute('name', 'pickup.agent.phone'); expect(screen.getByLabelText('Email')).toHaveAttribute('name', 'pickup.agent.email'); - expect(screen.queryByText('Delivery location')).not.toBeInTheDocument(); + expect(screen.queryByText('Delivery Address')).not.toBeInTheDocument(); expect(screen.queryByText(/Receiving agent/)).not.toBeInTheDocument(); expect(screen.getByText('Customer remarks')).toBeTruthy(); @@ -958,12 +958,12 @@ describe('ShipmentForm component', () => { expect(await screen.findByText('NTS-release')).toHaveClass('usa-tag'); - expect(screen.queryByText('Pickup location')).not.toBeInTheDocument(); + expect(screen.queryByText('Pickup Address')).not.toBeInTheDocument(); expect(screen.queryByText(/Releasing agent/)).not.toBeInTheDocument(); expect(screen.getByLabelText('Requested delivery date')).toBeInstanceOf(HTMLInputElement); - expect(screen.getByText('Delivery location')).toBeInstanceOf(HTMLLegendElement); + expect(screen.getByText('Delivery Address')).toBeInstanceOf(HTMLLegendElement); expect(screen.getByText(/Receiving agent/).parentElement).toBeInstanceOf(HTMLLegendElement); expect(screen.getByLabelText('First name')).toHaveAttribute('name', 'delivery.agent.firstName'); @@ -1130,7 +1130,7 @@ describe('ShipmentForm component', () => { expect(await screen.findByText('HHG')).toHaveClass('usa-tag'); expect(screen.queryByRole('heading', { level: 2, name: 'Vendor' })).not.toBeInTheDocument(); expect(screen.getByLabelText('Requested pickup date')).toBeInTheDocument(); - expect(screen.getByText('Pickup location')).toBeInTheDocument(); + expect(screen.getByText('Pickup Address')).toBeInTheDocument(); expect(screen.getByLabelText('Requested delivery date')).toBeInTheDocument(); expect(screen.getByText(/Receiving agent/).parentElement).toBeInTheDocument(); expect(screen.getByText('Customer remarks')).toBeInTheDocument(); @@ -1561,7 +1561,7 @@ describe('ShipmentForm component', () => { expect(await screen.findByTestId('tag')).toHaveTextContent('PPM'); }); - it('PPM - destination address street 1 is OPTIONAL', async () => { + it('PPM - delivery address street 1 is OPTIONAL', async () => { renderWithRouter( { expect(screen.queryByRole('alert')).not.toBeInTheDocument(); }); - // test that destination address street1 is OPTIONAL and not raise any required alert + // test that delivery address street1 is OPTIONAL and not raise any required alert await userEvent.type(document.querySelector('input[name="destination.address.streetAddress1"]'), ' '); await userEvent.tab(); await waitFor(() => { @@ -1806,7 +1806,7 @@ describe('ShipmentForm component', () => { expect(screen.getAllByLabelText('No')[3]).not.toBeChecked(); }); - it('test destination address street 1 is OPTIONAL', async () => { + it('test delivery address street 1 is OPTIONAL', async () => { isBooleanFlagEnabled.mockImplementation(() => Promise.resolve(true)); renderWithRouter( { expect(screen.queryByRole('alert')).not.toBeInTheDocument(); }); - // test that destination address street1 is OPTIONAL and not raise any required alert + // test that delivery address street1 is OPTIONAL and not raise any required alert await userEvent.clear(document.querySelector('input[name="destination.address.streetAddress1"]')); await userEvent.tab(); await waitFor(() => { // verify required alert was not raised expect(screen.queryByRole('alert')).not.toBeInTheDocument(); - // 'Optional' labelHint on address display. expecting a total of 9(2 for pickup address and 3 destination address, 4 for secondary addrs). - // This is to verify Optional labelHints are displayed correctly for PPM onboarding/edit for the destination address + // 'Optional' labelHint on address display. expecting a total of 9(2 for pickup address and 3 delivery address, 4 for secondary addrs). + // This is to verify Optional labelHints are displayed correctly for PPM onboarding/edit for the delivery address // street 1 is now OPTIONAL. If this fails it means addtional labelHints have been introduced elsewhere within the control. const hints = document.getElementsByClassName('usa-hint'); expect(hints.length).toBe(9); @@ -2135,7 +2135,7 @@ describe('ShipmentForm component', () => { expect(await screen.findByTestId('tag')).toHaveTextContent('PPM'); expect(screen.getByText('Is this PPM an Actual Expense Reimbursement?')).toBeInTheDocument(); expect(screen.getByText('What address are you moving from?')).toBeInTheDocument(); - expect(screen.getByText('Second pickup address')).toBeInTheDocument(); + expect(screen.getByText('Second Pickup Address')).toBeInTheDocument(); expect( screen.getByText( 'Will you move any belongings from a second address? (Must be near the pickup address. Subject to approval.)', @@ -2143,7 +2143,7 @@ describe('ShipmentForm component', () => { ).toBeInTheDocument(); expect(screen.getByText('Delivery Address')).toBeInTheDocument(); - expect(screen.getByText('Second delivery address')).toBeInTheDocument(); + expect(screen.getByText('Second Delivery Address')).toBeInTheDocument(); expect( screen.getByText( 'Will you move any belongings to a second address? (Must be near the delivery address. Subject to approval.)', @@ -2160,9 +2160,9 @@ describe('ShipmentForm component', () => { userRole={roleTypes.SERVICES_COUNSELOR} />, ); - expect(screen.queryByText('Third pickup address')).not.toBeInTheDocument(); + expect(screen.queryByText('Third Pickup Address')).not.toBeInTheDocument(); fireEvent.click(screen.getByTestId('has-secondary-pickup')); - expect(await screen.findByText('Third pickup address')).toBeInTheDocument(); + expect(await screen.findByText('Third Pickup Address')).toBeInTheDocument(); expect( await screen.findByText( 'Will you move any belongings from a third address? (Must be near the pickup address. Subject to approval.)', @@ -2179,9 +2179,9 @@ describe('ShipmentForm component', () => { userRole={roleTypes.SERVICES_COUNSELOR} />, ); - expect(screen.queryByText('Third delivery address')).not.toBeInTheDocument(); + expect(screen.queryByText('Third Delivery Address')).not.toBeInTheDocument(); fireEvent.click(screen.getByTestId('has-secondary-destination')); - expect(await screen.findByText('Third delivery address')).toBeInTheDocument(); + expect(await screen.findByText('Third Delivery Address')).toBeInTheDocument(); expect( await screen.findByText( 'Will you move any belongings to a third address? (Must be near the delivery address. Subject to approval.)', diff --git a/src/components/Office/TXOTabNav/TXOTabNav.test.jsx b/src/components/Office/TXOTabNav/TXOTabNav.test.jsx index 2779c53b11e..36f38df0e18 100644 --- a/src/components/Office/TXOTabNav/TXOTabNav.test.jsx +++ b/src/components/Office/TXOTabNav/TXOTabNav.test.jsx @@ -49,7 +49,7 @@ describe('Move details tag rendering', () => { expect(within(moveDetailsTab).getByTestId('tag')).toHaveTextContent('5'); }); - it('should render the move details tab container with a tag that shows the count of items that need attention when there are approved shipments with a destination address update requiring TXO review', () => { + it('should render the move details tab container with a tag that shows the count of items that need attention when there are approved shipments with a delivery address update requiring TXO review', () => { const moveDetailsOneShipment = { ...basicNavProps, shipmentsWithDeliveryAddressUpdateRequestedCount: 1, diff --git a/src/components/PrimeUI/CreateShipmentServiceItemForm/CreateShipmentServiceItemForm.jsx b/src/components/PrimeUI/CreateShipmentServiceItemForm/CreateShipmentServiceItemForm.jsx index c6c1d1fa68a..15ccedb7d41 100644 --- a/src/components/PrimeUI/CreateShipmentServiceItemForm/CreateShipmentServiceItemForm.jsx +++ b/src/components/PrimeUI/CreateShipmentServiceItemForm/CreateShipmentServiceItemForm.jsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { Dropdown, Label } from '@trussworks/react-uswds'; import PropTypes from 'prop-types'; @@ -7,15 +7,33 @@ import DestinationSITServiceItemForm from './DestinationSITServiceItemForm'; import OriginSITServiceItemForm from './OriginSITServiceItemForm'; import ShuttleSITServiceItemForm from './ShuttleSITServiceItemForm'; import DomesticCratingForm from './DomesticCratingForm'; +import InternationalCratingForm from './InternationalCratingForm'; +import { isBooleanFlagEnabled } from 'utils/featureFlags'; import { ShipmentShape } from 'types/shipment'; import { createServiceItemModelTypes } from 'constants/prime'; import Shipment from 'components/PrimeUI/Shipment/Shipment'; +import { FEATURE_FLAG_KEYS } from 'shared/constants'; const CreateShipmentServiceItemForm = ({ shipment, createServiceItemMutation }) => { - const { MTOServiceItemOriginSIT, MTOServiceItemDestSIT, MTOServiceItemShuttle, MTOServiceItemDomesticCrating } = - createServiceItemModelTypes; + const { + MTOServiceItemOriginSIT, + MTOServiceItemDestSIT, + MTOServiceItemShuttle, + MTOServiceItemDomesticCrating, + MTOServiceItemInternationalCrating, + } = createServiceItemModelTypes; const [selectedServiceItemType, setSelectedServiceItemType] = useState(MTOServiceItemOriginSIT); + const [enableAlaskaFeatureFlag, setEnableAlaskaFeatureFlag] = useState(false); + + useEffect(() => { + const fetchData = async () => { + isBooleanFlagEnabled(FEATURE_FLAG_KEYS.ENABLE_ALASKA).then((res) => { + setEnableAlaskaFeatureFlag(res); + }); + }; + fetchData(); + }, []); const handleServiceItemTypeChange = (event) => { setSelectedServiceItemType(event.target.value); @@ -31,6 +49,7 @@ const CreateShipmentServiceItemForm = ({ shipment, createServiceItemMutation }) + {enableAlaskaFeatureFlag && } {selectedServiceItemType === MTOServiceItemOriginSIT && ( @@ -45,6 +64,9 @@ const CreateShipmentServiceItemForm = ({ shipment, createServiceItemMutation }) {selectedServiceItemType === MTOServiceItemDomesticCrating && ( )} + {enableAlaskaFeatureFlag && selectedServiceItemType === MTOServiceItemInternationalCrating && ( + + )}
    ); }; diff --git a/src/components/PrimeUI/CreateShipmentServiceItemForm/CreateShipmentServiceItemForm.test.jsx b/src/components/PrimeUI/CreateShipmentServiceItemForm/CreateShipmentServiceItemForm.test.jsx index c6f54569405..55da98f0b2f 100644 --- a/src/components/PrimeUI/CreateShipmentServiceItemForm/CreateShipmentServiceItemForm.test.jsx +++ b/src/components/PrimeUI/CreateShipmentServiceItemForm/CreateShipmentServiceItemForm.test.jsx @@ -1,11 +1,17 @@ import React from 'react'; -import { render, screen } from '@testing-library/react'; +import { render, screen, act } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import CreateShipmentServiceItemForm from './CreateShipmentServiceItemForm'; import { createServiceItemModelTypes } from 'constants/prime'; import { MockProviders } from 'testUtils'; +import { isBooleanFlagEnabled } from 'utils/featureFlags'; + +jest.mock('utils/featureFlags', () => ({ + ...jest.requireActual('utils/featureFlags'), + isBooleanFlagEnabled: jest.fn().mockImplementation(() => Promise.resolve(false)), +})); const approvedMoveTaskOrder = { moveTaskOrder: { @@ -84,15 +90,19 @@ describe('CreateShipmentServiceItemForm component', () => { ['destinationSITServiceItemForm', createServiceItemModelTypes.MTOServiceItemDestSIT], ['shuttleSITServiceItemForm', createServiceItemModelTypes.MTOServiceItemShuttle], ['DomesticCratingForm', createServiceItemModelTypes.MTOServiceItemDomesticCrating], + ['InternationalCratingForm', createServiceItemModelTypes.MTOServiceItemInternationalCrating], ])('renders %s after selecting %s type', async (formName, serviceItemType) => { + isBooleanFlagEnabled.mockResolvedValue(true); const shipment = approvedMoveTaskOrder.moveTaskOrder.mtoShipments[0]; - render( - - - , - ); + await act(async () => { + render( + + + , + ); + }); - const dropdown = screen.getByRole('combobox', { name: 'Service item type' }); + const dropdown = await screen.getByRole('combobox', { name: 'Service item type' }); await userEvent.selectOptions(dropdown, [serviceItemType]); expect(screen.getByRole('form', { testid: formName })).toBeInTheDocument(); diff --git a/src/components/PrimeUI/CreateShipmentServiceItemForm/DestinationSITServiceItemForm.jsx b/src/components/PrimeUI/CreateShipmentServiceItemForm/DestinationSITServiceItemForm.jsx index bceb3170564..0eef9278cca 100644 --- a/src/components/PrimeUI/CreateShipmentServiceItemForm/DestinationSITServiceItemForm.jsx +++ b/src/components/PrimeUI/CreateShipmentServiceItemForm/DestinationSITServiceItemForm.jsx @@ -116,8 +116,8 @@ const DestinationSITServiceItemForm = ({ shipment, submission }) => { DDDSIT (Destination SIT delivery)
    DDSFSC (Destination SIT fuel surcharge)

    - NOTE: The above service items will use the current destination address of the shipment as - their final destination address. Ensure the shipment address is accurate before creating these service items. + NOTE: The above service items will use the current delivery address of the shipment as their + final delivery address. Ensure the shipment address is accurate before creating these service items. diff --git a/src/components/PrimeUI/CreateShipmentServiceItemForm/DestinationSITServiceItemForm.test.jsx b/src/components/PrimeUI/CreateShipmentServiceItemForm/DestinationSITServiceItemForm.test.jsx index a4c4a8c95fb..d87d31e54f2 100644 --- a/src/components/PrimeUI/CreateShipmentServiceItemForm/DestinationSITServiceItemForm.test.jsx +++ b/src/components/PrimeUI/CreateShipmentServiceItemForm/DestinationSITServiceItemForm.test.jsx @@ -97,7 +97,7 @@ describe('DestinationSITServiceItemForm component', () => { expect(hintInfo).toHaveTextContent('DDDSIT (Destination SIT delivery)'); expect(hintInfo).toHaveTextContent('DDSFSC (Destination SIT fuel surcharge)'); expect(hintInfo).toHaveTextContent( - 'NOTE: The above service items will use the current destination address of the shipment as their final destination address. Ensure the shipment address is accurate before creating these service items.', + 'NOTE: The above service items will use the current delivery address of the shipment as their final delivery address. Ensure the shipment address is accurate before creating these service items.', ); }); diff --git a/src/components/PrimeUI/CreateShipmentServiceItemForm/InternationalCratingForm.jsx b/src/components/PrimeUI/CreateShipmentServiceItemForm/InternationalCratingForm.jsx new file mode 100644 index 00000000000..e8f8ba42d03 --- /dev/null +++ b/src/components/PrimeUI/CreateShipmentServiceItemForm/InternationalCratingForm.jsx @@ -0,0 +1,157 @@ +import * as Yup from 'yup'; +import { Formik } from 'formik'; +import { Button } from '@trussworks/react-uswds'; +import React from 'react'; +import PropTypes from 'prop-types'; + +import { Form } from 'components/form/Form'; +import TextField from 'components/form/fields/TextField/TextField'; +import { DropdownInput } from 'components/form/fields/DropdownInput'; +import { ShipmentShape } from 'types/shipment'; +import { internationalCratingServiceItemCodeOptions, createServiceItemModelTypes } from 'constants/prime'; +import MaskedTextField from 'components/form/fields/MaskedTextField/MaskedTextField'; +import { CheckboxField } from 'components/form/fields'; +import { SERVICE_ITEM_CODES } from 'constants/serviceItems'; + +const internationalShippingValidationSchema = Yup.object().shape({ + reServiceCode: Yup.string().required('Required'), + itemLength: Yup.string().required('Required'), + itemWidth: Yup.string().required('Required'), + itemHeight: Yup.string().required('Required'), + crateLength: Yup.string().required('Required'), + crateWidth: Yup.string().required('Required'), + crateHeight: Yup.string().required('Required'), + description: Yup.string().required('Required'), + reason: Yup.string().required('Required'), +}); + +const InternationalCratingForm = ({ shipment, submission }) => { + const initialValues = { + moveTaskOrderID: shipment.moveTaskOrderID, + mtoShipmentID: shipment.id, + modelType: createServiceItemModelTypes.MTOServiceItemInternationalCrating, + standaloneCrate: false, + externalCrate: false, + itemLength: '', + itemWidth: '', + itemHeight: '', + crateLength: '', + crateWidth: '', + crateHeight: '', + reason: '', + description: '', + }; + + const onSubmit = (values) => { + const { itemLength, itemWidth, itemHeight, crateLength, crateWidth, crateHeight, ...otherFields } = values; + + const body = { + item: { + length: Number.parseInt(itemLength, 10), + width: Number.parseInt(itemWidth, 10), + height: Number.parseInt(itemHeight, 10), + }, + crate: { + length: Number.parseInt(crateLength, 10), + width: Number.parseInt(crateWidth, 10), + height: Number.parseInt(crateHeight, 10), + }, + ...otherFields, + }; + submission({ body }); + }; + + return ( + + {({ values }) => { + return ( +
    + + {values.reServiceCode === SERVICE_ITEM_CODES.ICRT && ( + <> + + + + )} + + + + + + + + + + + ); + }} +
    + ); +}; + +InternationalCratingForm.propTypes = { + shipment: ShipmentShape.isRequired, + submission: PropTypes.func.isRequired, +}; + +export default InternationalCratingForm; diff --git a/src/components/PrimeUI/CreateShipmentServiceItemForm/InternationalCratingForm.test.jsx b/src/components/PrimeUI/CreateShipmentServiceItemForm/InternationalCratingForm.test.jsx new file mode 100644 index 00000000000..29b4299f3b7 --- /dev/null +++ b/src/components/PrimeUI/CreateShipmentServiceItemForm/InternationalCratingForm.test.jsx @@ -0,0 +1,88 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; + +import InternationalCratingForm from './InternationalCratingForm'; + +const moveId = '9c7b255c-2981-4bf8-839f-61c7458e2b4d'; + +const approvedMoveTaskOrder = { + moveTaskOrder: { + id: '9c7b255c-2981-4bf8-839f-61c7458e2b4d', + moveCode: 'LR4T8V', + mtoShipments: [ + { + actualPickupDate: '2020-03-17', + agents: [], + approvedDate: '2021-10-20', + createdAt: '2021-10-21', + customerRemarks: 'Please treat gently', + destinationAddress: { + city: 'Fairfield', + id: 'bfe61147-5fd7-426e-b473-54ccf77bde35', + postalCode: '94535', + state: 'CA', + streetAddress1: '987 Any Avenue', + streetAddress2: 'P.O. Box 9876', + streetAddress3: 'c/o Some Person', + }, + eTag: 'MjAyMS0xMC0xOFQxODoyNDo0MS4zNzc5Nzha', + firstAvailableDeliveryDate: null, + id: 'ce01a5b8-9b44-4511-8a8d-edb60f2a4aee', + moveTaskOrderID: '9c7b255c-2981-4bf8-839f-61c7458e2b4d', + pickupAddress: { + city: 'Beverly Hills', + id: 'cf159eca-162c-4131-84a0-795e684416a6', + postalCode: '90210', + state: 'CA', + streetAddress1: '123 Any Street', + streetAddress2: 'P.O. Box 12345', + streetAddress3: 'c/o Some Person', + }, + primeActualWeight: 2000, + primeEstimatedWeight: 1400, + primeEstimatedWeightRecordedDate: null, + requestedPickupDate: '2020-03-15', + requiredDeliveryDate: null, + scheduledPickupDate: '2020-03-16', + secondaryDeliveryAddress: { + city: null, + postalCode: null, + state: null, + streetAddress1: null, + }, + shipmentType: 'HHG', + status: 'APPROVED', + updatedAt: '2021-10-22', + mtoServiceItems: null, + reweigh: { + id: '1234', + weight: 9000, + requestedAt: '2021-10-23', + }, + }, + ], + }, +}; + +describe('InternationalCratingForm component', () => { + it.each([ + ['Service item code', 'reServiceCode'], + ['Item length (thousandths of an inch)', 'itemLength'], + ['Item width (thousandths of an inch)', 'itemWidth'], + ['Item height (thousandths of an inch)', 'itemHeight'], + ['Crate length (thousandths of an inch)', 'crateLength'], + ['Crate width (thousandths of an inch)', 'crateWidth'], + ['Crate height (thousandths of an inch)', 'crateHeight'], + ['Description', 'description'], + ['Reason', 'reason'], + ])('renders field %s in form', (labelName, inputName) => { + const shipment = approvedMoveTaskOrder.moveTaskOrder.mtoShipments[0]; + + render(); + + // shipment text values + const field = screen.getByText(labelName); + expect(field).toBeInTheDocument(); + expect(field.closest('div').nextElementSibling.name).toBe(inputName); + }); +}); diff --git a/src/components/PrimeUI/Shipment/Shipment.jsx b/src/components/PrimeUI/Shipment/Shipment.jsx index df668e0eabc..85fd9723126 100644 --- a/src/components/PrimeUI/Shipment/Shipment.jsx +++ b/src/components/PrimeUI/Shipment/Shipment.jsx @@ -78,7 +78,7 @@ const Shipment = ({ shipment, moveId, onDelete, mtoServiceItems }) => { relative="path" className="usa-button usa-button-secondary" > - Update Shipment Destination Address + Update Shipment Delivery Address )} {shipment.shipmentType === SHIPMENT_OPTIONS.PPM && @@ -219,7 +219,7 @@ const Shipment = ({ shipment, moveId, onDelete, mtoServiceItems }) => {
    -
    Destination Address:
    +
    Delivery Address:
    {formatPrimeAPIShipmentAddress(shipment.destinationAddress)}
    {shipment.destinationAddress?.id && moveId && ( @@ -230,7 +230,7 @@ const Shipment = ({ shipment, moveId, onDelete, mtoServiceItems }) => {
    -
    Second Destination Address:
    +
    Second Delivery Address:
    {formatPrimeAPIShipmentAddress(shipment.secondaryDeliveryAddress)}
    {shipment.secondaryDeliveryAddress?.id && moveId && ( @@ -241,7 +241,7 @@ const Shipment = ({ shipment, moveId, onDelete, mtoServiceItems }) => {
    -
    Third Destination Address:
    +
    Third Delivery Address:
    {formatPrimeAPIShipmentAddress(shipment.tertiaryDeliveryAddress)}
    {shipment.tertiaryDeliveryAddress?.id && moveId && ( @@ -335,15 +335,15 @@ const Shipment = ({ shipment, moveId, onDelete, mtoServiceItems }) => {
    {formatPrimeAPIShipmentAddress(shipment.ppmShipment.tertiaryPickupAddress)}
    -
    Destination Address:
    +
    Delivery Address:
    {formatPrimeAPIShipmentAddress(shipment.ppmShipment.destinationAddress)}
    -
    Second Destination Address:
    +
    Second Delivery Address:
    {formatPrimeAPIShipmentAddress(shipment.ppmShipment.secondaryDestinationAddress)}
    -
    Third Destination Address:
    +
    Third Delivery Address:
    {formatPrimeAPIShipmentAddress(shipment.ppmShipment.tertiaryDestinationAddress)}
    diff --git a/src/components/PrimeUI/Shipment/Shipment.test.jsx b/src/components/PrimeUI/Shipment/Shipment.test.jsx index ff4416a482d..7bfca7243ed 100644 --- a/src/components/PrimeUI/Shipment/Shipment.test.jsx +++ b/src/components/PrimeUI/Shipment/Shipment.test.jsx @@ -217,7 +217,7 @@ describe('Shipment details component', () => { expect(field.nextElementSibling.textContent).toContain(shipment.pickupAddress.streetAddress2); expect(field.nextElementSibling.textContent).toContain(shipment.pickupAddress.postalCode); - field = screen.getByText('Destination Address:'); + field = screen.getByText('Delivery Address:'); expect(field).toBeInTheDocument(); expect(field.nextElementSibling.textContent).toContain(shipment.destinationAddress.city); expect(field.nextElementSibling.textContent).toContain(shipment.destinationAddress.state); diff --git a/src/components/Table/SearchResultsTable.jsx b/src/components/Table/SearchResultsTable.jsx index 9e2669bd843..9369fe66f32 100644 --- a/src/components/Table/SearchResultsTable.jsx +++ b/src/components/Table/SearchResultsTable.jsx @@ -43,8 +43,8 @@ const moveSearchColumns = (moveLockFlag, handleEditProfileClick) => [ id: 'locator', isFilterable: false, }), - createHeader('DOD ID', 'dodID', { - id: 'dodID', + createHeader('DOD ID', 'edipi', { + id: 'edipi', isFilterable: false, }), createHeader('EMPLID', 'emplid', { @@ -242,8 +242,8 @@ const customerSearchColumns = ({ setCanAddOrders }) => [ isFilterable: false, }, ), - createHeader('DOD ID', 'dodID', { - id: 'dodID', + createHeader('DOD ID', 'edipi', { + id: 'edipi', isFilterable: false, }), createHeader('EMPLID', 'emplid', { @@ -383,7 +383,7 @@ const SearchResultsTable = (props) => { filtersToAdd.push({ id: 'locator', value: moveCode.trim() }); } if (dodID) { - filtersToAdd.push({ id: 'dodID', value: dodID.trim() }); + filtersToAdd.push({ id: 'edipi', value: dodID.trim() }); } if (customerName) { filtersToAdd.push({ id: 'customerName', value: customerName }); diff --git a/src/components/Table/SearchResultsTable.test.jsx b/src/components/Table/SearchResultsTable.test.jsx index 3904157bf3f..e31dc66f70f 100644 --- a/src/components/Table/SearchResultsTable.test.jsx +++ b/src/components/Table/SearchResultsTable.test.jsx @@ -52,7 +52,7 @@ const mockTableData = [ const mockCustomerTableData = [ { branch: 'MARINES', - dodID: '6585626513', + edipi: '6585626513', firstName: 'Ted', id: '8604447b-cbfc-4d59-a9a1-dec219eb2046', lastName: 'Marine', @@ -147,8 +147,8 @@ describe('SearchResultsTable', () => { expect(results).toBeInTheDocument(); const branch = screen.queryByText('Marine Corps'); expect(branch).toBeInTheDocument(); - const dodID = screen.queryByText('6585626513'); - expect(dodID).toBeInTheDocument(); + const edipi = screen.queryByText('6585626513'); + expect(edipi).toBeInTheDocument(); const name = screen.queryByText('Marine, Ted'); expect(name).toBeInTheDocument(); const email = screen.queryByText('leo_spaceman_sm@example.com'); diff --git a/src/components/form/AddressFields/AddressFields.stories.jsx b/src/components/form/AddressFields/AddressFields.stories.jsx index 61730cab606..5a5c524fecd 100644 --- a/src/components/form/AddressFields/AddressFields.stories.jsx +++ b/src/components/form/AddressFields/AddressFields.stories.jsx @@ -48,7 +48,7 @@ export const CurrentResidentialAddress = () => ( > {() => (
    - + )} @@ -69,7 +69,7 @@ export const CurrentResidentialAddressWithInitialValues = () => ( > {() => (
    - + )} @@ -91,7 +91,7 @@ export const CurrentResidentialAddressWithCustomValidators = () => ( {() => (
    (value === 'Nowhere' ? 'No one lives there' : ''), @@ -119,7 +119,7 @@ export const InsideSectionWrapper = () => ( {() => ( - + )} diff --git a/src/constants/MoveHistory/Database/AddressTypes.js b/src/constants/MoveHistory/Database/AddressTypes.js index 54aa03c2e99..ce350f66c8e 100644 --- a/src/constants/MoveHistory/Database/AddressTypes.js +++ b/src/constants/MoveHistory/Database/AddressTypes.js @@ -4,5 +4,7 @@ export default { pickupAddress: 'pickup_address', residentialAddress: 'residential_address', secondaryDestinationAddress: 'secondary_destination_address', + tertiaryDestinationAddress: 'tertiary_destination_address', secondaryPickupAddress: 'secondary_pickup_address', + tertiaryPickupAddress: 'tertiary_pickup_address', }; diff --git a/src/constants/MoveHistory/Database/FieldMappings.js b/src/constants/MoveHistory/Database/FieldMappings.js index 29dd438a8e3..3bd77f2696f 100644 --- a/src/constants/MoveHistory/Database/FieldMappings.js +++ b/src/constants/MoveHistory/Database/FieldMappings.js @@ -42,10 +42,12 @@ export default { requested_pickup_date: 'Requested pickup date', grade: 'Pay grade', shipment_type: 'Shipment type', - pickup_address: 'Origin address', - secondary_pickup_address: 'Secondary origin address', - destination_address: 'Destination address', - secondary_destination_address: 'Secondary destination address', + pickup_address: 'Pickup Address', + secondary_pickup_address: 'Second Pickup Address', + tertiary_pickup_address: 'Third Pickup Address', + destination_address: 'Delivery Address', + secondary_destination_address: 'Second Delivery Address', + tertiary_destination_address: 'Third Delivery Address', receiving_agent: 'Receiving agent', releasing_agent: 'Releasing agent', tio_remarks: 'Max billable weight remark', @@ -58,8 +60,8 @@ export default { sit_departure_date: 'SIT departure date', sit_entry_date: 'SIT entry date', sit_postal_code: 'SIT postal code', - sit_destination_address_final: 'Final SIT delivery address', - sit_destination_address_initial: 'Initial SIT delivery address', + sit_destination_address_final: 'Final SIT Delivery Address', + sit_destination_address_initial: 'Initial SIT Delivery Address', sit_requested_delivery: 'Requested SIT delivery date ', contractor_remarks: 'Contractor remarks', office_remarks: 'Office remarks', @@ -82,8 +84,8 @@ export default { secondary_email: 'Secondary email', secondary_telephone: 'Secondary telephone', telephone: 'Telephone', - residential_address: 'Current address', - backup_address: 'Backup address', + residential_address: 'Pickup Address', + backup_address: 'Backup Address', current_duty_location_name: 'Current duty location name', financial_review_remarks: 'Financial review remarks', backup_contact_name: 'Backup contact name', diff --git a/src/constants/MoveHistory/EventTemplates/CancelMove/CancelMove.jsx b/src/constants/MoveHistory/EventTemplates/CancelMove/CancelMove.jsx new file mode 100644 index 00000000000..cfd23ef6f2d --- /dev/null +++ b/src/constants/MoveHistory/EventTemplates/CancelMove/CancelMove.jsx @@ -0,0 +1,13 @@ +import React from 'react'; + +import a from 'constants/MoveHistory/Database/Actions'; +import t from 'constants/MoveHistory/Database/Tables'; +import o from 'constants/MoveHistory/UIDisplay/Operations'; + +export default { + action: a.UPDATE, + eventName: o.cancelMove, + tableName: t.moves, + getEventNameDisplay: () => 'Canceled move', + getDetails: () => <>Move canceled, +}; diff --git a/src/constants/MoveHistory/EventTemplates/CancelMove/CancelMove.test.jsx b/src/constants/MoveHistory/EventTemplates/CancelMove/CancelMove.test.jsx new file mode 100644 index 00000000000..41241dadf38 --- /dev/null +++ b/src/constants/MoveHistory/EventTemplates/CancelMove/CancelMove.test.jsx @@ -0,0 +1,24 @@ +import { screen, render } from '@testing-library/react'; + +import getTemplate from 'constants/MoveHistory/TemplateManager'; +import e from 'constants/MoveHistory/EventTemplates/CancelMove/CancelMove'; + +describe('when given a Move cancellation history record', () => { + const historyRecord = { + action: 'UPDATE', + eventName: 'cancelMove', + tableName: 'moves', + }; + + it('correctly matches the Move cancellation event to the proper template', () => { + const template = getTemplate(historyRecord); + expect(template).toMatchObject(e); + }); + + it('displays the correct value in the details column', () => { + const template = getTemplate(historyRecord); + + render(template.getDetails(historyRecord)); + expect(screen.getByText('Move canceled')); + }); +}); diff --git a/src/constants/MoveHistory/EventTemplates/CancelMove/CancelMoveMTOShipments.jsx b/src/constants/MoveHistory/EventTemplates/CancelMove/CancelMoveMTOShipments.jsx new file mode 100644 index 00000000000..2a71deef895 --- /dev/null +++ b/src/constants/MoveHistory/EventTemplates/CancelMove/CancelMoveMTOShipments.jsx @@ -0,0 +1,27 @@ +import React from 'react'; + +import a from 'constants/MoveHistory/Database/Actions'; +import t from 'constants/MoveHistory/Database/Tables'; +import o from 'constants/MoveHistory/UIDisplay/Operations'; +import LabeledDetails from 'pages/Office/MoveHistory/LabeledDetails'; +import { getMtoShipmentLabel } from 'utils/formatMtoShipment'; + +const formatChangedValues = (historyRecord) => { + const { changedValues } = historyRecord; + const newChangedValues = { + ...changedValues, + ...getMtoShipmentLabel(historyRecord), + }; + + return { ...historyRecord, changedValues: newChangedValues }; +}; + +export default { + action: a.UPDATE, + eventName: o.cancelMove, + tableName: t.mto_shipments, + getEventNameDisplay: () => 'Canceled shipment', + getDetails: (historyRecord) => { + return ; + }, +}; diff --git a/src/constants/MoveHistory/EventTemplates/CancelMove/CancelMoveMTOShipments.test.jsx b/src/constants/MoveHistory/EventTemplates/CancelMove/CancelMoveMTOShipments.test.jsx new file mode 100644 index 00000000000..c66f1e9bfb6 --- /dev/null +++ b/src/constants/MoveHistory/EventTemplates/CancelMove/CancelMoveMTOShipments.test.jsx @@ -0,0 +1,36 @@ +import { screen, render } from '@testing-library/react'; + +import getTemplate from 'constants/MoveHistory/TemplateManager'; +import e from 'constants/MoveHistory/EventTemplates/CancelMove/CancelMoveMTOShipments'; + +describe('when given a Move cancellation shipment history record', () => { + const historyRecord = { + action: 'UPDATE', + changedValues: { + status: 'CANCELED', + }, + context: [ + { + shipment_id_abbr: '95db3', + shipment_locator: '6K9PYC-01', + shipment_type: 'HHG', + }, + ], + eventName: 'cancelMove', + tableName: 'mto_shipments', + }; + + it('correctly matches the Move cancellation shipment event to the proper template', () => { + const template = getTemplate(historyRecord); + expect(template).toMatchObject(e); + }); + + it('displays the correct value in the details column', () => { + const template = getTemplate(historyRecord); + + render(template.getDetails(historyRecord)); + expect(screen.getByText('HHG shipment #6K9PYC-01')); + expect(screen.getByText('Status')); + expect(screen.getByText(': CANCELED')); + }); +}); diff --git a/src/constants/MoveHistory/EventTemplates/CancelMove/CancelMovePPMShipments.jsx b/src/constants/MoveHistory/EventTemplates/CancelMove/CancelMovePPMShipments.jsx new file mode 100644 index 00000000000..76cef4f5e27 --- /dev/null +++ b/src/constants/MoveHistory/EventTemplates/CancelMove/CancelMovePPMShipments.jsx @@ -0,0 +1,27 @@ +import React from 'react'; + +import a from 'constants/MoveHistory/Database/Actions'; +import t from 'constants/MoveHistory/Database/Tables'; +import o from 'constants/MoveHistory/UIDisplay/Operations'; +import LabeledDetails from 'pages/Office/MoveHistory/LabeledDetails'; +import { getMtoShipmentLabel } from 'utils/formatMtoShipment'; + +const formatChangedValues = (historyRecord) => { + const { changedValues } = historyRecord; + const newChangedValues = { + ...changedValues, + ...getMtoShipmentLabel(historyRecord), + }; + + return { ...historyRecord, changedValues: newChangedValues }; +}; + +export default { + action: a.UPDATE, + eventName: o.cancelMove, + tableName: t.ppm_shipments, + getEventNameDisplay: () => 'Canceled shipment', + getDetails: (historyRecord) => { + return ; + }, +}; diff --git a/src/constants/MoveHistory/EventTemplates/CancelMove/CancelMovePPMShipments.test.jsx b/src/constants/MoveHistory/EventTemplates/CancelMove/CancelMovePPMShipments.test.jsx new file mode 100644 index 00000000000..b64b82ce71d --- /dev/null +++ b/src/constants/MoveHistory/EventTemplates/CancelMove/CancelMovePPMShipments.test.jsx @@ -0,0 +1,36 @@ +import { screen, render } from '@testing-library/react'; + +import getTemplate from 'constants/MoveHistory/TemplateManager'; +import e from 'constants/MoveHistory/EventTemplates/CancelMove/CancelMovePPMShipments'; + +describe('when given a Move cancellation PPM shipment history record', () => { + const historyRecord = { + action: 'UPDATE', + changedValues: { + status: 'CANCELED', + }, + context: [ + { + shipment_id_abbr: '87db6', + shipment_locator: '6K9PYZ-01', + shipment_type: 'PPM', + }, + ], + eventName: 'cancelMove', + tableName: 'ppm_shipments', + }; + + it('correctly matches the Move cancellation PPM shipment event to the proper template', () => { + const template = getTemplate(historyRecord); + expect(template).toMatchObject(e); + }); + + it('displays the correct value in the details column', () => { + const template = getTemplate(historyRecord); + + render(template.getDetails(historyRecord)); + expect(screen.getByText('PPM shipment #6K9PYZ-01')); + expect(screen.getByText('Status')); + expect(screen.getByText(': CANCELED')); + }); +}); diff --git a/src/constants/MoveHistory/EventTemplates/MoveCanceler/MoveCanceler.jsx b/src/constants/MoveHistory/EventTemplates/MoveCanceler/MoveCanceler.jsx new file mode 100644 index 00000000000..4cc3c592cb3 --- /dev/null +++ b/src/constants/MoveHistory/EventTemplates/MoveCanceler/MoveCanceler.jsx @@ -0,0 +1,13 @@ +import React from 'react'; + +import a from 'constants/MoveHistory/Database/Actions'; +import t from 'constants/MoveHistory/Database/Tables'; +import o from 'constants/MoveHistory/UIDisplay/Operations'; + +export default { + action: a.UPDATE, + eventName: o.moveCanceler, + tableName: t.moves, + getEventNameDisplay: () => 'Canceled move', + getDetails: () => <>Move canceled, +}; diff --git a/src/constants/MoveHistory/EventTemplates/MoveCanceler/MoveCanceler.test.jsx b/src/constants/MoveHistory/EventTemplates/MoveCanceler/MoveCanceler.test.jsx new file mode 100644 index 00000000000..9ed1b134a2f --- /dev/null +++ b/src/constants/MoveHistory/EventTemplates/MoveCanceler/MoveCanceler.test.jsx @@ -0,0 +1,24 @@ +import { screen, render } from '@testing-library/react'; + +import getTemplate from 'constants/MoveHistory/TemplateManager'; +import e from 'constants/MoveHistory/EventTemplates/MoveCanceler/MoveCanceler'; + +describe('when given a Move cancellation history record', () => { + const historyRecord = { + action: 'UPDATE', + eventName: 'moveCanceler', + tableName: 'moves', + }; + + it('correctly matches the Move cancellation event to the proper template', () => { + const template = getTemplate(historyRecord); + expect(template).toMatchObject(e); + }); + + it('displays the correct value in the details column', () => { + const template = getTemplate(historyRecord); + + render(template.getDetails(historyRecord)); + expect(screen.getByText('Move canceled')); + }); +}); diff --git a/src/constants/MoveHistory/EventTemplates/MoveCanceler/MoveCancelerMTOShipments.jsx b/src/constants/MoveHistory/EventTemplates/MoveCanceler/MoveCancelerMTOShipments.jsx new file mode 100644 index 00000000000..c7645a7b0d6 --- /dev/null +++ b/src/constants/MoveHistory/EventTemplates/MoveCanceler/MoveCancelerMTOShipments.jsx @@ -0,0 +1,27 @@ +import React from 'react'; + +import a from 'constants/MoveHistory/Database/Actions'; +import t from 'constants/MoveHistory/Database/Tables'; +import o from 'constants/MoveHistory/UIDisplay/Operations'; +import LabeledDetails from 'pages/Office/MoveHistory/LabeledDetails'; +import { getMtoShipmentLabel } from 'utils/formatMtoShipment'; + +const formatChangedValues = (historyRecord) => { + const { changedValues } = historyRecord; + const newChangedValues = { + ...changedValues, + ...getMtoShipmentLabel(historyRecord), + }; + + return { ...historyRecord, changedValues: newChangedValues }; +}; + +export default { + action: a.UPDATE, + eventName: o.moveCanceler, + tableName: t.mto_shipments, + getEventNameDisplay: () => 'Canceled shipment', + getDetails: (historyRecord) => { + return ; + }, +}; diff --git a/src/constants/MoveHistory/EventTemplates/MoveCanceler/MoveCancelerMTOShipments.test.jsx b/src/constants/MoveHistory/EventTemplates/MoveCanceler/MoveCancelerMTOShipments.test.jsx new file mode 100644 index 00000000000..f1884a60c7b --- /dev/null +++ b/src/constants/MoveHistory/EventTemplates/MoveCanceler/MoveCancelerMTOShipments.test.jsx @@ -0,0 +1,36 @@ +import { screen, render } from '@testing-library/react'; + +import getTemplate from 'constants/MoveHistory/TemplateManager'; +import e from 'constants/MoveHistory/EventTemplates/MoveCanceler/MoveCancelerMTOShipments'; + +describe('when given a Move cancellation shipment history record', () => { + const historyRecord = { + action: 'UPDATE', + changedValues: { + status: 'CANCELED', + }, + context: [ + { + shipment_id_abbr: '95db3', + shipment_locator: '6K9PYC-01', + shipment_type: 'HHG', + }, + ], + eventName: 'moveCanceler', + tableName: 'mto_shipments', + }; + + it('correctly matches the Move cancellation shipment event to the proper template', () => { + const template = getTemplate(historyRecord); + expect(template).toMatchObject(e); + }); + + it('displays the correct value in the details column', () => { + const template = getTemplate(historyRecord); + + render(template.getDetails(historyRecord)); + expect(screen.getByText('HHG shipment #6K9PYC-01')); + expect(screen.getByText('Status')); + expect(screen.getByText(': CANCELED')); + }); +}); diff --git a/src/constants/MoveHistory/EventTemplates/MoveCanceler/MoveCancelerPPMShipments.jsx b/src/constants/MoveHistory/EventTemplates/MoveCanceler/MoveCancelerPPMShipments.jsx new file mode 100644 index 00000000000..71ba6e48db9 --- /dev/null +++ b/src/constants/MoveHistory/EventTemplates/MoveCanceler/MoveCancelerPPMShipments.jsx @@ -0,0 +1,27 @@ +import React from 'react'; + +import a from 'constants/MoveHistory/Database/Actions'; +import t from 'constants/MoveHistory/Database/Tables'; +import o from 'constants/MoveHistory/UIDisplay/Operations'; +import LabeledDetails from 'pages/Office/MoveHistory/LabeledDetails'; +import { getMtoShipmentLabel } from 'utils/formatMtoShipment'; + +const formatChangedValues = (historyRecord) => { + const { changedValues } = historyRecord; + const newChangedValues = { + ...changedValues, + ...getMtoShipmentLabel(historyRecord), + }; + + return { ...historyRecord, changedValues: newChangedValues }; +}; + +export default { + action: a.UPDATE, + eventName: o.moveCanceler, + tableName: t.ppm_shipments, + getEventNameDisplay: () => 'Canceled shipment', + getDetails: (historyRecord) => { + return ; + }, +}; diff --git a/src/constants/MoveHistory/EventTemplates/MoveCanceler/MoveCancelerPPMShipments.test.jsx b/src/constants/MoveHistory/EventTemplates/MoveCanceler/MoveCancelerPPMShipments.test.jsx new file mode 100644 index 00000000000..178049f847f --- /dev/null +++ b/src/constants/MoveHistory/EventTemplates/MoveCanceler/MoveCancelerPPMShipments.test.jsx @@ -0,0 +1,36 @@ +import { screen, render } from '@testing-library/react'; + +import getTemplate from 'constants/MoveHistory/TemplateManager'; +import e from 'constants/MoveHistory/EventTemplates/MoveCanceler/MoveCancelerPPMShipments'; + +describe('when given a Move cancellation PPM shipment history record', () => { + const historyRecord = { + action: 'UPDATE', + changedValues: { + status: 'CANCELED', + }, + context: [ + { + shipment_id_abbr: '87db6', + shipment_locator: '6K9PYZ-01', + shipment_type: 'PPM', + }, + ], + eventName: 'moveCanceler', + tableName: 'ppm_shipments', + }; + + it('correctly matches the Move cancellation PPM shipment event to the proper template', () => { + const template = getTemplate(historyRecord); + expect(template).toMatchObject(e); + }); + + it('displays the correct value in the details column', () => { + const template = getTemplate(historyRecord); + + render(template.getDetails(historyRecord)); + expect(screen.getByText('PPM shipment #6K9PYZ-01')); + expect(screen.getByText('Status')); + expect(screen.getByText(': CANCELED')); + }); +}); diff --git a/src/constants/MoveHistory/EventTemplates/UpdateAddress/createAddress.test.jsx b/src/constants/MoveHistory/EventTemplates/UpdateAddress/createAddress.test.jsx index be640c3d4aa..7c0d5806562 100644 --- a/src/constants/MoveHistory/EventTemplates/UpdateAddress/createAddress.test.jsx +++ b/src/constants/MoveHistory/EventTemplates/UpdateAddress/createAddress.test.jsx @@ -5,12 +5,14 @@ import createAddress from 'constants/MoveHistory/EventTemplates/UpdateAddress/cr import ADDRESS_TYPE from 'constants/MoveHistory/Database/AddressTypes'; const LABEL = { - backupMailingAddress: 'Backup address', - destinationAddress: 'Destination address', - pickupAddress: 'Origin address', - residentialAddress: 'Current address', - secondaryDestinationAddress: 'Secondary destination address', - secondaryPickupAddress: 'Secondary origin address', + backupMailingAddress: 'Backup Address', + destinationAddress: 'Delivery Address', + pickupAddress: 'Pickup Address', + residentialAddress: 'Pickup Address', + secondaryDestinationAddress: 'Second Delivery Address', + secondaryPickupAddress: 'Second Pickup Address', + tertiaryDestinationAddress: 'Third Delivery Address', + tertiaryPickupAddress: 'Third Pickup Address', }; describe('when given an insert with address table history record', () => { diff --git a/src/constants/MoveHistory/EventTemplates/UpdateAddress/updateAddress.test.jsx b/src/constants/MoveHistory/EventTemplates/UpdateAddress/updateAddress.test.jsx index 64a82a0f625..290a76e097f 100644 --- a/src/constants/MoveHistory/EventTemplates/UpdateAddress/updateAddress.test.jsx +++ b/src/constants/MoveHistory/EventTemplates/UpdateAddress/updateAddress.test.jsx @@ -7,12 +7,14 @@ import { shipmentTypes } from 'constants/shipments'; import { formatMoveHistoryFullAddress } from 'utils/formatters'; const LABEL = { - backupMailingAddress: 'Backup address', - destinationAddress: 'Destination address', - pickupAddress: 'Origin address', - residentialAddress: 'Current address', - secondaryDestinationAddress: 'Secondary destination address', - secondaryPickupAddress: 'Secondary origin address', + backupMailingAddress: 'Backup Address', + destinationAddress: 'Delivery Address', + pickupAddress: 'Pickup Address', + residentialAddress: 'Pickup Address', + secondaryDestinationAddress: 'Second Delivery Address', + secondaryPickupAddress: 'Second Pickup Address', + tertiaryDestinationAddress: 'Third Delivery Address', + tertiaryPickupAddress: 'Third Pickup Address', }; describe('when given a Update basic service item address history record', () => { @@ -112,7 +114,7 @@ describe('when given a Update basic service item address history record', () => }; const updatedTemplate = getTemplate(updatedHistoryRecord); - it.each([['Current address', ': 1234 New Ave, los angeles, CA 90210']])( + it.each([['Pickup Address', ': 1234 New Ave, los angeles, CA 90210']])( 'Label `%s` should have the full address `%s`', async (label, value) => { render(updatedTemplate.getDetails(updatedHistoryRecord)); diff --git a/src/constants/MoveHistory/EventTemplates/index.js b/src/constants/MoveHistory/EventTemplates/index.js index 23320f181db..42ac00d51c7 100644 --- a/src/constants/MoveHistory/EventTemplates/index.js +++ b/src/constants/MoveHistory/EventTemplates/index.js @@ -103,3 +103,9 @@ export { default as createOrderCreateMoves } from './CreateOrder/createOrderCrea export { default as updateOrderUpdateMove } from './UpdateOrder/updateOrderUpdateMove'; export { default as addAppealToViolation } from './AddAppeal/addAppealToViolation'; export { default as addAppealToSeriousIncident } from './AddAppeal/addAppealToSeriousIncident'; +export { default as moveCanceler } from './MoveCanceler/MoveCanceler'; +export { default as moveCancelerMTOShipments } from './MoveCanceler/MoveCancelerMTOShipments'; +export { default as moveCancelerPPMShipments } from './MoveCanceler/MoveCancelerPPMShipments'; +export { default as cancelMove } from './CancelMove/CancelMove'; +export { default as cancelMoveMTOShipments } from './CancelMove/CancelMoveMTOShipments'; +export { default as cancelMovePPMShipments } from './CancelMove/CancelMovePPMShipments'; diff --git a/src/constants/MoveHistory/UIDisplay/Operations.js b/src/constants/MoveHistory/UIDisplay/Operations.js index da84b19e774..8ca4a3f5cd0 100644 --- a/src/constants/MoveHistory/UIDisplay/Operations.js +++ b/src/constants/MoveHistory/UIDisplay/Operations.js @@ -64,4 +64,5 @@ export default { moveCanceler: 'moveCanceler', // ghc.yaml addAppealToViolation: 'addAppealToViolation', // ghc.yaml addAppealToSeriousIncident: 'addAppealToSeriousIncident', // ghc.yaml + cancelMove: 'cancelMove', // internal.yaml }; diff --git a/src/constants/prime.js b/src/constants/prime.js index f78e76414cf..098dfe54dc7 100644 --- a/src/constants/prime.js +++ b/src/constants/prime.js @@ -6,6 +6,7 @@ export const createServiceItemModelTypes = { MTOServiceItemDestSIT: 'MTOServiceItemDestSIT', MTOServiceItemShuttle: 'MTOServiceItemShuttle', MTOServiceItemDomesticCrating: 'MTOServiceItemDomesticCrating', + MTOServiceItemInternationalCrating: 'MTOServiceItemInternationalCrating', }; export const shuttleServiceItemCodeOptions = [ @@ -18,4 +19,9 @@ export const domesticCratingServiceItemCodeOptions = [ { value: serviceItemCodes.DUCRT, key: SERVICE_ITEM_CODES.DUCRT }, ]; +export const internationalCratingServiceItemCodeOptions = [ + { value: serviceItemCodes.ICRT, key: SERVICE_ITEM_CODES.ICRT }, + { value: serviceItemCodes.IUCRT, key: SERVICE_ITEM_CODES.IUCRT }, +]; + export default createServiceItemModelTypes; diff --git a/src/constants/serviceItems.js b/src/constants/serviceItems.js index 07069fbe280..6964104aa57 100644 --- a/src/constants/serviceItems.js +++ b/src/constants/serviceItems.js @@ -140,6 +140,8 @@ const SERVICE_ITEM_CODES = { DDSHUT: 'DDSHUT', DCRT: 'DCRT', DUCRT: 'DUCRT', + ICRT: 'ICRT', + IUCRT: 'IUCRT', MS: 'MS', DOSFSC: 'DOSFSC', DDSFSC: 'DDSFSC', diff --git a/src/constants/shipments.js b/src/constants/shipments.js index 06ae5f39424..21b9c25b944 100644 --- a/src/constants/shipments.js +++ b/src/constants/shipments.js @@ -49,6 +49,16 @@ export const ppmShipmentStatuses = { CANCELED: 'CANCELED', }; +export const ppmShipmentStatusLabels = { + [ppmShipmentStatuses.CANCELED]: 'Canceled', + [ppmShipmentStatuses.DRAFT]: 'Draft', + [ppmShipmentStatuses.NEEDS_CLOSEOUT]: 'Needs Closeout', + [ppmShipmentStatuses.NEEDS_ADVANCE_APPROVAL]: 'Needs Advance Approval', + [ppmShipmentStatuses.SUBMITTED]: 'Submitted', + [ppmShipmentStatuses.WAITING_ON_CUSTOMER]: 'Waiting on customer', + [ppmShipmentStatuses.CLOSEOUT_COMPLETE]: 'packet ready for download', +}; + export const boatShipmentTypes = { HAUL_AWAY: 'HAUL_AWAY', TOW_AWAY: 'TOW_AWAY', diff --git a/src/containers/Headers/OfficeLoggedInHeader.jsx b/src/containers/Headers/OfficeLoggedInHeader.jsx index 09d07002920..93965f8ce5f 100644 --- a/src/containers/Headers/OfficeLoggedInHeader.jsx +++ b/src/containers/Headers/OfficeLoggedInHeader.jsx @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; -import { Link, useNavigate } from 'react-router-dom'; +import { Link, useNavigate, useLocation } from 'react-router-dom'; import classnames from 'classnames'; import GblocSwitcher from 'components/Office/GblocSwitcher/GblocSwitcher'; @@ -12,6 +12,7 @@ import { logOut as logOutAction } from 'store/auth/actions'; import { OfficeUserInfoShape } from 'types/index'; import { selectLoggedInUser } from 'store/entities/selectors'; import { roleTypes } from 'constants/userRoles'; +import { checkForLockedMovesAndUnlock } from 'services/ghcApi'; const OfficeLoggedInHeader = ({ officeUser, activeRole, logOut }) => { const navigate = useNavigate(); @@ -33,10 +34,19 @@ const OfficeLoggedInHeader = ({ officeUser, activeRole, logOut }) => { }; let queueText = ''; + const location = useLocation(); + const validUnlockingOfficers = [ + roleTypes.QAE, + roleTypes.CUSTOMER_SERVICE_REPRESENTATIVE, + roleTypes.GSR, + roleTypes.HQ, + ]; if (activeRole === roleTypes.TOO) { queueText = 'moves'; } else if (activeRole === roleTypes.TIO) { queueText = 'payment requests'; + } else if (validUnlockingOfficers.includes(activeRole) && location.pathname === '/') { + checkForLockedMovesAndUnlock('move', officeUser.id); } return ( diff --git a/src/content/serviceItems.js b/src/content/serviceItems.js index a41717f6962..cf9e614427c 100644 --- a/src/content/serviceItems.js +++ b/src/content/serviceItems.js @@ -26,7 +26,6 @@ export const serviceItemCodes = { ICOLH: 'International C->O shipping & LH', ICOUB: 'International C->O UB', ICRT: 'International crating', - ICRTSA: 'International crating - standalone', IDASIT: "International destination add'l day SIT", IDDSIT: 'International destination SIT delivery', IDFSIT: 'International destination 1st day SIT', diff --git a/src/hooks/custom.js b/src/hooks/custom.js index ba2b8132f35..48c52c5413f 100644 --- a/src/hooks/custom.js +++ b/src/hooks/custom.js @@ -32,9 +32,9 @@ const addressesMatch = (address1, address2) => { }; // This allows us to take all shipments and identify the next one in the diversion chain easily -// A child diversion's pickup address is the destination address of the parent +// A child diversion's pickup address is the delivery address of the parent const findChildShipmentByAddress = (currentShipment, allShipments) => { - // Find a shipment whose pickup address matches the current shipment's destination address + // Find a shipment whose pickup address matches the current shipment's delivery address return allShipments.find( (shipment) => addressesMatch(shipment.pickupAddress, currentShipment.destinationAddress) && shipment.diversion, ); @@ -90,7 +90,7 @@ const getLowestShipmentNetWeight = (shipments) => { * This function calculates the total Billable Weight of the move, * by adding up all of the calculatedBillableWeight fields of all shipments with the required statuses. * It has unique calculations to also only count the lowest weight from a diverted shipment "Chain". - * It is chained by a shipment having its diversion parameter set to true and the destination address + * It is chained by a shipment having its diversion parameter set to true and the delivery address * of the parent shipment matching the pickup address of the child shipment. * * This function does **NOT** include PPM net weights in the calculation. diff --git a/src/hooks/queries.js b/src/hooks/queries.js index de2aab16125..6167cf459fd 100644 --- a/src/hooks/queries.js +++ b/src/hooks/queries.js @@ -148,7 +148,7 @@ export const useTXOMoveInfoQueries = (moveCode) => { }, ); const customerData = customer && Object.values(customer)[0]; - const { isLoading, isError, isSuccess } = getQueriesStatus([moveQuery, orderQuery, customerQuery]); + const { isLoading, isError, isSuccess, errors } = getQueriesStatus([moveQuery, orderQuery, customerQuery]); return { move, @@ -157,6 +157,7 @@ export const useTXOMoveInfoQueries = (moveCode) => { isLoading, isError, isSuccess, + errors, }; }; @@ -438,7 +439,7 @@ export const useMoveTaskOrderQueries = (moveCode) => { export const useGetDocumentQuery = (documentId) => { const staleTime = 15 * 60000; // 15 * 60000 milliseconds = 15 mins const cacheTime = staleTime; - const { data: { documents, uploads } = {}, ...documentsQuery } = useQuery( + const { data: { documents, upload } = {}, ...documentsQuery } = useQuery( [ORDERS_DOCUMENTS, documentId], ({ queryKey }) => getDocument(...queryKey), { @@ -453,7 +454,7 @@ export const useGetDocumentQuery = (documentId) => { return { documents, - uploads, + upload, isLoading, isError, isSuccess, @@ -845,6 +846,8 @@ export const useMoveDetailsQueries = (moveCode) => { const order = Object.values(orders || {})?.[0]; + const { upload: orderDocuments, ...documentQuery } = useGetDocumentQuery(order.uploaded_order_id); + const { data: mtoShipments, ...mtoShipmentQuery } = useQuery({ queryKey: [MTO_SHIPMENTS, moveId, false], queryFn: ({ queryKey }) => getMTOShipments(...queryKey), @@ -874,9 +877,10 @@ export const useMoveDetailsQueries = (moveCode) => { options: { enabled: !!moveId }, }); - const { isLoading, isError, isSuccess } = getQueriesStatus([ + const { isLoading, isError, isSuccess, errors } = getQueriesStatus([ moveQuery, orderQuery, + documentQuery, customerQuery, mtoShipmentQuery, mtoServiceItemQuery, @@ -886,6 +890,7 @@ export const useMoveDetailsQueries = (moveCode) => { return { move, order, + orderDocuments, customerData, closeoutOffice, mtoShipments, @@ -893,6 +898,7 @@ export const useMoveDetailsQueries = (moveCode) => { isLoading, isError, isSuccess, + errors, }; }; @@ -922,12 +928,13 @@ export const usePrimeSimulatorGetMove = (moveCode) => { ({ queryKey }) => getPrimeSimulatorMove(...queryKey), ); - const { isLoading, isError, isSuccess } = getQueriesStatus([primeSimulatorGetMoveQuery]); + const { isLoading, isError, isSuccess, errors } = getQueriesStatus([primeSimulatorGetMoveQuery]); return { moveTaskOrder, isLoading, isError, isSuccess, + errors, }; }; diff --git a/src/hooks/queries.test.jsx b/src/hooks/queries.test.jsx index 7e3ac191391..8564d749703 100644 --- a/src/hooks/queries.test.jsx +++ b/src/hooks/queries.test.jsx @@ -201,11 +201,11 @@ jest.mock('services/ghcApi', () => ({ documents: { [id]: { id, - uploads: [`${id}0`], + uploads: [`${id}`], }, }, upload: { - id: `${id}0`, + id: `${id}`, }, }), getMovesQueue: () => @@ -331,6 +331,8 @@ describe('useTXOMoveInfoQueries', () => { isLoading: true, isError: false, isSuccess: false, + errors: [], + move: undefined, }); await waitFor(() => result.current.isSuccess); @@ -356,6 +358,7 @@ describe('useTXOMoveInfoQueries', () => { isLoading: false, isError: false, isSuccess: true, + errors: [], move: { id: '1234', ordersId: '4321', @@ -471,42 +474,7 @@ describe('useMoveDetailsQueries', () => { const moveCode = 'ABCDEF'; const { result, waitFor } = renderHook(() => useMoveDetailsQueries(moveCode), { wrapper }); - expect(result.current).toEqual({ - move: { - id: '1234', - ordersId: '4321', - moveCode: 'ABCDEF', - }, - closeoutOffice: undefined, - customerData: { - id: '2468', - last_name: 'Kerry', - first_name: 'Smith', - dodID: '999999999', - agency: 'NAVY', - }, - order: { - id: '4321', - customerID: '2468', - customer: { id: '2468', last_name: 'Kerry', first_name: 'Smith', dodID: '999999999' }, - uploaded_order_id: '2', - uploadedAmendedOrderID: '3', - departmentIndicator: 'Navy', - grade: 'E-6', - originDutyLocation: { - name: 'JBSA Lackland', - }, - destinationDutyLocation: { - name: 'JB Lewis-McChord', - }, - report_by_date: '2018-08-01', - }, - mtoShipments: undefined, - mtoServiceItems: undefined, - isLoading: true, - isError: false, - isSuccess: false, - }); + expect(result.current.isLoading).toEqual(true); await waitFor(() => result.current.isSuccess); @@ -540,6 +508,9 @@ describe('useMoveDetailsQueries', () => { }, report_by_date: '2018-08-01', }, + orderDocuments: { + id: '2', + }, mtoShipments: [ { id: 'a1', @@ -620,6 +591,7 @@ describe('useMoveDetailsQueries', () => { isLoading: false, isError: false, isSuccess: true, + errors: [], }); }); }); @@ -627,9 +599,9 @@ describe('useMoveDetailsQueries', () => { describe('useMoveTaskOrderQueries', () => { it('loads data', async () => { const moveId = 'ABCDEF'; - const { result, waitForNextUpdate } = renderHook(() => useMoveTaskOrderQueries(moveId), { wrapper }); + const { result, waitFor } = renderHook(() => useMoveTaskOrderQueries(moveId), { wrapper }); - await waitForNextUpdate(); + await waitFor(() => result.current.isSuccess); expect(result.current).toEqual({ orders: { @@ -834,11 +806,11 @@ describe('useOrdersDocumentQueries', () => { it('loads data', async () => { const testLocatorId = 'ABCDEF'; - const { result, waitForNextUpdate } = renderHook(() => useOrdersDocumentQueries(testLocatorId), { + const { result, waitFor } = renderHook(() => useOrdersDocumentQueries(testLocatorId), { wrapper, }); - await waitForNextUpdate(); + await waitFor(() => result.current.isSuccess); expect(result.current).toEqual({ move: { id: '1234', ordersId: '4321', moveCode: testLocatorId }, @@ -863,21 +835,21 @@ describe('useOrdersDocumentQueries', () => { documents: { 2: { id: '2', - uploads: ['20'], + uploads: ['2'], }, }, upload: { - id: '20', + id: '2', }, amendedDocuments: { 3: { id: '3', - uploads: ['30'], + uploads: ['3'], }, }, amendedOrderDocumentId: '3', amendedUpload: { - id: '30', + id: '3', }, isLoading: false, isError: false, @@ -888,12 +860,12 @@ describe('useOrdersDocumentQueries', () => { describe('useMovesQueueQueries', () => { it('loads data', async () => { - const { result, waitForNextUpdate } = renderHook( + const { result, waitFor } = renderHook( () => useMovesQueueQueries({ filters: [], currentPage: 1, currentPageSize: 100 }), { wrapper }, ); - await waitForNextUpdate(); + await waitFor(() => result.current.isSuccess); expect(result.current).toEqual({ queueResult: { @@ -918,12 +890,12 @@ describe('useMovesQueueQueries', () => { describe('usePaymentRequestsQueueQueries', () => { it('loads data', async () => { - const { result, waitForNextUpdate } = renderHook( + const { result, waitFor } = renderHook( () => usePaymentRequestQueueQueries({ filters: [], currentPage: 1, currentPageSize: 100 }), { wrapper }, ); - await waitForNextUpdate(); + await waitFor(() => result.current.isSuccess); expect(result.current).toEqual({ queueResult: { diff --git a/src/pages/Maintenance/MaintenancePage.jsx b/src/pages/Maintenance/MaintenancePage.jsx new file mode 100644 index 00000000000..180ee32e2df --- /dev/null +++ b/src/pages/Maintenance/MaintenancePage.jsx @@ -0,0 +1,21 @@ +import React from 'react'; + +import styles from './MaintenancePage.module.scss'; + +import CUIHeader from 'components/CUIHeader/CUIHeader'; +import MilMoveHeader from 'components/MilMoveHeader'; + +const MaintenancePage = () => { + return ( + <> + + +
    +

    System Maintenance

    +

    This system is currently undergoing maintenance. Please check back later.

    +
    + + ); +}; + +export default MaintenancePage; diff --git a/src/pages/Maintenance/MaintenancePage.module.scss b/src/pages/Maintenance/MaintenancePage.module.scss new file mode 100644 index 00000000000..0000c9bdc9c --- /dev/null +++ b/src/pages/Maintenance/MaintenancePage.module.scss @@ -0,0 +1,10 @@ +.maintenance_wrapper { + h1 { + font-weight: 500; + font-size: 2.67rem; + } + + text-align: center; + width: 550px; + margin: 10% auto; +} \ No newline at end of file diff --git a/src/pages/Maintenance/MaintenancePage.test.jsx b/src/pages/Maintenance/MaintenancePage.test.jsx new file mode 100644 index 00000000000..1f88ced3793 --- /dev/null +++ b/src/pages/Maintenance/MaintenancePage.test.jsx @@ -0,0 +1,14 @@ +import React from 'react'; +import { shallow } from 'enzyme'; + +import MaintenancePage from './MaintenancePage'; + +describe('Maintenance Page', () => { + it(' renders the maintenance page text', () => { + const wrapper = shallow(); + expect(wrapper.find('div')).toBeDefined(); + expect(wrapper.text()).toEqual( + 'System MaintenanceThis system is currently undergoing maintenance. Please check back later.', + ); + }); +}); diff --git a/src/pages/MyMove/Home/HomeHelpers.jsx b/src/pages/MyMove/Home/HomeHelpers.jsx index 0ead448e116..7a73196a168 100644 --- a/src/pages/MyMove/Home/HomeHelpers.jsx +++ b/src/pages/MyMove/Home/HomeHelpers.jsx @@ -142,8 +142,8 @@ export const HelperPPMCloseoutSubmitted = () => ( to your Finance office to finalize your acutal incentive amount and request payment.

    - If any documentation is unclear or inaccurate, the counselor will send it back for you to edit. When you are done - editing, you will submit it again and a counselor will review your changes. + If any documentation is unclear or inaccurate, the counselor will reach out to you to request clarification or + documentation updates.

    ); diff --git a/src/pages/MyMove/Home/MoveHome.test.jsx b/src/pages/MyMove/Home/MoveHome.test.jsx index 5fba8c34808..e86cff86bd3 100644 --- a/src/pages/MyMove/Home/MoveHome.test.jsx +++ b/src/pages/MyMove/Home/MoveHome.test.jsx @@ -6,8 +6,8 @@ import { waitFor } from '@testing-library/react'; import MoveHome from './MoveHome'; -import { customerRoutes } from 'constants/routes'; import { MockProviders } from 'testUtils'; +import { customerRoutes } from 'constants/routes'; import { cancelMove, downloadPPMAOAPacket } from 'services/internalApi'; import { ORDERS_TYPE } from 'constants/orders'; diff --git a/src/pages/MyMove/Home/index.test.jsx b/src/pages/MyMove/Home/index.test.jsx index db9583fc993..f70227004df 100644 --- a/src/pages/MyMove/Home/index.test.jsx +++ b/src/pages/MyMove/Home/index.test.jsx @@ -1026,4 +1026,55 @@ describe('Home component', () => { ); }); }); + describe('if the user has a ppm closeout', () => { + const props = { + ...defaultProps, + move: { ...defaultProps.move, status: MOVE_STATUSES.APPROVED, submitted_at: new Date().toISOString() }, + orders, + uploadedOrderDocuments, + }; + it('is submitted and currently in review', () => { + const propsForCloseoutCompleteShipment = { + ...props, + mtoShipments: [ + createPPMShipmentWithFinalIncentive({ + ppmShipment: { + status: ppmShipmentStatuses.NEEDS_CLOSEOUT, + pickupAddress: { + streetAddress1: '1 Test Street', + streetAddress2: '2 Test Street', + streetAddress3: '3 Test Street', + city: 'Pickup Test City', + state: 'NY', + postalCode: '10001', + }, + destinationAddress: { + streetAddress1: '1 Test Street', + streetAddress2: '2 Test Street', + streetAddress3: '3 Test Street', + city: 'Destination Test City', + state: 'NY', + postalCode: '11111', + }, + }, + }), + ], + }; + render( + + + , + ); + expect( + screen.getByText( + 'If your documentation is clear and valid, you’ll be able to download a payment packet. You can submit that packet to your Finance office to finalize your acutal incentive amount and request payment.', + ), + ).toBeInTheDocument(); + expect( + screen.getByText( + 'If any documentation is unclear or inaccurate, the counselor will reach out to you to request clarification or documentation updates.', + ), + ).toBeInTheDocument(); + }); + }); }); diff --git a/src/pages/MyMove/Multi-Moves/MultiMovesMoveContainer/MultiMovesMoveContainer.jsx b/src/pages/MyMove/Multi-Moves/MultiMovesMoveContainer/MultiMovesMoveContainer.jsx index 9bff7751091..ea5f9a8a98c 100644 --- a/src/pages/MyMove/Multi-Moves/MultiMovesMoveContainer/MultiMovesMoveContainer.jsx +++ b/src/pages/MyMove/Multi-Moves/MultiMovesMoveContainer/MultiMovesMoveContainer.jsx @@ -20,7 +20,7 @@ import { downloadPPMAOAPacket, downloadPPMPaymentPacket } from 'services/interna import { ppmShipmentStatuses } from 'constants/shipments'; import { setFlashMessage as setFlashMessageAction } from 'store/flash/actions'; import scrollToTop from 'shared/scrollToTop'; -import { MOVE_STATUSES } from 'shared/constants'; +import { MOVE_STATUSES, SHIPMENT_TYPES } from 'shared/constants'; const MultiMovesMoveContainer = ({ moves, setFlashMessage }) => { const [expandedMoves, setExpandedMoves] = useState({}); @@ -37,27 +37,30 @@ const MultiMovesMoveContainer = ({ moves, setFlashMessage }) => { // handles the title of the shipment header below each move const generateShipmentTypeTitle = (shipmentType) => { - if (shipmentType === 'HHG') { + if (shipmentType === SHIPMENT_TYPES.HHG) { return 'Household Goods'; } - if (shipmentType === 'PPM') { + if (shipmentType === SHIPMENT_TYPES.PPM) { return 'Personally Procured Move'; } - if (shipmentType === 'HHG_INTO_NTS_DOMESTIC') { + if (shipmentType === SHIPMENT_TYPES.NTS) { return 'Household Goods NTS'; } - if (shipmentType === 'HHG_OUTOF_NTS_DOMESTIC') { + if (shipmentType === SHIPMENT_TYPES.NTSR) { return 'Household Goods NTSR'; } - if (shipmentType === 'MOBILE_HOME') { + if (shipmentType === SHIPMENT_TYPES.MOBILE_HOME) { return 'Mobile Home'; } - if (shipmentType === 'BOAT_HAUL_AWAY') { + if (shipmentType === SHIPMENT_TYPES.BOAT_HAUL_AWAY) { return 'Boat Haul Away'; } - if (shipmentType === 'BOAT_TOW_AWAY') { + if (shipmentType === SHIPMENT_TYPES.BOAT_TOW_AWAY) { return 'Boat Tow Away'; } + if (shipmentType === SHIPMENT_TYPES.UNACCOMPANIED_BAGGAGE) { + return 'Unaccompanied Baggage'; + } return 'Shipment'; }; @@ -178,7 +181,7 @@ const MultiMovesMoveContainer = ({ moves, setFlashMessage }) => { >
    -

    {generateShipmentTypeTitle(s.shipmentType)}

    +

    {generateShipmentTypeTitle(s.shipmentType)}

    {s?.ppmShipment?.advanceStatus === ADVANCE_STATUSES.APPROVED.apiValue || s?.ppmShipment?.status === ppmShipmentStatuses.CLOSEOUT_COMPLETE ? ( { // Initially, the move details should not be visible expect(screen.queryByText('Shipment')).not.toBeInTheDocument(); - fireEvent.click(screen.getByTestId('expand-icon')); // Now, the move details should be visible @@ -83,6 +82,29 @@ describe('MultiMovesMoveContainer', () => { expect(goToMoveButtons).toHaveLength(2); }); + it('renders correct shipment type headings for all shipment types when expanded', async () => { + render( + + + , + ); + + // expand the shipments + fireEvent.click(screen.getByTestId('expand-icon')); + await waitFor(() => expect(screen.getByText('Shipments')).toBeInTheDocument()); + + const headings = screen.queryAllByRole('heading', { level: 4 }); + expect(headings).toHaveLength(8); + expect(headings[0]).toHaveTextContent('Household Goods'); + expect(headings[1]).toHaveTextContent('Personally Procured Move'); + expect(headings[2]).toHaveTextContent('Household Goods NTS'); + expect(headings[3]).toHaveTextContent('Household Goods NTSR'); + expect(headings[4]).toHaveTextContent('Mobile Home'); + expect(headings[5]).toHaveTextContent('Boat Haul Away'); + expect(headings[6]).toHaveTextContent('Boat Tow Away'); + expect(headings[7]).toHaveTextContent('Unaccompanied Baggage'); + }); + it('renders appropriate ppm download options', async () => { const { currentMove } = mockMovesPPMWithAdvanceOptions; render( @@ -131,6 +153,26 @@ describe('MultiMovesMoveContainer', () => { expect(screen.queryByText('PPM Packet')).not.toBeInTheDocument(); }); + it('renders correct shipment type headings for PPMs', async () => { + const { currentMove } = mockMovesPPMWithAdvanceOptions; + render( + + + , + ); + + // expand the shipments + fireEvent.click(screen.getByTestId('expand-icon')); + await waitFor(() => expect(screen.getByText('Shipments')).toBeInTheDocument()); + + const headings = screen.queryAllByRole('heading', { level: 4 }); + expect(headings).toHaveLength(4); + expect(headings[0]).toHaveTextContent('Personally Procured Move'); + expect(headings[1]).toHaveTextContent('Personally Procured Move'); + expect(headings[2]).toHaveTextContent('Personally Procured Move'); + expect(headings[3]).toHaveTextContent('Personally Procured Move'); + }); + it('renders Canceled move label', () => { render( diff --git a/src/pages/MyMove/Multi-Moves/MultiMovesTestData.js b/src/pages/MyMove/Multi-Moves/MultiMovesTestData.js index aa7e5fd9bc3..59557f690a7 100644 --- a/src/pages/MyMove/Multi-Moves/MultiMovesTestData.js +++ b/src/pages/MyMove/Multi-Moves/MultiMovesTestData.js @@ -1,5 +1,6 @@ import { ADVANCE_STATUSES } from 'constants/ppms'; import { ppmShipmentStatuses } from 'constants/shipments'; +import { SHIPMENT_TYPES } from 'shared/constants'; export const mockMovesPCS = { currentMove: [ @@ -45,43 +46,49 @@ export const mockMovesPCS = { mtoShipments: [ { id: 'shipment1', - shipmentType: 'HHG', + shipmentType: SHIPMENT_TYPES.HHG, status: 'APPROVED', created_at: '2024-01-03 15:28:28.468 -0600', }, { id: 'shipment2', - shipmentType: 'PPM', + shipmentType: SHIPMENT_TYPES.PPM, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment3', - shipmentType: 'HHG_INTO_NTS_DOMESTIC', + shipmentType: SHIPMENT_TYPES.NTS, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment4', - shipmentType: 'HHG_OUTOF_NTS_DOMESTIC', + shipmentType: SHIPMENT_TYPES.NTSR, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment5', - shipmentType: 'MOBILE_HOME', + shipmentType: SHIPMENT_TYPES.MOBILE_HOME, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment6', - shipmentType: 'BOAT_HAUL_AWAY', + shipmentType: SHIPMENT_TYPES.BOAT_HAUL_AWAY, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment6', - shipmentType: 'BOAT_TOW_AWAY', + shipmentType: SHIPMENT_TYPES.BOAT_TOW_AWAY, + status: 'APPROVED', + created_at: '2024-01-05 15:28:28.468 -0600', + }, + { + id: 'shipment7', + shipmentType: SHIPMENT_TYPES.UNACCOMPANIED_BAGGAGE, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, @@ -131,43 +138,43 @@ export const mockMovesPCS = { mtoShipments: [ { id: 'shipment1', - shipmentType: 'HHG', + shipmentType: SHIPMENT_TYPES.HHG, status: 'APPROVED', created_at: '2024-01-03 15:28:28.468 -0600', }, { id: 'shipment2', - shipmentType: 'PPM', + shipmentType: SHIPMENT_TYPES.PPM, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment3', - shipmentType: 'HHG_INTO_NTS_DOMESTIC', + shipmentType: SHIPMENT_TYPES.NTS, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment4', - shipmentType: 'HHG_OUTOF_NTS_DOMESTIC', + shipmentType: SHIPMENT_TYPES.NTSR, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment5', - shipmentType: 'MOBILE_HOME', + shipmentType: SHIPMENT_TYPES.MOBILE_HOME, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment6', - shipmentType: 'BOAT_HAUL_AWAY', + shipmentType: SHIPMENT_TYPES.BOAT_HAUL_AWAY, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment6', - shipmentType: 'BOAT_TOW_AWAY', + shipmentType: SHIPMENT_TYPES.BOAT_TOW_AWAY, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, @@ -215,43 +222,43 @@ export const mockMovesPCS = { mtoShipments: [ { id: 'shipment1', - shipmentType: 'HHG', + shipmentType: SHIPMENT_TYPES.HHG, status: 'APPROVED', created_at: '2024-01-03 15:28:28.468 -0600', }, { id: 'shipment2', - shipmentType: 'PPM', + shipmentType: SHIPMENT_TYPES.PPM, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment3', - shipmentType: 'HHG_INTO_NTS_DOMESTIC', + shipmentType: SHIPMENT_TYPES.NTS, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment4', - shipmentType: 'HHG_OUTOF_NTS_DOMESTIC', + shipmentType: SHIPMENT_TYPES.NTSR, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment5', - shipmentType: 'MOBILE_HOME', + shipmentType: SHIPMENT_TYPES.MOBILE_HOME, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment6', - shipmentType: 'BOAT_HAUL_AWAY', + shipmentType: SHIPMENT_TYPES.BOAT_HAUL_AWAY, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment6', - shipmentType: 'BOAT_TOW_AWAY', + shipmentType: SHIPMENT_TYPES.BOAT_TOW_AWAY, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, @@ -301,43 +308,43 @@ export const mockMovesPCS = { mtoShipments: [ { id: 'shipment1', - shipmentType: 'HHG', + shipmentType: SHIPMENT_TYPES.HHG, status: 'APPROVED', created_at: '2024-01-03 15:28:28.468 -0600', }, { id: 'shipment2', - shipmentType: 'PPM', + shipmentType: SHIPMENT_TYPES.PPM, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment3', - shipmentType: 'HHG_INTO_NTS_DOMESTIC', + shipmentType: SHIPMENT_TYPES.NTS, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment4', - shipmentType: 'HHG_OUTOF_NTS_DOMESTIC', + shipmentType: SHIPMENT_TYPES.NTSR, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment5', - shipmentType: 'MOBILE_HOME', + shipmentType: SHIPMENT_TYPES.MOBILE_HOME, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment6', - shipmentType: 'BOAT_HAUL_AWAY', + shipmentType: SHIPMENT_TYPES.BOAT_HAUL_AWAY, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment6', - shipmentType: 'BOAT_TOW_AWAY', + shipmentType: SHIPMENT_TYPES.BOAT_TOW_AWAY, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, @@ -390,43 +397,43 @@ export const mockMovesRetirement = { mtoShipments: [ { id: 'shipment1', - shipmentType: 'HHG', + shipmentType: SHIPMENT_TYPES.HHG, status: 'APPROVED', created_at: '2024-01-03 15:28:28.468 -0600', }, { id: 'shipment2', - shipmentType: 'PPM', + shipmentType: SHIPMENT_TYPES.PPM, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment3', - shipmentType: 'HHG_INTO_NTS_DOMESTIC', + shipmentType: SHIPMENT_TYPES.NTS, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment4', - shipmentType: 'HHG_OUTOF_NTS_DOMESTIC', + shipmentType: SHIPMENT_TYPES.NTSR, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment5', - shipmentType: 'MOBILE_HOME', + shipmentType: SHIPMENT_TYPES.MOBILE_HOME, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment6', - shipmentType: 'BOAT_HAUL_AWAY', + shipmentType: SHIPMENT_TYPES.BOAT_HAUL_AWAY, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment6', - shipmentType: 'BOAT_TOW_AWAY', + shipmentType: SHIPMENT_TYPES.BOAT_TOW_AWAY, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, @@ -476,43 +483,43 @@ export const mockMovesRetirement = { mtoShipments: [ { id: 'shipment1', - shipmentType: 'HHG', + shipmentType: SHIPMENT_TYPES.HHG, status: 'APPROVED', created_at: '2024-01-03 15:28:28.468 -0600', }, { id: 'shipment2', - shipmentType: 'PPM', + shipmentType: SHIPMENT_TYPES.PPM, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment3', - shipmentType: 'HHG_INTO_NTS_DOMESTIC', + shipmentType: SHIPMENT_TYPES.NTS, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment4', - shipmentType: 'HHG_OUTOF_NTS_DOMESTIC', + shipmentType: SHIPMENT_TYPES.NTSR, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment5', - shipmentType: 'MOBILE_HOME', + shipmentType: SHIPMENT_TYPES.MOBILE_HOME, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment6', - shipmentType: 'BOAT_HAUL_AWAY', + shipmentType: SHIPMENT_TYPES.BOAT_HAUL_AWAY, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment6', - shipmentType: 'BOAT_TOW_AWAY', + shipmentType: SHIPMENT_TYPES.BOAT_TOW_AWAY, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, @@ -560,43 +567,43 @@ export const mockMovesRetirement = { mtoShipments: [ { id: 'shipment1', - shipmentType: 'HHG', + shipmentType: SHIPMENT_TYPES.HHG, status: 'APPROVED', created_at: '2024-01-03 15:28:28.468 -0600', }, { id: 'shipment2', - shipmentType: 'PPM', + shipmentType: SHIPMENT_TYPES.PPM, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment3', - shipmentType: 'HHG_INTO_NTS_DOMESTIC', + shipmentType: SHIPMENT_TYPES.NTS, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment4', - shipmentType: 'HHG_OUTOF_NTS_DOMESTIC', + shipmentType: SHIPMENT_TYPES.NTSR, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment5', - shipmentType: 'MOBILE_HOME', + shipmentType: SHIPMENT_TYPES.MOBILE_HOME, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment6', - shipmentType: 'BOAT_HAUL_AWAY', + shipmentType: SHIPMENT_TYPES.BOAT_HAUL_AWAY, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment6', - shipmentType: 'BOAT_TOW_AWAY', + shipmentType: SHIPMENT_TYPES.BOAT_TOW_AWAY, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, @@ -649,43 +656,43 @@ export const mockMovesSeparation = { mtoShipments: [ { id: 'shipment1', - shipmentType: 'HHG', + shipmentType: SHIPMENT_TYPES.HHG, status: 'APPROVED', created_at: '2024-01-03 15:28:28.468 -0600', }, { id: 'shipment2', - shipmentType: 'PPM', + shipmentType: SHIPMENT_TYPES.PPM, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment3', - shipmentType: 'HHG_INTO_NTS_DOMESTIC', + shipmentType: SHIPMENT_TYPES.NTS, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment4', - shipmentType: 'HHG_OUTOF_NTS_DOMESTIC', + shipmentType: SHIPMENT_TYPES.NTSR, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment5', - shipmentType: 'MOBILE_HOME', + shipmentType: SHIPMENT_TYPES.MOBILE_HOME, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment6', - shipmentType: 'BOAT_HAUL_AWAY', + shipmentType: SHIPMENT_TYPES.BOAT_HAUL_AWAY, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment6', - shipmentType: 'BOAT_TOW_AWAY', + shipmentType: SHIPMENT_TYPES.BOAT_TOW_AWAY, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, @@ -735,43 +742,43 @@ export const mockMovesSeparation = { mtoShipments: [ { id: 'shipment1', - shipmentType: 'HHG', + shipmentType: SHIPMENT_TYPES.HHG, status: 'APPROVED', created_at: '2024-01-03 15:28:28.468 -0600', }, { id: 'shipment2', - shipmentType: 'PPM', + shipmentType: SHIPMENT_TYPES.PPM, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment3', - shipmentType: 'HHG_INTO_NTS_DOMESTIC', + shipmentType: SHIPMENT_TYPES.NTS, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment4', - shipmentType: 'HHG_OUTOF_NTS_DOMESTIC', + shipmentType: SHIPMENT_TYPES.NTSR, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment5', - shipmentType: 'MOBILE_HOME', + shipmentType: SHIPMENT_TYPES.MOBILE_HOME, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment6', - shipmentType: 'BOAT_HAUL_AWAY', + shipmentType: SHIPMENT_TYPES.BOAT_HAUL_AWAY, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment6', - shipmentType: 'BOAT_TOW_AWAY', + shipmentType: SHIPMENT_TYPES.BOAT_TOW_AWAY, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, @@ -819,43 +826,43 @@ export const mockMovesSeparation = { mtoShipments: [ { id: 'shipment1', - shipmentType: 'HHG', + shipmentType: SHIPMENT_TYPES.HHG, status: 'APPROVED', created_at: '2024-01-03 15:28:28.468 -0600', }, { id: 'shipment2', - shipmentType: 'PPM', + shipmentType: SHIPMENT_TYPES.PPM, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment3', - shipmentType: 'HHG_INTO_NTS_DOMESTIC', + shipmentType: SHIPMENT_TYPES.NTS, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment4', - shipmentType: 'HHG_OUTOF_NTS_DOMESTIC', + shipmentType: SHIPMENT_TYPES.NTSR, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment5', - shipmentType: 'MOBILE_HOME', + shipmentType: SHIPMENT_TYPES.MOBILE_HOME, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment6', - shipmentType: 'BOAT_HAUL_AWAY', + shipmentType: SHIPMENT_TYPES.BOAT_HAUL_AWAY, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment6', - shipmentType: 'BOAT_TOW_AWAY', + shipmentType: SHIPMENT_TYPES.BOAT_TOW_AWAY, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, @@ -908,43 +915,43 @@ export const mockMovesNoPreviousMoves = { mtoShipments: [ { id: 'shipment1', - shipmentType: 'HHG', + shipmentType: SHIPMENT_TYPES.HHG, status: 'APPROVED', created_at: '2024-01-03 15:28:28.468 -0600', }, { id: 'shipment2', - shipmentType: 'PPM', + shipmentType: SHIPMENT_TYPES.PPM, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment3', - shipmentType: 'HHG_INTO_NTS_DOMESTIC', + shipmentType: SHIPMENT_TYPES.NTS, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment4', - shipmentType: 'HHG_OUTOF_NTS_DOMESTIC', + shipmentType: SHIPMENT_TYPES.NTSR, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment5', - shipmentType: 'MOBILE_HOME', + shipmentType: SHIPMENT_TYPES.MOBILE_HOME, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment6', - shipmentType: 'BOAT_HAUL_AWAY', + shipmentType: SHIPMENT_TYPES.BOAT_HAUL_AWAY, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment6', - shipmentType: 'BOAT_TOW_AWAY', + shipmentType: SHIPMENT_TYPES.BOAT_TOW_AWAY, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, @@ -999,43 +1006,43 @@ export const mockMovesNoCurrentMoveWithPreviousMoves = { mtoShipments: [ { id: 'shipment1', - shipmentType: 'HHG', + shipmentType: SHIPMENT_TYPES.HHG, status: 'APPROVED', created_at: '2024-01-03 15:28:28.468 -0600', }, { id: 'shipment2', - shipmentType: 'PPM', + shipmentType: SHIPMENT_TYPES.PPM, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment3', - shipmentType: 'HHG_INTO_NTS_DOMESTIC', + shipmentType: SHIPMENT_TYPES.NTS, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment4', - shipmentType: 'HHG_OUTOF_NTS_DOMESTIC', + shipmentType: SHIPMENT_TYPES.NTSR, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment5', - shipmentType: 'MOBILE_HOME', + shipmentType: SHIPMENT_TYPES.MOBILE_HOME, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment6', - shipmentType: 'BOAT_HAUL_AWAY', + shipmentType: SHIPMENT_TYPES.BOAT_HAUL_AWAY, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment6', - shipmentType: 'BOAT_TOW_AWAY', + shipmentType: SHIPMENT_TYPES.BOAT_TOW_AWAY, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, @@ -1083,43 +1090,43 @@ export const mockMovesNoCurrentMoveWithPreviousMoves = { mtoShipments: [ { id: 'shipment1', - shipmentType: 'HHG', + shipmentType: SHIPMENT_TYPES.HHG, status: 'APPROVED', created_at: '2024-01-03 15:28:28.468 -0600', }, { id: 'shipment2', - shipmentType: 'PPM', + shipmentType: SHIPMENT_TYPES.PPM, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment3', - shipmentType: 'HHG_INTO_NTS_DOMESTIC', + shipmentType: SHIPMENT_TYPES.NTS, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment4', - shipmentType: 'HHG_OUTOF_NTS_DOMESTIC', + shipmentType: SHIPMENT_TYPES.NTSR, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment5', - shipmentType: 'MOBILE_HOME', + shipmentType: SHIPMENT_TYPES.MOBILE_HOME, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment6', - shipmentType: 'BOAT_HAUL_AWAY', + shipmentType: SHIPMENT_TYPES.BOAT_HAUL_AWAY, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, { id: 'shipment6', - shipmentType: 'BOAT_TOW_AWAY', + shipmentType: SHIPMENT_TYPES.BOAT_TOW_AWAY, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', }, @@ -1177,7 +1184,7 @@ export const mockMovesPPMWithAdvanceOptions = { mtoShipments: [ { id: 'shipment1', - shipmentType: 'PPM', + shipmentType: SHIPMENT_TYPES.PPM, status: 'APPROVED', created_at: '2024-01-03 15:28:28.468 -0600', ppmShipment: { @@ -1187,7 +1194,7 @@ export const mockMovesPPMWithAdvanceOptions = { }, { id: 'shipment2', - shipmentType: 'PPM', + shipmentType: SHIPMENT_TYPES.PPM, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', ppmShipment: { @@ -1197,7 +1204,7 @@ export const mockMovesPPMWithAdvanceOptions = { }, { id: 'shipment3', - shipmentType: 'PPM', + shipmentType: SHIPMENT_TYPES.PPM, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', ppmShipment: { @@ -1207,7 +1214,7 @@ export const mockMovesPPMWithAdvanceOptions = { }, { id: 'shipment4', - shipmentType: 'PPM', + shipmentType: SHIPMENT_TYPES.PPM, status: 'APPROVED', created_at: '2024-01-05 15:28:28.468 -0600', ppmShipment: { diff --git a/src/pages/MyMove/PPM/Booking/DateAndLocation/DateAndLocation.test.jsx b/src/pages/MyMove/PPM/Booking/DateAndLocation/DateAndLocation.test.jsx index bfb01dfa755..c43e5c91b1d 100644 --- a/src/pages/MyMove/PPM/Booking/DateAndLocation/DateAndLocation.test.jsx +++ b/src/pages/MyMove/PPM/Booking/DateAndLocation/DateAndLocation.test.jsx @@ -499,7 +499,7 @@ describe('DateAndLocation component', () => { ); }); - // tertiary destination address + // tertiary delivery address await act(async () => { await userEvent.type( diff --git a/src/pages/MyMove/SelectShipmentType.jsx b/src/pages/MyMove/SelectShipmentType.jsx index ef636a5be16..b92102f9814 100644 --- a/src/pages/MyMove/SelectShipmentType.jsx +++ b/src/pages/MyMove/SelectShipmentType.jsx @@ -18,7 +18,7 @@ import { generalRoutes, customerRoutes } from 'constants/routes'; import styles from 'pages/MyMove/SelectShipmentType.module.scss'; import { loadMTOShipments as loadMTOShipmentsAction } from 'shared/Entities/modules/mtoShipments'; import { updateMove as updateMoveAction } from 'store/entities/actions'; -import { selectMTOShipmentsForCurrentMove } from 'store/entities/selectors'; +import { selectMTOShipmentsForCurrentMove, selectOrdersForLoggedInUser } from 'store/entities/selectors'; import formStyles from 'styles/form.module.scss'; import { MoveTaskOrderShape } from 'types/order'; import { ShipmentShape } from 'types/shipment'; @@ -40,6 +40,7 @@ export class SelectShipmentType extends Component { enableNTSR: false, enableBoat: false, enableMobileHome: false, + enableUB: false, }; } @@ -71,6 +72,11 @@ export class SelectShipmentType extends Component { enableMobileHome: enabled, }); }); + isBooleanFlagEnabled(FEATURE_FLAG_KEYS.UNACCOMPANIED_BAGGAGE).then((enabled) => { + this.setState({ + enableUB: enabled, + }); + }); } setShipmentType = (e) => { @@ -111,6 +117,7 @@ export class SelectShipmentType extends Component { router: { navigate }, move, mtoShipments, + orders, } = this.props; const { shipmentType, @@ -122,6 +129,7 @@ export class SelectShipmentType extends Component { enableNTSR, enableBoat, enableMobileHome, + enableUB, errorMessage, } = this.state; @@ -147,6 +155,14 @@ export class SelectShipmentType extends Component { const mobileHomeCardText = 'Provide information about your mobile home.'; + const ubCardText = shipmentInfo.isUBSelectable + ? 'Certain personal property items are packed and moved by professionals, paid for by the government. Subject to item type and weight limitations. This is an unaccompanied baggage shipment (UB).' + : 'Talk with your movers directly if you want to add or change shipments.'; + + const hasOconusDutyLocation = orders[0] + ? orders[0].origin_duty_location.address.isOconus || orders[0].new_duty_location.address.isOconus + : false; + const selectableCardDefaultProps = { onChange: (e) => this.setShipmentType(e), name: 'shipmentType', @@ -213,6 +229,19 @@ export class SelectShipmentType extends Component { /> )} + {enableUB && hasOconusDutyLocation && ( + + )} + {enableNTS || enableNTSR ? ( <>

    @@ -310,6 +339,8 @@ export class SelectShipmentType extends Component { { } = ownProps; const move = selectMove(state, moveId); const mtoShipments = selectMTOShipmentsForCurrentMove(state); + const orders = selectOrdersForLoggedInUser(state); return { move, mtoShipments, + orders, }; }; diff --git a/src/pages/MyMove/SelectShipmentType.stories.jsx b/src/pages/MyMove/SelectShipmentType.stories.jsx index d77c272db46..e78a444ffb5 100644 --- a/src/pages/MyMove/SelectShipmentType.stories.jsx +++ b/src/pages/MyMove/SelectShipmentType.stories.jsx @@ -20,6 +20,31 @@ const defaultProps = { status: 'DRAFT', }, mtoShipments: [], + orders: [], +}; + +const oconusOriginDutyLocationProps = { + router: { navigate: noop }, + updateMove: noop, + loadMTOShipments: noop, + move: { + status: 'DRAFT', + }, + mtoShipments: [], + orders: [ + { + origin_duty_location: { + address: { + isOconus: true, + }, + }, + new_duty_location: { + address: { + isOconus: false, + }, + }, + }, + ], }; export const Submitted = () => { @@ -115,3 +140,20 @@ export const WithNTSAndNTSRComplete = () => { ); }; + +export const WithUBComplete = () => { + const props = { + ...oconusOriginDutyLocationProps, + mtoShipments: [ + { + shipmentType: SHIPMENT_OPTIONS.UNACCOMPANIED_BAGGAGE, + }, + ], + }; + + return ( + + + + ); +}; diff --git a/src/pages/MyMove/SelectShipmentType.test.jsx b/src/pages/MyMove/SelectShipmentType.test.jsx index 88778b36d4e..4e47f9b3e41 100644 --- a/src/pages/MyMove/SelectShipmentType.test.jsx +++ b/src/pages/MyMove/SelectShipmentType.test.jsx @@ -21,8 +21,57 @@ describe('SelectShipmentType', () => { loadMTOShipments: jest.fn(), move: { id: 'mockId', status: MOVE_STATUSES.DRAFT }, mtoShipments: [], + orders: [], }; + const oconusOriginDutyLocationProps = { + updateMove: jest.fn(), + router: { navigate: jest.fn() }, + + loadMTOShipments: jest.fn(), + move: { id: 'mockId', status: MOVE_STATUSES.DRAFT }, + mtoShipments: [], + orders: [ + { + origin_duty_location: { + address: { + isOconus: true, + }, + }, + new_duty_location: { + address: { + isOconus: false, + }, + }, + }, + ], + }; + + const oconusNewDutyLocationProps = { + updateMove: jest.fn(), + router: { navigate: jest.fn() }, + + loadMTOShipments: jest.fn(), + move: { id: 'mockId', status: MOVE_STATUSES.DRAFT }, + mtoShipments: [], + orders: [ + { + origin_duty_location: { + address: { + isOconus: false, + }, + }, + new_duty_location: { + address: { + isOconus: true, + }, + }, + }, + ], + }; + + // selectOrdersForLoggedInUser.mockImplementation(() => originOconusDutyLocationProps.orders); + const getWrapper = (props = {}) => { return mount(); }; @@ -122,14 +171,18 @@ describe('SelectShipmentType', () => { expect(ntsCard.length).toBe(0); const ntsrCard = wrapper.find(`SelectableCard[id="${SHIPMENT_OPTIONS.NTSR}"]`); expect(ntsrCard.length).toBe(0); + const ubCard = wrapper.find(`SelectableCard[id="${SHIPMENT_OPTIONS.UNACCOMPANIED_BAGGAGE}"]`); + expect(ubCard.length).toBe(0); expect(wrapper.state('enablePPM')).toEqual(false); expect(wrapper.state('enableNTS')).toEqual(false); expect(wrapper.state('enableNTSR')).toEqual(false); + expect(wrapper.state('enableUB')).toEqual(false); expect(isBooleanFlagEnabled).toBeCalledWith(FEATURE_FLAG_KEYS.PPM); expect(isBooleanFlagEnabled).toBeCalledWith(FEATURE_FLAG_KEYS.NTS); expect(isBooleanFlagEnabled).toBeCalledWith(FEATURE_FLAG_KEYS.NTSR); + expect(isBooleanFlagEnabled).toBeCalledWith(FEATURE_FLAG_KEYS.UNACCOMPANIED_BAGGAGE); }); it('feature flags for shipment types show SelectableCard', async () => { @@ -155,6 +208,97 @@ describe('SelectShipmentType', () => { expect(isBooleanFlagEnabled).toBeCalledWith(FEATURE_FLAG_KEYS.NTS); expect(isBooleanFlagEnabled).toBeCalledWith(FEATURE_FLAG_KEYS.NTSR); }); + + it('UB feature flag on does NOT show UB SelectableCard if no OCONUS origin or destination duty location', async () => { + isBooleanFlagEnabled.mockImplementation(() => Promise.resolve(true)); + + const props = {}; + const wrapper = shallow(); + await wrapper; + const hhgCard = wrapper.find(`SelectableCard[id="${SHIPMENT_OPTIONS.HHG}"]`); + expect(hhgCard.length).toBe(1); + const ppmCard = wrapper.find(`SelectableCard[id="${SHIPMENT_OPTIONS.PPM}"]`); + expect(ppmCard.length).toBe(1); + const ntsCard = wrapper.find(`SelectableCard[id="${SHIPMENT_OPTIONS.NTS}"]`); + expect(ntsCard.length).toBe(1); + const ntsrCard = wrapper.find(`SelectableCard[id="${SHIPMENT_OPTIONS.NTSR}"]`); + expect(ntsrCard.length).toBe(1); + + // even though the UB FF flag is on, we still don't want to show it as selectable + // unless we have an OCONUS origin or destination duty location + const ubCard = wrapper.find(`SelectableCard[id="${SHIPMENT_OPTIONS.UNACCOMPANIED_BAGGAGE}"]`); + expect(ubCard.length).toBe(0); + + expect(wrapper.state('enablePPM')).toEqual(true); + expect(wrapper.state('enableNTS')).toEqual(true); + expect(wrapper.state('enableNTSR')).toEqual(true); + expect(wrapper.state('enableUB')).toEqual(true); + + expect(isBooleanFlagEnabled).toBeCalledWith(FEATURE_FLAG_KEYS.PPM); + expect(isBooleanFlagEnabled).toBeCalledWith(FEATURE_FLAG_KEYS.NTS); + expect(isBooleanFlagEnabled).toBeCalledWith(FEATURE_FLAG_KEYS.NTSR); + expect(isBooleanFlagEnabled).toBeCalledWith(FEATURE_FLAG_KEYS.UNACCOMPANIED_BAGGAGE); + }); + + it('UB feature flag on DOES show UB SelectableCard if OCONUS origin duty location', async () => { + isBooleanFlagEnabled.mockImplementation(() => Promise.resolve(true)); + + const props = {}; + const wrapper = shallow(); + await wrapper; + const hhgCard = wrapper.find(`SelectableCard[id="${SHIPMENT_OPTIONS.HHG}"]`); + expect(hhgCard.length).toBe(1); + const ppmCard = wrapper.find(`SelectableCard[id="${SHIPMENT_OPTIONS.PPM}"]`); + expect(ppmCard.length).toBe(1); + const ntsCard = wrapper.find(`SelectableCard[id="${SHIPMENT_OPTIONS.NTS}"]`); + expect(ntsCard.length).toBe(1); + const ntsrCard = wrapper.find(`SelectableCard[id="${SHIPMENT_OPTIONS.NTSR}"]`); + expect(ntsrCard.length).toBe(1); + + // since origin duty location has isOconus as true, now we can select UBs + const ubCard = wrapper.find(`SelectableCard[id="${SHIPMENT_OPTIONS.UNACCOMPANIED_BAGGAGE}"]`); + expect(ubCard.length).toBe(1); + + expect(wrapper.state('enablePPM')).toEqual(true); + expect(wrapper.state('enableNTS')).toEqual(true); + expect(wrapper.state('enableNTSR')).toEqual(true); + expect(wrapper.state('enableUB')).toEqual(true); + + expect(isBooleanFlagEnabled).toBeCalledWith(FEATURE_FLAG_KEYS.PPM); + expect(isBooleanFlagEnabled).toBeCalledWith(FEATURE_FLAG_KEYS.NTS); + expect(isBooleanFlagEnabled).toBeCalledWith(FEATURE_FLAG_KEYS.NTSR); + expect(isBooleanFlagEnabled).toBeCalledWith(FEATURE_FLAG_KEYS.UNACCOMPANIED_BAGGAGE); + }); + + it('UB feature flag on DOES show UB SelectableCard if OCONUS new duty location', async () => { + isBooleanFlagEnabled.mockImplementation(() => Promise.resolve(true)); + + const props = {}; + const wrapper = shallow(); + await wrapper; + const hhgCard = wrapper.find(`SelectableCard[id="${SHIPMENT_OPTIONS.HHG}"]`); + expect(hhgCard.length).toBe(1); + const ppmCard = wrapper.find(`SelectableCard[id="${SHIPMENT_OPTIONS.PPM}"]`); + expect(ppmCard.length).toBe(1); + const ntsCard = wrapper.find(`SelectableCard[id="${SHIPMENT_OPTIONS.NTS}"]`); + expect(ntsCard.length).toBe(1); + const ntsrCard = wrapper.find(`SelectableCard[id="${SHIPMENT_OPTIONS.NTSR}"]`); + expect(ntsrCard.length).toBe(1); + + // since new duty location has isOconus as true, now we can select UBs + const ubCard = wrapper.find(`SelectableCard[id="${SHIPMENT_OPTIONS.UNACCOMPANIED_BAGGAGE}"]`); + expect(ubCard.length).toBe(1); + + expect(wrapper.state('enablePPM')).toEqual(true); + expect(wrapper.state('enableNTS')).toEqual(true); + expect(wrapper.state('enableNTSR')).toEqual(true); + expect(wrapper.state('enableUB')).toEqual(true); + + expect(isBooleanFlagEnabled).toBeCalledWith(FEATURE_FLAG_KEYS.PPM); + expect(isBooleanFlagEnabled).toBeCalledWith(FEATURE_FLAG_KEYS.NTS); + expect(isBooleanFlagEnabled).toBeCalledWith(FEATURE_FLAG_KEYS.NTSR); + expect(isBooleanFlagEnabled).toBeCalledWith(FEATURE_FLAG_KEYS.UNACCOMPANIED_BAGGAGE); + }); }); describe('when no PPMs or shipments have been created', () => { @@ -233,6 +377,7 @@ describe('SelectShipmentType', () => { wrapper.setState({ enablePPM: true }); wrapper.setState({ enableNTS: true }); wrapper.setState({ enableNTSR: true }); + wrapper.setState({ enableUB: true }); it('should render the correct text', () => { expect(wrapper.find('.usa-checkbox__label-description').at(0).text()).toContain( 'Talk with your movers directly if you want to add or change shipments.', diff --git a/src/pages/Office/AddShipment/AddShipment.test.jsx b/src/pages/Office/AddShipment/AddShipment.test.jsx index 162bc165a02..c10c3cffa87 100644 --- a/src/pages/Office/AddShipment/AddShipment.test.jsx +++ b/src/pages/Office/AddShipment/AddShipment.test.jsx @@ -207,7 +207,7 @@ describe('AddShipment component', () => { expect(saveButton).toBeDisabled(); }); - expect(screen.getByLabelText('Use current address')).not.toBeChecked(); + expect(screen.getByLabelText('Use pickup address')).not.toBeChecked(); await userEvent.type(screen.getAllByLabelText('Address 1')[0], '812 S 129th St'); await userEvent.type(screen.getAllByLabelText('City')[0], 'San Antonio'); diff --git a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx index 2f89a760762..fe1b3480932 100644 --- a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx +++ b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx @@ -377,7 +377,7 @@ export const CreateCustomerForm = ({ userPrivileges, setFlashMessage, setCanAddO

    -

    Current Address

    +

    Pickup Address

    { expect(screen.getByText('Customer Affiliation')).toBeInTheDocument(); expect(screen.getByText('Customer Name')).toBeInTheDocument(); expect(screen.getByText('Contact Info')).toBeInTheDocument(); - expect(screen.getByText('Current Address')).toBeInTheDocument(); + expect(screen.getByText('Pickup Address')).toBeInTheDocument(); expect(screen.getByText('Backup Address')).toBeInTheDocument(); expect(screen.getByText('Backup Contact')).toBeInTheDocument(); expect(screen.getByText('Okta Account')).toBeInTheDocument(); diff --git a/src/pages/Office/HeadquartersQueues/HeadquartersQueues.jsx b/src/pages/Office/HeadquartersQueues/HeadquartersQueues.jsx index 8ba085fc464..129f9f49cda 100644 --- a/src/pages/Office/HeadquartersQueues/HeadquartersQueues.jsx +++ b/src/pages/Office/HeadquartersQueues/HeadquartersQueues.jsx @@ -38,7 +38,7 @@ import { milmoveLogger } from 'utils/milmoveLog'; import ConnectedFlashMessage from 'containers/FlashMessage/FlashMessage'; import CustomerSearchForm from 'components/CustomerSearchForm/CustomerSearchForm'; -const HeadquartersQueue = () => { +const HeadquartersQueue = ({ isQueueManagementFFEnabled }) => { const navigate = useNavigate(); const { queueType } = useParams(); const [search, setSearch] = useState({ moveCode: null, dodID: null, customerName: null, paymentRequestCode: null }); @@ -237,7 +237,7 @@ const HeadquartersQueue = () => { defaultSortedColumns={[{ id: 'status', desc: false }]} disableMultiSort disableSortBy={false} - columns={tooQueueColumns(moveLockFlag, showBranchFilter)} + columns={tooQueueColumns(moveLockFlag, isQueueManagementFFEnabled, showBranchFilter)} title="All moves" handleClick={handleClickNavigateToDetails} useQueries={useMovesQueueQueries} @@ -264,7 +264,7 @@ const HeadquartersQueue = () => { defaultSortedColumns={[{ id: 'age', desc: true }]} disableMultiSort disableSortBy={false} - columns={tioQueueColumns(moveLockFlag, showBranchFilter)} + columns={tioQueueColumns(moveLockFlag, isQueueManagementFFEnabled, showBranchFilter)} title="Payment requests" handleClick={handleClickNavigateToPaymentRequests} useQueries={usePaymentRequestQueueQueries} @@ -291,7 +291,7 @@ const HeadquartersQueue = () => { defaultSortedColumns={[{ id: 'closeoutInitiated', desc: false }]} disableMultiSort disableSortBy={false} - columns={closeoutColumns(moveLockFlag, inPPMCloseoutGBLOC)} + columns={closeoutColumns(moveLockFlag, inPPMCloseoutGBLOC, null, null, isQueueManagementFFEnabled)} title="Moves" handleClick={handleClickNavigateToDetails} useQueries={useServicesCounselingQueuePPMQueries} @@ -319,7 +319,7 @@ const HeadquartersQueue = () => { defaultSortedColumns={[{ id: 'submittedAt', desc: false }]} disableMultiSort disableSortBy={false} - columns={counselingColumns(moveLockFlag)} + columns={counselingColumns(moveLockFlag, null, null, isQueueManagementFFEnabled)} title="Moves" handleClick={handleClickNavigateToDetails} useQueries={useServicesCounselingQueueQueries} diff --git a/src/pages/Office/HeadquartersQueues/HeadquartersQueues.test.jsx b/src/pages/Office/HeadquartersQueues/HeadquartersQueues.test.jsx index 30b328ebe3f..f2c0f4a23c3 100644 --- a/src/pages/Office/HeadquartersQueues/HeadquartersQueues.test.jsx +++ b/src/pages/Office/HeadquartersQueues/HeadquartersQueues.test.jsx @@ -47,7 +47,7 @@ jest.mock('hooks/queries', () => ({ agency: 'AIR_FORCE', first_name: 'test first', last_name: 'test last', - dodID: '555555555', + edipi: '555555555', }, locator: 'AB5P', departmentIndicator: 'ARMY', @@ -68,7 +68,7 @@ jest.mock('hooks/queries', () => ({ agency: 'MARINES', first_name: 'test another first', last_name: 'test another last', - dodID: '4444444444', + edipi: '4444444444', }, locator: 'T12A', departmentIndicator: 'NAVY_AND_MARINES', @@ -98,7 +98,7 @@ jest.mock('hooks/queries', () => ({ agency: 'ARMY', first_name: 'test first', last_name: 'test last', - dodID: '555555555', + edipi: '555555555', }, locator: 'AB5PC', requestedMoveDate: '2021-03-01T00:00:00.000Z', @@ -115,7 +115,7 @@ jest.mock('hooks/queries', () => ({ agency: 'COAST_GUARD', first_name: 'test another first', last_name: 'test another last', - dodID: '4444444444', + edipi: '4444444444', }, locator: 'T12AR', requestedMoveDate: '2021-04-15T00:00:00.000Z', @@ -132,7 +132,7 @@ jest.mock('hooks/queries', () => ({ agency: 'MARINES', first_name: 'test third first', last_name: 'test third last', - dodID: '4444444444', + edipi: '4444444444', }, locator: 'T12MP', requestedMoveDate: '2021-04-15T00:00:00.000Z', @@ -179,7 +179,7 @@ const GetMountedComponent = (queueTypeToMount) => { return wrapper; }; -const SEARCH_OPTIONS = ['Move Code', 'DoD ID', 'Customer Name', 'Payment Request Number']; +const SEARCH_OPTIONS = ['Move Code', 'edipi', 'Customer Name', 'Payment Request Number']; describe('HeadquartersQueue', () => { afterEach(() => { @@ -199,7 +199,7 @@ describe('HeadquartersQueue', () => { const firstMove = moves.at(0); expect(firstMove.find({ 'data-testid': 'customerName-0' }).text()).toBe('test last, test first'); - expect(firstMove.find({ 'data-testid': 'dodID-0' }).text()).toBe('555555555'); + expect(firstMove.find({ 'data-testid': 'edipi-0' }).text()).toBe('555555555'); expect(firstMove.find({ 'data-testid': 'status-0' }).text()).toBe('New move'); expect(firstMove.find({ 'data-testid': 'locator-0' }).text()).toBe('AB5P'); expect(firstMove.find({ 'data-testid': 'branch-0' }).text()).toBe('Air Force'); @@ -211,7 +211,7 @@ describe('HeadquartersQueue', () => { const secondMove = moves.at(1); expect(secondMove.find({ 'data-testid': 'customerName-1' }).text()).toBe('test another last, test another first'); - expect(secondMove.find({ 'data-testid': 'dodID-1' }).text()).toBe('4444444444'); + expect(secondMove.find({ 'data-testid': 'edipi-1' }).text()).toBe('4444444444'); expect(secondMove.find({ 'data-testid': 'status-1' }).text()).toBe('Move approved'); expect(secondMove.find({ 'data-testid': 'locator-1' }).text()).toBe('T12A'); expect(secondMove.find({ 'data-testid': 'branch-1' }).text()).toBe('Marine Corps'); diff --git a/src/pages/Office/MoveDetails/MoveDetails.jsx b/src/pages/Office/MoveDetails/MoveDetails.jsx index 7f1f53212fa..612054e851d 100644 --- a/src/pages/Office/MoveDetails/MoveDetails.jsx +++ b/src/pages/Office/MoveDetails/MoveDetails.jsx @@ -86,8 +86,19 @@ const MoveDetails = ({ const navigate = useNavigate(); - const { move, customerData, order, closeoutOffice, mtoShipments, mtoServiceItems, isLoading, isError } = - useMoveDetailsQueries(moveCode); + const { + move, + customerData, + order, + orderDocuments, + closeoutOffice, + mtoShipments, + mtoServiceItems, + isLoading, + isError, + } = useMoveDetailsQueries(moveCode); + + const validOrdersDocuments = Object.values(orderDocuments || {})?.filter((file) => !file.deletedAt); // for now we are only showing dest type on retiree and separatee orders let isRetirementOrSeparation = false; @@ -296,16 +307,17 @@ const MoveDetails = ({ // using useMemo here due to this being used in a useEffect // using useMemo prevents the useEffect from being rendered on ever render by memoizing the object - // so that it only recognizes the change when the orders object changes + // so that it only recognizes the change when the orders or validOrdersDocuments objects change const requiredOrdersInfo = useMemo( () => ({ ordersNumber: order?.order_number || '', ordersType: order?.order_type || '', ordersTypeDetail: order?.order_type_detail || '', + ordersDocuments: validOrdersDocuments?.length ? validOrdersDocuments : null, tacMDC: order?.tac || '', departmentIndicator: order?.department_indicator || '', }), - [order], + [order, validOrdersDocuments], ); // Keep num of missing orders info synced up @@ -338,6 +350,7 @@ const MoveDetails = ({ ordersNumber: order.order_number, ordersType: order.order_type, ordersTypeDetail: order.order_type_detail, + ordersDocuments: validOrdersDocuments?.length ? validOrdersDocuments : null, uploadedAmendedOrderID: order.uploadedAmendedOrderID, amendedOrdersAcknowledgedAt: order.amendedOrdersAcknowledgedAt, tacMDC: order.tac, diff --git a/src/pages/Office/MoveDetails/MoveDetails.test.jsx b/src/pages/Office/MoveDetails/MoveDetails.test.jsx index a6ad1a97aaf..2f5cdea8752 100644 --- a/src/pages/Office/MoveDetails/MoveDetails.test.jsx +++ b/src/pages/Office/MoveDetails/MoveDetails.test.jsx @@ -66,6 +66,19 @@ const requestedMoveDetailsQuery = { postalCode: '92310', }, }, + orderDocuments: { + 'c0a22a98-a806-47a2-ab54-2dac938667b3': { + bytes: 2202009, + contentType: 'application/pdf', + createdAt: '2024-10-23T16:31:21.085Z', + filename: 'testFile.pdf', + id: 'c0a22a98-a806-47a2-ab54-2dac938667b3', + status: 'PROCESSING', + updatedAt: '2024-10-23T16:31:21.085Z', + uploadType: 'USER', + url: '/storage/USER/uploads/c0a22a98-a806-47a2-ab54-2dac938667b3?contentType=application%2Fpdf', + }, + }, customer: { agency: 'ARMY', backup_contact: { @@ -268,6 +281,19 @@ const requestedMoveDetailsQueryRetiree = { ntsTac: '1111', ntsSac: '2222', }, + orderDocuments: { + 'c0a22a98-a806-47a2-ab54-2dac938667b3': { + bytes: 2202009, + contentType: 'application/pdf', + createdAt: '2024-10-23T16:31:21.085Z', + filename: 'testFile.pdf', + id: 'c0a22a98-a806-47a2-ab54-2dac938667b3', + status: 'PROCESSING', + updatedAt: '2024-10-23T16:31:21.085Z', + uploadType: 'USER', + url: '/storage/USER/uploads/c0a22a98-a806-47a2-ab54-2dac938667b3?contentType=application%2Fpdf', + }, + }, mtoShipments: [ { customerRemarks: 'please treat gently', @@ -426,6 +452,19 @@ const requestedMoveDetailsAmendedOrdersQuery = { uploadedAmendedOrderID: '3', tac: '9999', }, + orderDocuments: { + 'c0a22a98-a806-47a2-ab54-2dac938667b3': { + bytes: 2202009, + contentType: 'application/pdf', + createdAt: '2024-10-23T16:31:21.085Z', + filename: 'testFile.pdf', + id: 'c0a22a98-a806-47a2-ab54-2dac938667b3', + status: 'PROCESSING', + updatedAt: '2024-10-23T16:31:21.085Z', + uploadType: 'USER', + url: '/storage/USER/uploads/c0a22a98-a806-47a2-ab54-2dac938667b3?contentType=application%2Fpdf', + }, + }, mtoShipments: [ { customerRemarks: 'please treat gently', @@ -576,6 +615,7 @@ const requestedMoveDetailsMissingInfoQuery = { totalWeight: 8000, }, }, + orderDocuments: undefined, mtoShipments: [ { customerRemarks: 'please treat gently', @@ -1025,6 +1065,32 @@ describe('MoveDetails page', () => { }); }); + describe('When required Orders Document is missing', () => { + useMoveDetailsQueries.mockReturnValue(requestedMoveDetailsMissingInfoQuery); + + const mockSetMissingOrdersInfoCount = jest.fn(); + + mount( + + + , + ); + + it('missing info count matches missing info from queries', () => { + expect(mockSetMissingOrdersInfoCount).toHaveBeenCalledTimes(1); + // 5 order values missing + 1 'upload' missing + expect(mockSetMissingOrdersInfoCount).toHaveBeenCalledWith(6); + }); + }); + describe('When required shipment information (like TAC) is missing', () => { it('renders an error indicator in the sidebar', async () => { useMoveDetailsQueries.mockReturnValue(requestedMoveDetailsMissingInfoQuery); @@ -1047,7 +1113,7 @@ describe('MoveDetails page', () => { }); }); - describe('When a shipment has a pending destination address update requested by the Prime', () => { + describe('When a shipment has a pending delivery address update requested by the Prime', () => { it('renders an alert indicator in the sidebar', async () => { useMoveDetailsQueries.mockReturnValue(requestedMoveDetailsMissingInfoQuery); diff --git a/src/pages/Office/MoveQueue/MoveQueue.jsx b/src/pages/Office/MoveQueue/MoveQueue.jsx index 01226ba4e5c..87ef31b4386 100644 --- a/src/pages/Office/MoveQueue/MoveQueue.jsx +++ b/src/pages/Office/MoveQueue/MoveQueue.jsx @@ -69,11 +69,11 @@ export const columns = (moveLockFlag, isQueueManagementEnabled, showBranchFilter }, }, ), - createHeader('DoD ID', 'customer.dodID', { - id: 'dodID', + createHeader('DoD ID', 'customer.edipi', { + id: 'edipi', isFilterable: true, exportValue: (row) => { - return row.customer.dodID; + return row.customer.edipi; }, }), createHeader('EMPLID', 'customer.emplid', { @@ -143,6 +143,10 @@ export const columns = (moveLockFlag, isQueueManagementEnabled, showBranchFilter }, }), createHeader('Origin GBLOC', 'originGBLOC', { disableSortBy: true }), + createHeader('Counseling office', 'counselingOffice', { + id: 'counselingOffice', + isFilterable: true, + }), ]; if (isQueueManagementEnabled) cols.push( diff --git a/src/pages/Office/MoveQueue/MoveQueue.test.jsx b/src/pages/Office/MoveQueue/MoveQueue.test.jsx index 8cce5ad31d9..5ce4cee9c89 100644 --- a/src/pages/Office/MoveQueue/MoveQueue.test.jsx +++ b/src/pages/Office/MoveQueue/MoveQueue.test.jsx @@ -30,7 +30,7 @@ const moveData = [ agency: 'AIR_FORCE', first_name: 'test first', last_name: 'test last', - dodID: '555555555', + edipi: '555555555', }, locator: 'AB5P', departmentIndicator: 'ARMY', @@ -40,6 +40,7 @@ const moveData = [ name: 'Area 51', }, originGBLOC: 'EEEE', + counselingOffice: '67592323-fc7e-4b35-83a7-57faa53b7acf', requestedMoveDate: '2023-02-10', appearedInTooAt: '2023-02-10T00:00:00.000Z', lockExpiresAt: '2099-02-10T00:00:00.000Z', @@ -68,7 +69,7 @@ const moveData = [ agency: 'COAST_GUARD', first_name: 'test another first', last_name: 'test another last', - dodID: '4444444444', + edipi: '4444444444', emplid: '4589652', }, locator: 'T12A', @@ -79,6 +80,7 @@ const moveData = [ name: 'Los Alamos', }, originGBLOC: 'EEEE', + counselingOffice: '67592323-fc7e-4b35-83a7-57faa53b7acf', requestedMoveDate: '2023-02-12', appearedInTooAt: '2023-02-12T00:00:00.000Z', assignedTo: { @@ -105,7 +107,7 @@ const moveData = [ agency: 'Marine Corps', first_name: 'will', last_name: 'robinson', - dodID: '6666666666', + edipi: '6666666666', }, locator: 'PREP', departmentIndicator: 'MARINES', @@ -115,6 +117,7 @@ const moveData = [ name: 'Area 52', }, originGBLOC: 'EEEE', + counselingOffice: '67592323-fc7e-4b35-83a7-57faa53b7acf', requestedMoveDate: '2023-03-12', appearedInTooAt: '2023-03-12T00:00:00.000Z', lockExpiresAt: '2099-03-12T00:00:00.000Z', @@ -193,8 +196,8 @@ describe('MoveQueue', () => { expect(currentMove.find({ 'data-testid': `customerName-${currentIndex}` }).text()).toBe( `${moveData[currentIndex].customer.last_name}, ${moveData[currentIndex].customer.first_name}`, ); - expect(currentMove.find({ 'data-testid': `dodID-${currentIndex}` }).text()).toBe( - moveData[currentIndex].customer.dodID, + expect(currentMove.find({ 'data-testid': `edipi-${currentIndex}` }).text()).toBe( + moveData[currentIndex].customer.edipi, ); expect(currentMove.find({ 'data-testid': `status-${currentIndex}` }).text()).toBe('New move'); expect(currentMove.find({ 'data-testid': `locator-${currentIndex}` }).text()).toBe(moveData[currentIndex].locator); @@ -210,6 +213,9 @@ describe('MoveQueue', () => { expect(currentMove.find({ 'data-testid': `originGBLOC-${currentIndex}` }).text()).toBe( moveData[currentIndex].originGBLOC, ); + expect(currentMove.find({ 'data-testid': `counselingOffice-${currentIndex}` }).text()).toBe( + moveData[currentIndex].counselingOffice, + ); expect(currentMove.find({ 'data-testid': `requestedMoveDate-${currentIndex}` }).text()).toBe('10 Feb 2023'); expect(currentMove.find({ 'data-testid': `appearedInTooAt-${currentIndex}` }).text()).toBe('10 Feb 2023'); @@ -221,8 +227,8 @@ describe('MoveQueue', () => { expect(currentMove.find({ 'data-testid': `customerName-${currentIndex}` }).text()).toBe( `${moveData[currentIndex].customer.last_name}, ${moveData[currentIndex].customer.first_name}`, ); - expect(currentMove.find({ 'data-testid': `dodID-${currentIndex}` }).text()).toBe( - moveData[currentIndex].customer.dodID, + expect(currentMove.find({ 'data-testid': `edipi-${currentIndex}` }).text()).toBe( + moveData[currentIndex].customer.edipi, ); expect(currentMove.find({ 'data-testid': `emplid-${currentIndex}` }).text()).toBe( moveData[currentIndex].customer.emplid, @@ -241,6 +247,9 @@ describe('MoveQueue', () => { expect(currentMove.find({ 'data-testid': `originGBLOC-${currentIndex}` }).text()).toBe( moveData[currentIndex].originGBLOC, ); + expect(currentMove.find({ 'data-testid': `counselingOffice-${currentIndex}` }).text()).toBe( + moveData[currentIndex].counselingOffice, + ); expect(currentMove.find({ 'data-testid': `requestedMoveDate-${currentIndex}` }).text()).toBe('12 Feb 2023'); expect(currentMove.find({ 'data-testid': `appearedInTooAt-${currentIndex}` }).text()).toBe('12 Feb 2023'); @@ -249,8 +258,8 @@ describe('MoveQueue', () => { expect(currentMove.find({ 'data-testid': `customerName-${currentIndex}` }).text()).toBe( `${moveData[currentIndex].customer.last_name}, ${moveData[currentIndex].customer.first_name}`, ); - expect(currentMove.find({ 'data-testid': `dodID-${currentIndex}` }).text()).toBe( - moveData[currentIndex].customer.dodID, + expect(currentMove.find({ 'data-testid': `edipi-${currentIndex}` }).text()).toBe( + moveData[currentIndex].customer.edipi, ); expect(currentMove.find({ 'data-testid': `status-${currentIndex}` }).text()).toBe('New move'); expect(currentMove.find({ 'data-testid': `locator-${currentIndex}` }).text()).toBe(moveData[currentIndex].locator); @@ -266,6 +275,9 @@ describe('MoveQueue', () => { expect(currentMove.find({ 'data-testid': `originGBLOC-${currentIndex}` }).text()).toBe( moveData[currentIndex].originGBLOC, ); + expect(currentMove.find({ 'data-testid': `counselingOffice-${currentIndex}` }).text()).toBe( + moveData[currentIndex].counselingOffice, + ); expect(currentMove.find({ 'data-testid': `requestedMoveDate-${currentIndex}` }).text()).toBe('12 Mar 2023'); expect(currentMove.find({ 'data-testid': `appearedInTooAt-${currentIndex}` }).text()).toBe('12 Mar 2023'); expect(currentMove.find({ 'data-testid': `assignedTo-${currentIndex}` }).text()).toBe('John, Jimmy'); diff --git a/src/pages/Office/MoveTaskOrder/MoveTaskOrder.jsx b/src/pages/Office/MoveTaskOrder/MoveTaskOrder.jsx index bf6239d2c9e..e02b258a4ae 100644 --- a/src/pages/Office/MoveTaskOrder/MoveTaskOrder.jsx +++ b/src/pages/Office/MoveTaskOrder/MoveTaskOrder.jsx @@ -177,7 +177,9 @@ export const MoveTaskOrder = (props) => { status: item.status, estimatedPrice: item.estimatedPrice, standaloneCrate: item.standaloneCrate, + externalCrate: item.externalCrate, lockedPricedCents: item.lockedPriceCents, + market: item.market, }; if (serviceItemsForShipment[`${newItem.mtoShipmentID}`]) { diff --git a/src/pages/Office/PaymentRequestQueue/PaymentRequestQueue.jsx b/src/pages/Office/PaymentRequestQueue/PaymentRequestQueue.jsx index 74511206562..e93cba3ff65 100644 --- a/src/pages/Office/PaymentRequestQueue/PaymentRequestQueue.jsx +++ b/src/pages/Office/PaymentRequestQueue/PaymentRequestQueue.jsx @@ -73,11 +73,11 @@ export const columns = (moveLockFlag, isQueueManagementEnabled, showBranchFilter }, }, ), - createHeader('DoD ID', 'customer.dodID', { - id: 'dodID', + createHeader('DoD ID', 'customer.edipi', { + id: 'edipi', isFilterable: true, exportValue: (row) => { - return row.customer.dodID; + return row.customer.edipi; }, }), createHeader('EMPLID', 'customer.emplid', { @@ -134,6 +134,10 @@ export const columns = (moveLockFlag, isQueueManagementEnabled, showBranchFilter }, ), createHeader('Origin GBLOC', 'originGBLOC', { disableSortBy: true }), + createHeader('Counseling office', 'counselingOffice', { + id: 'counselingOffice', + isFilterable: true, + }), createHeader('Origin Duty Location', 'originDutyLocation.name', { id: 'originDutyLocation', isFilterable: true, diff --git a/src/pages/Office/PaymentRequestQueue/PaymentRequestQueue.test.jsx b/src/pages/Office/PaymentRequestQueue/PaymentRequestQueue.test.jsx index 41ee979d30a..eac3b10faa2 100644 --- a/src/pages/Office/PaymentRequestQueue/PaymentRequestQueue.test.jsx +++ b/src/pages/Office/PaymentRequestQueue/PaymentRequestQueue.test.jsx @@ -44,7 +44,7 @@ jest.mock('hooks/queries', () => ({ }, customer: { agency: 'COAST_GUARD', - dodID: '3305957632', + edipi: '3305957632', emplid: '1253694', eTag: 'MjAyMC0xMC0xNVQyMzo0ODozNC41ODQxOTZa', email: 'leo_spaceman_sm@example.com', @@ -59,6 +59,7 @@ jest.mock('hooks/queries', () => ({ locator: 'R993T7', moveID: '5d4b25bb-eb04-4c03-9a81-ee0398cb779e', originGBLOC: 'LKNQ', + counselingOffice: '67592323-fc7e-4b35-83a7-57faa53b7acf', status: 'PENDING', submittedAt: '2020-10-15T23:48:35.420Z', originDutyLocation: { @@ -80,7 +81,6 @@ jest.mock('hooks/queries', () => ({ agency: 'NAVY', cacValidated: true, eTag: 'MjAyNC0xMC0xMFQyMjoyNDo1My4xNjYxNjNa', - dodID: '1234567890', edipi: '1234567', email: '20241010222310-ae019978709c@example.com', first_name: 'Ooga', @@ -98,6 +98,7 @@ jest.mock('hooks/queries', () => ({ locator: '0OOGAB', moveID: '8f29e53d-e816-4476-bfee-f38d07b94f2d', originGBLOC: 'LKNQ', + counselingOffice: '67592323-fc7e-4b35-83a7-57faa53b7acf', status: 'PENDING', submittedAt: '2020-10-17T23:48:35.420Z', originDutyLocation: { @@ -122,7 +123,7 @@ jest.mock('hooks/queries', () => ({ age: 0.8477863, customer: { agency: 'ARMY', - dodID: '3305957632', + edipi: '3305957632', eTag: 'MjAyMC0xMC0xNVQyMzo0ODozNC41ODQxOTZa', email: 'leo_spaceman_sm@example.com', first_name: 'Leo', @@ -186,7 +187,7 @@ describe('PaymentRequestQueue', () => { const firstPaymentRequest = paymentRequests.at(0); expect(firstPaymentRequest.find('td.customerName').text()).toBe('Spacemen, Leo'); - expect(firstPaymentRequest.find('td.dodID').text()).toBe('3305957632'); + expect(firstPaymentRequest.find('td.edipi').text()).toBe('3305957632'); expect(firstPaymentRequest.find('td.emplid').text()).toBe('1253694'); expect(firstPaymentRequest.find('td.status').text()).toBe('Payment requested'); expect(firstPaymentRequest.find('td.age').text()).toBe('Less than 1 day'); @@ -195,10 +196,11 @@ describe('PaymentRequestQueue', () => { expect(firstPaymentRequest.find('td.branch').text()).toBe('Coast Guard'); expect(firstPaymentRequest.find('td.originGBLOC').text()).toBe('LKNQ'); expect(firstPaymentRequest.find('td.originDutyLocation').text()).toBe('Scott AFB'); + expect(firstPaymentRequest.find('td.counselingOffice').text()).toBe('67592323-fc7e-4b35-83a7-57faa53b7acf'); const secondPaymentRequest = paymentRequests.at(1); expect(secondPaymentRequest.find('td.customerName').text()).toBe('Booga, Ooga'); - expect(secondPaymentRequest.find('td.dodID').text()).toBe('1234567890'); + expect(secondPaymentRequest.find('td.edipi').text()).toBe('1234567'); expect(secondPaymentRequest.find('td.emplid').text()).toBe(''); expect(secondPaymentRequest.find('td.status').text()).toBe('Payment requested'); expect(secondPaymentRequest.find('td.age').text()).toBe('Less than 1 day'); @@ -207,6 +209,7 @@ describe('PaymentRequestQueue', () => { expect(secondPaymentRequest.find('td.branch').text()).toBe('Navy'); expect(secondPaymentRequest.find('td.originGBLOC').text()).toBe('LKNQ'); expect(secondPaymentRequest.find('td.originDutyLocation').text()).toBe('Scott AFB'); + expect(secondPaymentRequest.find('td.counselingOffice').text()).toBe('67592323-fc7e-4b35-83a7-57faa53b7acf'); }); it('renders the table with data and expected values with queue management ff', async () => { diff --git a/src/pages/Office/ServicesCounselingAddShipment/ServicesCounselingAddShipment.test.jsx b/src/pages/Office/ServicesCounselingAddShipment/ServicesCounselingAddShipment.test.jsx index a70c3ecc29b..5d9a177ddb4 100644 --- a/src/pages/Office/ServicesCounselingAddShipment/ServicesCounselingAddShipment.test.jsx +++ b/src/pages/Office/ServicesCounselingAddShipment/ServicesCounselingAddShipment.test.jsx @@ -210,7 +210,7 @@ describe('ServicesCounselingAddShipment component', () => { expect(saveButton).toBeDisabled(); }); - expect(screen.getByLabelText('Use current address')).not.toBeChecked(); + expect(screen.getByLabelText('Use pickup address')).not.toBeChecked(); await userEvent.type(screen.getAllByLabelText('Address 1')[0], '812 S 129th St'); await userEvent.type(screen.getAllByLabelText('City')[0], 'San Antonio'); diff --git a/src/pages/Office/ServicesCounselingMoveDetails/ServicesCounselingMoveDetails.jsx b/src/pages/Office/ServicesCounselingMoveDetails/ServicesCounselingMoveDetails.jsx index ad33e385f4e..b12bac8a42d 100644 --- a/src/pages/Office/ServicesCounselingMoveDetails/ServicesCounselingMoveDetails.jsx +++ b/src/pages/Office/ServicesCounselingMoveDetails/ServicesCounselingMoveDetails.jsx @@ -30,6 +30,7 @@ import shipmentCardsStyles from 'styles/shipmentCards.module.scss'; import LeftNav from 'components/LeftNav/LeftNav'; import LeftNavTag from 'components/LeftNavTag/LeftNavTag'; import LoadingPlaceholder from 'shared/LoadingPlaceholder'; +import Inaccessible from 'shared/Inaccessible'; import SomethingWentWrong from 'shared/SomethingWentWrong'; import { AlertStateShape } from 'types/alert'; import formattedCustomerName from 'utils/formattedCustomerName'; @@ -72,8 +73,11 @@ const ServicesCounselingMoveDetails = ({ }); const hasDocuments = documentsForViewer?.length > 0; - const { order, customerData, move, closeoutOffice, mtoShipments, isLoading, isError } = + const { order, orderDocuments, customerData, move, closeoutOffice, mtoShipments, isLoading, isError, errors } = useMoveDetailsQueries(moveCode); + + const validOrdersDocuments = Object.values(orderDocuments || {})?.filter((file) => !file.deletedAt); + const { customer, entitlement: allowances } = order; const moveWeightTotal = calculateWeightRequested(mtoShipments); @@ -89,7 +93,7 @@ const ServicesCounselingMoveDetails = ({ }, []); // nts defaults show preferred pickup date and pickup address, flagged items when collapsed - // ntsr defaults shows preferred delivery date, storage facility address, destination address, flagged items when collapsed + // ntsr defaults shows preferred delivery date, storage facility address, delivery address, flagged items when collapsed const showWhenCollapsed = { HHG_INTO_NTS_DOMESTIC: ['counselorRemarks'], HHG_OUTOF_NTS_DOMESTIC: ['counselorRemarks'], @@ -334,7 +338,7 @@ const ServicesCounselingMoveDetails = ({ const customerInfo = { name: formattedCustomerName(customer.last_name, customer.first_name, customer.suffix, customer.middle_name), agency: customer.agency, - dodId: customer.dodID, + edipi: customer.edipi, emplid: customer.emplid, phone: customer.phone, altPhone: customer.secondaryTelephone, @@ -366,6 +370,7 @@ const ServicesCounselingMoveDetails = ({ ordersType: order.order_type, ordersNumber: order.order_number, ordersTypeDetail: order.order_type_detail, + ordersDocuments: validOrdersDocuments?.length ? validOrdersDocuments : null, tacMDC: order.tac, sacSDN: order.sac, NTStac: order.ntsTac, @@ -383,16 +388,17 @@ const ServicesCounselingMoveDetails = ({ // using useMemo here due to this being used in a useEffect // using useMemo prevents the useEffect from being rendered on ever render by memoizing the object - // so that it only recognizes the change when the orders object changes + // so that it only recognizes the change when the orders or validOrdersDocuments objects change const requiredOrdersInfo = useMemo( () => ({ ordersNumber: order?.order_number || '', ordersType: order?.order_type || '', ordersTypeDetail: order?.order_type_detail || '', + ordersDocuments: validOrdersDocuments?.length ? validOrdersDocuments : null, tacMDC: order?.tac || '', departmentIndicator: order?.department_indicator || '', }), - [order], + [order, validOrdersDocuments], ); const handleButtonDropdownChange = (e) => { @@ -488,7 +494,9 @@ const ServicesCounselingMoveDetails = ({ }, [order, requiredOrdersInfo, setMissingOrdersInfoCount]); if (isLoading) return ; - if (isError) return ; + if (isError) { + return errors?.[0]?.response?.body?.message ? : ; + } const handleShowSubmitMoveModal = () => { setIsSubmitModalVisible(true); diff --git a/src/pages/Office/ServicesCounselingMoveDetails/ServicesCounselingMoveDetails.test.jsx b/src/pages/Office/ServicesCounselingMoveDetails/ServicesCounselingMoveDetails.test.jsx index d3484679fe8..e02dbf7321f 100644 --- a/src/pages/Office/ServicesCounselingMoveDetails/ServicesCounselingMoveDetails.test.jsx +++ b/src/pages/Office/ServicesCounselingMoveDetails/ServicesCounselingMoveDetails.test.jsx @@ -7,6 +7,7 @@ import userEvent from '@testing-library/user-event'; import ServicesCounselingMoveDetails from './ServicesCounselingMoveDetails'; import MOVE_STATUSES from 'constants/moves'; +import { ERROR_RETURN_VALUE, LOADING_RETURN_VALUE, INACCESSIBLE_RETURN_VALUE } from 'utils/test/api'; import { ORDERS_TYPE, ORDERS_TYPE_DETAILS } from 'constants/orders'; import { servicesCounselingRoutes } from 'constants/routes'; import { permissionTypes } from 'constants/permissions'; @@ -218,6 +219,7 @@ const orderMissingRequiredInfo = { totalDependents: 1, totalWeight: 8000, }, + orderDocuments: undefined, order_number: '', order_type: ORDERS_TYPE.PERMANENT_CHANGE_OF_STATION, order_type_detail: '', @@ -308,12 +310,29 @@ const newMoveDetailsQuery = { department_indicator: 'ARMY', tac: '9999', }, + orderDocuments: { + z: { + bytes: 2202009, + contentType: 'application/pdf', + createdAt: '2024-10-23T16:31:21.085Z', + filename: 'testFile.pdf', + id: 'z', + status: 'PROCESSING', + updatedAt: '2024-10-23T16:31:21.085Z', + uploadType: 'USER', + url: '/storage/USER/uploads/z?contentType=application%2Fpdf', + }, + }, mtoShipments, mtoServiceItems: [], mtoAgents: [], isLoading: false, isError: false, isSuccess: true, +}; + +const newOrdersDocumentQuery = { + ...newMoveDetailsQuery, upload: { z: { id: 'z', @@ -548,25 +567,11 @@ const renderComponent = (props, permissions = [permissionTypes.updateShipment, p ); }; -const loadingReturnValue = { - ...newMoveDetailsQuery, - isLoading: true, - isError: false, - isSuccess: false, -}; - -const errorReturnValue = { - ...newMoveDetailsQuery, - isLoading: false, - isError: true, - isSuccess: false, -}; - describe('MoveDetails page', () => { describe('check loading and error component states', () => { it('renders the Loading Placeholder when the query is still loading', async () => { - useMoveDetailsQueries.mockReturnValue(loadingReturnValue); - useOrdersDocumentQueries.mockReturnValue(loadingReturnValue); + useMoveDetailsQueries.mockReturnValue({ ...newMoveDetailsQuery, ...LOADING_RETURN_VALUE }); + useOrdersDocumentQueries.mockReturnValue({ ...newMoveDetailsQuery, ...LOADING_RETURN_VALUE }); renderComponent(); @@ -575,20 +580,30 @@ describe('MoveDetails page', () => { }); it('renders the Something Went Wrong component when the query errors', async () => { - useMoveDetailsQueries.mockReturnValue(errorReturnValue); - useOrdersDocumentQueries.mockReturnValue(errorReturnValue); + useMoveDetailsQueries.mockReturnValue({ ...newMoveDetailsQuery, ...ERROR_RETURN_VALUE }); + useOrdersDocumentQueries.mockReturnValue({ ...newMoveDetailsQuery, ...ERROR_RETURN_VALUE }); renderComponent(); const errorMessage = await screen.getByText(/Something went wrong./); expect(errorMessage).toBeInTheDocument(); }); + + it('renders the Inaccessible component when the query returns an inaccessible response', async () => { + useMoveDetailsQueries.mockReturnValue({ ...newMoveDetailsQuery, ...INACCESSIBLE_RETURN_VALUE }); + useOrdersDocumentQueries.mockReturnValue({ ...newMoveDetailsQuery, ...ERROR_RETURN_VALUE }); + + renderComponent(); + + const errorMessage = await screen.getByText(/Page is not accessible./); + expect(errorMessage).toBeInTheDocument(); + }); }); describe('Basic rendering', () => { it('renders the h1', async () => { useMoveDetailsQueries.mockReturnValue(newMoveDetailsQuery); - useOrdersDocumentQueries.mockReturnValue(newMoveDetailsQuery); + useOrdersDocumentQueries.mockReturnValue(newOrdersDocumentQuery); renderComponent(); @@ -599,7 +614,7 @@ describe('MoveDetails page', () => { 'renders side navigation for section %s', async (sectionName) => { useMoveDetailsQueries.mockReturnValue(newMoveDetailsQuery); - useOrdersDocumentQueries.mockReturnValue(newMoveDetailsQuery); + useOrdersDocumentQueries.mockReturnValue(newOrdersDocumentQuery); renderComponent(); @@ -614,7 +629,7 @@ describe('MoveDetails page', () => { }; useMoveDetailsQueries.mockReturnValue(moveDetailsQuery); - useOrdersDocumentQueries.mockReturnValue(moveDetailsQuery); + useOrdersDocumentQueries.mockReturnValue(newOrdersDocumentQuery); renderComponent(); @@ -629,24 +644,7 @@ describe('MoveDetails page', () => { }; useMoveDetailsQueries.mockReturnValue(moveDetailsQuery); - useOrdersDocumentQueries.mockReturnValue(moveDetailsQuery); - - const mockSetMissingOrdersInfoCount = jest.fn(); - renderComponent({ setMissingOrdersInfoCount: mockSetMissingOrdersInfoCount }); - - // Should have called `setMissingOrdersInfoCount` with 4 missing fields - expect(mockSetMissingOrdersInfoCount).toHaveBeenCalledTimes(1); - expect(mockSetMissingOrdersInfoCount).toHaveBeenCalledWith(4); - }); - - it('shares the number of missing orders information', () => { - const moveDetailsQuery = { - ...newMoveDetailsQuery, - order: orderMissingRequiredInfo, - }; - - useMoveDetailsQueries.mockReturnValue(moveDetailsQuery); - useOrdersDocumentQueries.mockReturnValue(moveDetailsQuery); + useOrdersDocumentQueries.mockReturnValue(newOrdersDocumentQuery); const mockSetMissingOrdersInfoCount = jest.fn(); renderComponent({ setMissingOrdersInfoCount: mockSetMissingOrdersInfoCount }); @@ -659,7 +657,7 @@ describe('MoveDetails page', () => { /* eslint-disable camelcase */ it('renders shipments info', async () => { useMoveDetailsQueries.mockReturnValue(newMoveDetailsQuery); - useOrdersDocumentQueries.mockReturnValue(newMoveDetailsQuery); + useOrdersDocumentQueries.mockReturnValue(newOrdersDocumentQuery); renderComponent(); @@ -677,9 +675,9 @@ describe('MoveDetails page', () => { ); } - const originAddressTerms = screen.getAllByText('Origin address'); + const originAddressTerms = screen.getAllByText('Pickup Address'); - expect(originAddressTerms.length).toBe(2); + expect(originAddressTerms.length).toBe(3); for (let i = 0; i < 2; i += 1) { const { streetAddress1, city, state, postalCode } = newMoveDetailsQuery.mtoShipments[i].pickupAddress; @@ -692,7 +690,7 @@ describe('MoveDetails page', () => { expect(addressText).toContain(postalCode); } - const destinationAddressTerms = screen.getAllByText('Destination address'); + const destinationAddressTerms = screen.getAllByText('Delivery Address'); expect(destinationAddressTerms.length).toBe(2); @@ -720,14 +718,14 @@ describe('MoveDetails page', () => { it('renders review documents button', async () => { useMoveDetailsQueries.mockReturnValue(ppmShipmentQuery); - useOrdersDocumentQueries.mockReturnValue(ppmShipmentQuery); + useOrdersDocumentQueries.mockReturnValue(newOrdersDocumentQuery); renderComponent(); expect(screen.getAllByRole('button', { name: 'Review documents' }).length).toBe(2); }); it('renders review shipment weights button with correct path', async () => { useMoveDetailsQueries.mockReturnValue(ppmShipmentQuery); - useOrdersDocumentQueries.mockReturnValue(ppmShipmentQuery); + useOrdersDocumentQueries.mockReturnValue(newOrdersDocumentQuery); const path = generatePath(servicesCounselingRoutes.BASE_REVIEW_SHIPMENT_WEIGHTS_PATH, { moveCode: mockRequestedMoveCode, }); @@ -741,7 +739,7 @@ describe('MoveDetails page', () => { it('shows an error if there is an advance requested and no advance status for a PPM shipment', async () => { useMoveDetailsQueries.mockReturnValue(ppmShipmentQuery); - useOrdersDocumentQueries.mockReturnValue(ppmShipmentQuery); + useOrdersDocumentQueries.mockReturnValue(newOrdersDocumentQuery); renderComponent(); const advanceStatusElement = screen.getAllByTestId('advanceRequestStatus')[0]; @@ -750,7 +748,7 @@ describe('MoveDetails page', () => { it('renders the excess weight alert and additional shipment concern if there is excess weight', async () => { useMoveDetailsQueries.mockReturnValue(ppmShipmentQuery); - useOrdersDocumentQueries.mockReturnValue(ppmShipmentQuery); + useOrdersDocumentQueries.mockReturnValue(newOrdersDocumentQuery); renderComponent(); const excessWeightAlert = screen.getByText( 'This move has excess weight. Review PPM weight ticket documents to resolve.', @@ -762,13 +760,13 @@ describe('MoveDetails page', () => { it('renders the allowances error message when allowances are less than moves values', async () => { useMoveDetailsQueries.mockReturnValue(ppmShipmentQuery); - useOrdersDocumentQueries.mockReturnValue(ppmShipmentQuery); + useOrdersDocumentQueries.mockReturnValue(newOrdersDocumentQuery); renderComponent(); const allowanceError = screen.getByTestId('allowanceError'); expect(allowanceError).toBeInTheDocument(); }); - it('renders shipments info even if destination address is missing', async () => { + it('renders shipments info even if delivery address is missing', async () => { const moveDetailsQuery = { ...newMoveDetailsQuery, mtoShipments: [ @@ -781,11 +779,11 @@ describe('MoveDetails page', () => { delete moveDetailsQuery.mtoShipments[0].destinationAddress; useMoveDetailsQueries.mockReturnValue(moveDetailsQuery); - useOrdersDocumentQueries.mockReturnValue(moveDetailsQuery); + useOrdersDocumentQueries.mockReturnValue(newOrdersDocumentQuery); renderComponent(); - const destinationAddressTerms = screen.getAllByText('Destination address'); + const destinationAddressTerms = screen.getAllByText('Delivery Address'); expect(destinationAddressTerms.length).toBe(2); @@ -806,7 +804,7 @@ describe('MoveDetails page', () => { it('renders customer info', async () => { useMoveDetailsQueries.mockReturnValue(newMoveDetailsQuery); - useOrdersDocumentQueries.mockReturnValue(newMoveDetailsQuery); + useOrdersDocumentQueries.mockReturnValue(newOrdersDocumentQuery); renderComponent(); @@ -824,7 +822,7 @@ describe('MoveDetails page', () => { describe('new move - needs service counseling', () => { it('submit move details button is on page', async () => { useMoveDetailsQueries.mockReturnValue(newMoveDetailsQuery); - useOrdersDocumentQueries.mockReturnValue(newMoveDetailsQuery); + useOrdersDocumentQueries.mockReturnValue(newOrdersDocumentQuery); renderComponent(); @@ -833,7 +831,7 @@ describe('MoveDetails page', () => { it('submit move details button is disabled when there are no shipments', async () => { useMoveDetailsQueries.mockReturnValue({ ...newMoveDetailsQuery, mtoShipments: [] }); - useOrdersDocumentQueries.mockReturnValue({ ...newMoveDetailsQuery, mtoShipments: [] }); + useOrdersDocumentQueries.mockReturnValue({ ...newOrdersDocumentQuery, mtoShipments: [] }); renderComponent(); @@ -863,9 +861,9 @@ describe('MoveDetails page', () => { }, }); useOrdersDocumentQueries.mockReturnValue({ - ...newMoveDetailsQuery, + ...newOrdersDocumentQuery, order: { - ...newMoveDetailsQuery.order, + ...newOrdersDocumentQuery.order, department_indicator: undefined, }, }); @@ -888,7 +886,7 @@ describe('MoveDetails page', () => { mtoShipments: deletedMtoShipments, }); useOrdersDocumentQueries.mockReturnValue({ - ...newMoveDetailsQuery, + ...newOrdersDocumentQuery, mtoShipments: deletedMtoShipments, }); @@ -911,7 +909,7 @@ describe('MoveDetails page', () => { mtoShipments: deletedMtoShipments, }); useOrdersDocumentQueries.mockReturnValue({ - ...newMoveDetailsQuery, + ...newOrdersDocumentQuery, mtoShipments: deletedMtoShipments, }); @@ -945,7 +943,7 @@ describe('MoveDetails page', () => { mtoShipments: [ntsrShipmentMissingRequiredInfo], }; useMoveDetailsQueries.mockReturnValue(moveDetailsQuery); - useOrdersDocumentQueries.mockReturnValue(moveDetailsQuery); + useOrdersDocumentQueries.mockReturnValue(newOrdersDocumentQuery); renderComponent(); @@ -955,7 +953,7 @@ describe('MoveDetails page', () => { it('renders the Orders Definition List', async () => { useMoveDetailsQueries.mockReturnValue(newMoveDetailsQuery); - useOrdersDocumentQueries.mockReturnValue(newMoveDetailsQuery); + useOrdersDocumentQueries.mockReturnValue(newOrdersDocumentQuery); renderComponent(); @@ -965,7 +963,7 @@ describe('MoveDetails page', () => { it('renders the Allowances Table', async () => { useMoveDetailsQueries.mockReturnValue(newMoveDetailsQuery); - useOrdersDocumentQueries.mockReturnValue(newMoveDetailsQuery); + useOrdersDocumentQueries.mockReturnValue(newOrdersDocumentQuery); renderComponent(); @@ -975,7 +973,7 @@ describe('MoveDetails page', () => { it('allows the service counselor to use the modal as expected', async () => { useMoveDetailsQueries.mockReturnValue(newMoveDetailsQuery); - useOrdersDocumentQueries.mockReturnValue(newMoveDetailsQuery); + useOrdersDocumentQueries.mockReturnValue(newOrdersDocumentQuery); updateMoveStatusServiceCounselingCompleted.mockImplementation(() => Promise.resolve({})); renderComponent(); @@ -1001,7 +999,7 @@ describe('MoveDetails page', () => { ['Edit customer info', servicesCounselingRoutes.CUSTOMER_INFO_EDIT_PATH], ])('shows the "%s" link as expected: %s', async (linkText, route) => { useMoveDetailsQueries.mockReturnValue(newMoveDetailsQuery); - useOrdersDocumentQueries.mockReturnValue(newMoveDetailsQuery); + useOrdersDocumentQueries.mockReturnValue(newOrdersDocumentQuery); renderComponent(); @@ -1053,7 +1051,7 @@ describe('MoveDetails page', () => { it('shows the edit shipment buttons', async () => { useMoveDetailsQueries.mockReturnValue(newMoveDetailsQuery); - useOrdersDocumentQueries.mockReturnValue(newMoveDetailsQuery); + useOrdersDocumentQueries.mockReturnValue(newOrdersDocumentQuery); renderComponent(); @@ -1073,7 +1071,7 @@ describe('MoveDetails page', () => { it('shows the customer and counselor remarks', async () => { useMoveDetailsQueries.mockReturnValue(newMoveDetailsQuery); - useOrdersDocumentQueries.mockReturnValue(newMoveDetailsQuery); + useOrdersDocumentQueries.mockReturnValue(newOrdersDocumentQuery); renderComponent(); @@ -1093,7 +1091,7 @@ describe('MoveDetails page', () => { describe('service counseling completed', () => { it('hides submit and view/edit buttons/links', async () => { useMoveDetailsQueries.mockReturnValue(counselingCompletedMoveDetailsQuery); - useOrdersDocumentQueries.mockReturnValue(counselingCompletedMoveDetailsQuery); + useOrdersDocumentQueries.mockReturnValue(newOrdersDocumentQuery); renderComponent(); @@ -1108,7 +1106,7 @@ describe('MoveDetails page', () => { describe('permission dependent rendering', () => { useMoveDetailsQueries.mockReturnValue(newMoveDetailsQuery); - useOrdersDocumentQueries.mockReturnValue(newMoveDetailsQuery); + useOrdersDocumentQueries.mockReturnValue(newOrdersDocumentQuery); it('renders the financial review flag button when user has permission', async () => { render( diff --git a/src/pages/Office/ServicesCounselingMoveInfo/ServicesCounselingMoveInfo.jsx b/src/pages/Office/ServicesCounselingMoveInfo/ServicesCounselingMoveInfo.jsx index fb7b11e8c30..771ad23417b 100644 --- a/src/pages/Office/ServicesCounselingMoveInfo/ServicesCounselingMoveInfo.jsx +++ b/src/pages/Office/ServicesCounselingMoveInfo/ServicesCounselingMoveInfo.jsx @@ -11,6 +11,7 @@ import { servicesCounselingRoutes } from 'constants/routes'; import { useTXOMoveInfoQueries, useUserQueries } from 'hooks/queries'; import LoadingPlaceholder from 'shared/LoadingPlaceholder'; import SomethingWentWrong from 'shared/SomethingWentWrong'; +import Inaccessible from 'shared/Inaccessible'; import { roleTypes } from 'constants/userRoles'; import LockedMoveBanner from 'components/LockedMoveBanner/LockedMoveBanner'; import { isBooleanFlagEnabled } from 'utils/featureFlags'; @@ -65,7 +66,7 @@ const ServicesCounselingMoveInfo = () => { // Clear the alert when route changes const location = useLocation(); const { moveCode } = useParams(); - const { move, order, customerData, isLoading, isError } = useTXOMoveInfoQueries(moveCode); + const { move, order, customerData, isLoading, isError, errors } = useTXOMoveInfoQueries(moveCode); const { data } = useUserQueries(); const officeUserID = data?.office_user?.id; @@ -149,7 +150,9 @@ const ServicesCounselingMoveInfo = () => { ); if (isLoading) return ; - if (isError) return ; + if (isError) { + return errors?.[0]?.response?.body?.message ? : ; + } // this locked move banner will display if the current user is not the one who has it locked // if the current user is the one who has it locked, it will not display diff --git a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx index 0b961e2e3c6..c77ff89ef3c 100644 --- a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx +++ b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx @@ -89,11 +89,11 @@ export const counselingColumns = (moveLockFlag, originLocationList, supervisor, }, }, ), - createHeader('DoD ID', 'customer.dodID', { - id: 'dodID', + createHeader('DoD ID', 'customer.edipi', { + id: 'edipi', isFilterable: true, exportValue: (row) => { - return row.customer.dodID; + return row.customer.edipi; }, }), createHeader('EMPLID', 'customer.emplid', { @@ -197,7 +197,7 @@ export const counselingColumns = (moveLockFlag, originLocationList, supervisor, 'Assigned', (row) => { return !row?.assignable ? ( -
    {`${row.assignedTo?.lastName}, ${row.assignedTo?.firstName}`}
    +
    {row.assignedTo ? `${row.assignedTo?.lastName}, ${row.assignedTo?.firstName}` : ''}
    ) : (
    [ - createHeader( - ' ', - (row) => { - const now = new Date(); - // this will render a lock icon if the move is locked & if the lockExpiresAt value is after right now - if (row.lockedByOfficeUserID && row.lockExpiresAt && now < new Date(row.lockExpiresAt) && moveLockFlag) { +export const closeoutColumns = ( + moveLockFlag, + ppmCloseoutGBLOC, + ppmCloseoutOriginLocationList, + supervisor, + isQueueManagementEnabled, +) => { + const cols = [ + createHeader( + ' ', + (row) => { + const now = new Date(); + // this will render a lock icon if the move is locked & if the lockExpiresAt value is after right now + if (row.lockedByOfficeUserID && row.lockExpiresAt && now < new Date(row.lockExpiresAt) && moveLockFlag) { + return ( +
    + +
    + ); + } + return null; + }, + { id: 'lock' }, + ), + createHeader('ID', 'id', { id: 'id' }), + createHeader( + 'Customer name', + (row) => { return ( -
    - +
    + {CHECK_SPECIAL_ORDERS_TYPES(row.orderType) ? ( + {SPECIAL_ORDERS_TYPES[`${row.orderType}`]} + ) : null} + {`${row.customer.last_name}, ${row.customer.first_name}`}
    ); - } - return null; - }, - { id: 'lock' }, - ), - createHeader('ID', 'id', { id: 'id' }), - createHeader( - 'Customer name', - (row) => { - return ( -
    - {CHECK_SPECIAL_ORDERS_TYPES(row.orderType) ? ( - {SPECIAL_ORDERS_TYPES[`${row.orderType}`]} - ) : null} - {`${row.customer.last_name}, ${row.customer.first_name}`} -
    - ); - }, - { - id: 'lastName', + }, + { + id: 'customerName', + isFilterable: true, + exportValue: (row) => { + return `${row.customer.last_name}, ${row.customer.first_name}`; + }, + }, + ), + createHeader('DoD ID', 'customer.edipi', { + id: 'edipi', isFilterable: true, exportValue: (row) => { - return `${row.customer.last_name}, ${row.customer.first_name}`; + return row.customer.edipi; }, - }, - ), - createHeader('DoD ID', 'customer.dodID', { - id: 'dodID', - isFilterable: true, - exportValue: (row) => { - return row.customer.dodID; - }, - }), - createHeader('EMPLID', 'customer.emplid', { - id: 'emplid', - isFilterable: true, - }), - createHeader('Move code', 'locator', { - id: 'locator', - isFilterable: true, - }), - createHeader( - 'Branch', - (row) => { - return serviceMemberAgencyLabel(row.customer.agency); - }, - { - id: 'branch', - isFilterable: true, - Filter: (props) => ( - // eslint-disable-next-line react/jsx-props-no-spreading - - ), - }, - ), - createHeader( - 'Status', - (row) => { - return SERVICE_COUNSELING_PPM_STATUS_LABELS[`${row.ppmStatus}`]; - }, - { - id: 'ppmStatus', - isFilterable: true, - Filter: (props) => ( - // eslint-disable-next-line react/jsx-props-no-spreading - - ), - }, - ), - createHeader( - 'Closeout initiated', - (row) => { - return formatDateFromIso(row.closeoutInitiated, DATE_FORMAT_STRING); - }, - { - id: 'closeoutInitiated', + }), + createHeader('EMPLID', 'customer.emplid', { + id: 'emplid', isFilterable: true, - // eslint-disable-next-line react/jsx-props-no-spreading - Filter: (props) => , - }, - ), - createHeader( - 'Full or partial PPM', - (row) => { - return SERVICE_COUNSELING_PPM_TYPE_LABELS[`${row.ppmType}`]; - }, - { - id: 'ppmType', + }), + createHeader('Move code', 'locator', { + id: 'locator', isFilterable: true, - Filter: (props) => ( + }), + createHeader( + 'Branch', + (row) => { + return serviceMemberAgencyLabel(row.customer.agency); + }, + { + id: 'branch', + isFilterable: true, + Filter: (props) => ( + // eslint-disable-next-line react/jsx-props-no-spreading + + ), + }, + ), + createHeader( + 'Status', + (row) => { + return SERVICE_COUNSELING_PPM_STATUS_LABELS[`${row.ppmStatus}`]; + }, + { + id: 'ppmStatus', + isFilterable: true, + Filter: (props) => ( + // eslint-disable-next-line react/jsx-props-no-spreading + + ), + }, + ), + createHeader( + 'Closeout initiated', + (row) => { + return formatDateFromIso(row.closeoutInitiated, DATE_FORMAT_STRING); + }, + { + id: 'closeoutInitiated', + isFilterable: true, // eslint-disable-next-line react/jsx-props-no-spreading - - ), - }, - ), - supervisor - ? createHeader( - 'Origin duty location', - (row) => { - return `${row.originDutyLocation.name}`; - }, - { + Filter: (props) => , + }, + ), + createHeader( + 'Full or partial PPM', + (row) => { + return SERVICE_COUNSELING_PPM_TYPE_LABELS[`${row.ppmType}`]; + }, + { + id: 'ppmType', + isFilterable: true, + Filter: (props) => ( + // eslint-disable-next-line react/jsx-props-no-spreading + + ), + }, + ), + supervisor + ? createHeader( + 'Origin duty location', + (row) => { + return `${row.originDutyLocation.name}`; + }, + { + id: 'originDutyLocation', + isFilterable: true, + exportValue: (row) => { + return row.originDutyLocation?.name; + }, + Filter: (props) => ( + + ), + }, + ) + : createHeader('Origin duty location', 'originDutyLocation.name', { id: 'originDutyLocation', isFilterable: true, exportValue: (row) => { return row.originDutyLocation?.name; }, - Filter: (props) => ( - - ), + }), + createHeader('Counseling office', 'counselingOffice', { + id: 'counselingOffice', + isFilterable: true, + }), + createHeader('Destination duty location', 'destinationDutyLocation.name', { + id: 'destinationDutyLocation', + isFilterable: true, + exportValue: (row) => { + return row.destinationDutyLocation?.name; + }, + }), + createHeader('PPM closeout location', 'closeoutLocation', { + id: 'closeoutLocation', + // This filter only makes sense if we're not in a closeout GBLOC. Users in a closeout GBLOC will + // see the same value in this column for every move. + isFilterable: !ppmCloseoutGBLOC, + }), + ]; + if (isQueueManagementEnabled) + cols.push( + createHeader( + 'Assigned', + (row) => { + return !row?.assignable ? ( +
    {row.assignedTo ? `${row.assignedTo?.lastName}, ${row.assignedTo?.firstName}` : ''}
    + ) : ( +
    + handleQueueAssignment(row.id, e.target.value, roleTypes.SERVICES_COUNSELOR)} + title="Assigned dropdown" + > + + {row.availableOfficeUsers.map(({ lastName, firstName, officeUserId }) => ( + + ))} + +
    + ); }, - ) - : createHeader('Origin duty location', 'originDutyLocation.name', { - id: 'originDutyLocation', - isFilterable: true, - exportValue: (row) => { - return row.originDutyLocation?.name; + { + id: 'assignedTo', + isFilterable: true, }, - }), - createHeader('Destination duty location', 'destinationDutyLocation.name', { - id: 'destinationDutyLocation', - isFilterable: true, - exportValue: (row) => { - return row.destinationDutyLocation?.name; - }, - }), - createHeader('PPM closeout location', 'closeoutLocation', { - id: 'closeoutLocation', - // This filter only makes sense if we're not in a closeout GBLOC. Users in a closeout GBLOC will - // see the same value in this column for every move. - isFilterable: !ppmCloseoutGBLOC, - }), -]; + ), + ); + + return cols; +}; const ServicesCounselingQueue = ({ userPrivileges, isQueueManagementFFEnabled }) => { const { queueType } = useParams(); @@ -588,7 +632,13 @@ const ServicesCounselingQueue = ({ userPrivileges, isQueueManagementFFEnabled }) defaultSortedColumns={[{ id: 'closeoutInitiated', desc: false }]} disableMultiSort disableSortBy={false} - columns={closeoutColumns(moveLockFlag, inPPMCloseoutGBLOC, ppmCloseoutOriginLocationList, supervisor)} + columns={closeoutColumns( + moveLockFlag, + inPPMCloseoutGBLOC, + ppmCloseoutOriginLocationList, + supervisor, + isQueueManagementFFEnabled, + )} title="Moves" handleClick={handleClick} useQueries={useServicesCounselingQueuePPMQueries} diff --git a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.test.jsx b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.test.jsx index 208725bc202..c2a9286379c 100644 --- a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.test.jsx +++ b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.test.jsx @@ -95,7 +95,7 @@ const needsCounselingMoves = { agency: SERVICE_MEMBER_AGENCIES.ARMY, first_name: 'test first', last_name: 'test last', - dodID: '555555555', + edipi: '555555555', }, locator: 'AB5PC', requestedMoveDate: '2021-03-01T00:00:00.000Z', @@ -129,7 +129,7 @@ const needsCounselingMoves = { agency: SERVICE_MEMBER_AGENCIES.COAST_GUARD, first_name: 'test another first', last_name: 'test another last', - dodID: '4444444444', + edipi: '4444444444', emplid: '4521567', }, locator: 'T12AR', @@ -165,7 +165,7 @@ const needsCounselingMoves = { agency: SERVICE_MEMBER_AGENCIES.MARINES, first_name: 'test third first', last_name: 'test third last', - dodID: '4444444444', + edipi: '4444444444', }, locator: 'T12MP', requestedMoveDate: '2021-04-15T00:00:00.000Z', @@ -209,7 +209,7 @@ const serviceCounselingCompletedMoves = { agency: SERVICE_MEMBER_AGENCIES.ARMY, first_name: 'test first', last_name: 'test last', - dodID: '555555555', + edipi: '555555555', }, locator: 'AB5PC', requestedMoveDate: '2021-03-01T00:00:00.000Z', @@ -231,7 +231,7 @@ const serviceCounselingCompletedMoves = { agency: SERVICE_MEMBER_AGENCIES.COAST_GUARD, first_name: 'test another first', last_name: 'test another last', - dodID: '4444444444', + edipi: '4444444444', }, locator: 'T12AR', requestedMoveDate: '2021-04-15T00:00:00.000Z', @@ -309,7 +309,7 @@ describe('ServicesCounselingQueue', () => { const moves = wrapper.find('tbody tr'); const firstMove = moves.at(0); expect(firstMove.find('td.customerName').text()).toBe('test last, test first'); - expect(firstMove.find('td.dodID').text()).toBe('555555555'); + expect(firstMove.find('td.edipi').text()).toBe('555555555'); expect(firstMove.find('td.locator').text()).toBe('AB5PC'); expect(firstMove.find('td.status').text()).toBe('Needs counseling'); expect(firstMove.find('td.requestedMoveDate').text()).toBe('01 Mar 2021'); @@ -321,7 +321,7 @@ describe('ServicesCounselingQueue', () => { const secondMove = moves.at(1); expect(secondMove.find('td.customerName').text()).toBe('test another last, test another first'); - expect(secondMove.find('td.dodID').text()).toBe('4444444444'); + expect(secondMove.find('td.edipi').text()).toBe('4444444444'); expect(secondMove.find('td.emplid').text()).toBe('4521567'); expect(secondMove.find('td.locator').text()).toBe('T12AR'); expect(secondMove.find('td.status').text()).toBe('Needs counseling'); @@ -334,7 +334,7 @@ describe('ServicesCounselingQueue', () => { const thirdMove = moves.at(2); expect(thirdMove.find('td.customerName').text()).toBe('test third last, test third first'); - expect(thirdMove.find('td.dodID').text()).toBe('4444444444'); + expect(thirdMove.find('td.edipi').text()).toBe('4444444444'); expect(thirdMove.find('td.locator').text()).toBe('T12MP'); expect(thirdMove.find('td.status').text()).toBe('Needs counseling'); expect(thirdMove.find('td.requestedMoveDate').text()).toBe('15 Apr 2021'); @@ -351,7 +351,7 @@ describe('ServicesCounselingQueue', () => { it('allows sorting on certain columns', () => { expect(wrapper.find('th[data-testid="customerName"][role="columnheader"]').prop('onClick')).not.toBe(undefined); - expect(wrapper.find('th[data-testid="dodID"][role="columnheader"]').prop('onClick')).not.toBe(undefined); + expect(wrapper.find('th[data-testid="edipi"][role="columnheader"]').prop('onClick')).not.toBe(undefined); expect(wrapper.find('th[data-testid="emplid"][role="columnheader"]').prop('onClick')).not.toBe(undefined); expect(wrapper.find('th[data-testid="locator"][role="columnheader"]').prop('onClick')).not.toBe(undefined); expect(wrapper.find('th[data-testid="requestedMoveDate"][role="columnheader"]').prop('onClick')).not.toBe( @@ -378,7 +378,7 @@ describe('ServicesCounselingQueue', () => { describe('verify cached filters are displayed in respective filter column header on page reload - Service Counselor', () => { window.sessionStorage.setItem( OFFICE_TABLE_QUEUE_SESSION_STORAGE_ID, - '{"counseling":{"filters":[{"id":"customerName","value":"Spacemen"},{"id":"dodID","value":"7232607949"},{"id":"locator","value":"PPMADD"},{"id":"requestedMoveDate","value":"2024-06-21"},{"id":"submittedAt","value":"2024-06-20T04:00:00+00:00"},{"id":"branch","value":"ARMY"},{"id":"originDutyLocation","value":"12345"}], "sortParam":[{"id":"customerName","desc":false}], "page":3,"pageSize":10}}', + '{"counseling":{"filters":[{"id":"customerName","value":"Spacemen"},{"id":"edipi","value":"7232607949"},{"id":"locator","value":"PPMADD"},{"id":"requestedMoveDate","value":"2024-06-21"},{"id":"submittedAt","value":"2024-06-20T04:00:00+00:00"},{"id":"branch","value":"ARMY"},{"id":"originDutyLocation","value":"12345"}], "sortParam":[{"id":"customerName","desc":false}], "page":3,"pageSize":10}}', ); useUserQueries.mockReturnValue(serviceCounselorUser); @@ -391,7 +391,7 @@ describe('ServicesCounselingQueue', () => { agency: SERVICE_MEMBER_AGENCIES.ARMY, first_name: 'test first', last_name: 'test last', - dodID: '555555555', + edipi: '555555555', }, locator: 'AB5PC', requestedMoveDate: '2021-03-01T00:00:00.000Z', @@ -415,7 +415,7 @@ describe('ServicesCounselingQueue', () => { // Verify controls are using cached data on load. // If any of these fail check setup data window.sessionStorage.setItem() expect(wrapper.find('th[data-testid="customerName"] input').instance().value).toBe('Spacemen'); - expect(wrapper.find('th[data-testid="dodID"] input').instance().value).toBe('7232607949'); + expect(wrapper.find('th[data-testid="edipi"] input').instance().value).toBe('7232607949'); expect(wrapper.find('th[data-testid="locator"] input').instance().value).toBe('PPMADD'); expect(wrapper.find('th[data-testid="requestedMoveDate"] input').instance().value).toBe('21 Jun 2024'); expect(wrapper.find('th[data-testid="submittedAt"] input').instance().value).toBe('20 Jun 2024'); @@ -438,7 +438,7 @@ describe('ServicesCounselingQueue', () => { , ); expect(wrapper.find('th[data-testid="customerName"] input').instance().value).toBe(''); - expect(wrapper.find('th[data-testid="dodID"] input').instance().value).toBe(''); + expect(wrapper.find('th[data-testid="edipi"] input').instance().value).toBe(''); expect(wrapper.find('th[data-testid="locator"] input').instance().value).toBe(''); expect(wrapper.find('th[data-testid="requestedMoveDate"] input').instance().value).toBe(''); expect(wrapper.find('th[data-testid="submittedAt"] input').instance().value).toBe(''); @@ -508,6 +508,7 @@ describe('ServicesCounselingQueue', () => { expect(screen.getByText(/Full or partial PPM/)).toBeInTheDocument(); expect(screen.getByText(/Destination duty location/)).toBeInTheDocument(); expect(screen.getByText(/Status/)).toBeInTheDocument(); + expect(screen.getByText(/Assigned/)).toBeInTheDocument(); } else { // Check for the "Search" tab const searchActive = screen.getByText('Search', { selector: '.usa-current .tab-title' }); diff --git a/src/pages/Office/TXOMoveInfo/TXOMoveInfo.jsx b/src/pages/Office/TXOMoveInfo/TXOMoveInfo.jsx index 26ba3b00f6a..e0bb226c40d 100644 --- a/src/pages/Office/TXOMoveInfo/TXOMoveInfo.jsx +++ b/src/pages/Office/TXOMoveInfo/TXOMoveInfo.jsx @@ -11,6 +11,7 @@ import LoadingPlaceholder from 'shared/LoadingPlaceholder'; import CustomerHeader from 'components/CustomerHeader'; import SystemError from 'components/SystemError'; import { useTXOMoveInfoQueries, useUserQueries } from 'hooks/queries'; +import Inaccessible, { INACCESSIBLE_API_RESPONSE } from 'shared/Inaccessible'; import SomethingWentWrong from 'shared/SomethingWentWrong'; import LockedMoveBanner from 'components/LockedMoveBanner/LockedMoveBanner'; import { isBooleanFlagEnabled } from 'utils/featureFlags'; @@ -47,7 +48,7 @@ const TXOMoveInfo = () => { const { hasRecentError, traceId } = useSelector((state) => state.interceptor); const { moveCode, reportId } = useParams(); const { pathname } = useLocation(); - const { move, order, customerData, isLoading, isError } = useTXOMoveInfoQueries(moveCode); + const { move, order, customerData, isLoading, isError, errors } = useTXOMoveInfoQueries(moveCode); const { data } = useUserQueries(); const officeUserID = data?.office_user?.id; @@ -111,7 +112,13 @@ const TXOMoveInfo = () => { ); if (isLoading) return ; - if (isError) return ; + if (isError) { + return errors?.[0]?.response?.body?.message === INACCESSIBLE_API_RESPONSE ? ( + + ) : ( + + ); + } // this locked move banner will display if the current user is not the one who has it locked // if the current user is the one who has it locked, it will not display diff --git a/src/pages/Office/TXOMoveInfo/TXOMoveInfo.test.jsx b/src/pages/Office/TXOMoveInfo/TXOMoveInfo.test.jsx index defd48ba94c..aef8a832b3d 100644 --- a/src/pages/Office/TXOMoveInfo/TXOMoveInfo.test.jsx +++ b/src/pages/Office/TXOMoveInfo/TXOMoveInfo.test.jsx @@ -12,6 +12,7 @@ import { tooRoutes } from 'constants/routes'; import { roleTypes } from 'constants/userRoles'; import { configureStore } from 'shared/store'; import { isBooleanFlagEnabled } from 'utils/featureFlags'; +import { ERROR_RETURN_VALUE, LOADING_RETURN_VALUE, INACCESSIBLE_RETURN_VALUE } from 'utils/test/api'; jest.mock('utils/featureFlags', () => ({ ...jest.requireActual('utils/featureFlags'), @@ -99,18 +100,6 @@ const basicUseTXOMoveInfoQueriesValue = { isSuccess: true, }; -const loadingReturnValue = { - isLoading: true, - isError: false, - isSuccess: false, -}; - -const errorReturnValue = { - isLoading: false, - isError: true, - isSuccess: false, -}; - const user = { isLoading: false, isError: false, @@ -146,7 +135,7 @@ beforeEach(() => { describe('TXO Move Info Container', () => { describe('check loading and error component states', () => { it('renders the Loading Placeholder when the query is still loading', async () => { - useTXOMoveInfoQueries.mockReturnValue(loadingReturnValue); + useTXOMoveInfoQueries.mockReturnValue(LOADING_RETURN_VALUE); render( @@ -159,7 +148,7 @@ describe('TXO Move Info Container', () => { }); it('renders the Something Went Wrong component when the query errors', async () => { - useTXOMoveInfoQueries.mockReturnValue(errorReturnValue); + useTXOMoveInfoQueries.mockReturnValue(ERROR_RETURN_VALUE); render( @@ -170,6 +159,19 @@ describe('TXO Move Info Container', () => { const errorMessage = await screen.getByText(/Something went wrong./); expect(errorMessage).toBeInTheDocument(); }); + + it('renders the Inaccessible component when the query returns an inaccessible response', async () => { + useTXOMoveInfoQueries.mockReturnValue(INACCESSIBLE_RETURN_VALUE); + + render( + + + , + ); + + const errorMessage = await screen.getByText(/Page is not accessible./); + expect(errorMessage).toBeInTheDocument(); + }); }); describe('Basic rendering', () => { diff --git a/src/pages/Office/index.jsx b/src/pages/Office/index.jsx index 8c4b8f0aea3..522fbf97625 100644 --- a/src/pages/Office/index.jsx +++ b/src/pages/Office/index.jsx @@ -12,7 +12,7 @@ import 'scenes/Office/office.scss'; import { milmoveLogger } from 'utils/milmoveLog'; import { retryPageLoading } from 'utils/retryPageLoading'; // API / Redux actions -import { selectGetCurrentUserIsLoading, selectIsLoggedIn } from 'store/auth/selectors'; +import { selectGetCurrentUserIsLoading, selectIsLoggedIn, selectUnderMaintenance } from 'store/auth/selectors'; import { loadUser as loadUserAction } from 'store/auth/actions'; import { selectLoggedInUser } from 'store/entities/selectors'; import { @@ -43,6 +43,7 @@ import PermissionProvider from 'components/Restricted/PermissionProvider'; import withRouter from 'utils/routing'; import { OktaLoggedOutBanner, OktaNeedsLoggedOutBanner } from 'components/OktaLogoutBanner'; import SelectedGblocProvider from 'components/Office/GblocSwitcher/SelectedGblocProvider'; +import MaintenancePage from 'pages/Maintenance/MaintenancePage'; // 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')); @@ -192,6 +193,7 @@ export class OfficeApp extends Component { hasRecentError, traceId, userPrivileges, + underMaintenance, } = this.props; const displayChangeRole = @@ -212,6 +214,10 @@ export class OfficeApp extends Component { pathname, ); + if (underMaintenance) { + return ; + } + const siteClasses = classnames('site', { [`site--fullscreen`]: isFullscreenPage, }); @@ -292,7 +298,7 @@ export class OfficeApp extends Component { end element={ - + } /> @@ -366,7 +372,7 @@ export class OfficeApp extends Component { end element={ - + } /> @@ -657,6 +663,7 @@ const mapStateToProps = (state) => { hasRecentError: state.interceptor.hasRecentError, traceId: state.interceptor.traceId, userPrivileges: user?.privileges || null, + underMaintenance: selectUnderMaintenance(state), }; }; diff --git a/src/pages/Office/index.test.jsx b/src/pages/Office/index.test.jsx index ba68be9c589..7ce5b3dc8ba 100644 --- a/src/pages/Office/index.test.jsx +++ b/src/pages/Office/index.test.jsx @@ -376,4 +376,21 @@ describe('Office App', () => { await waitFor(() => expect(screen.getByText('Mock Invalid Permissions Component'))); }); }); + + it('renders the Maintenance page flag is true', async () => { + const mockMaintenanceOfficeProps = { + loadUser: jest.fn(), + loadInternalSchema: jest.fn(), + loadPublicSchema: jest.fn(), + logOut: jest.fn(), + underMaintenance: true, + hasRecentError: false, + traceId: '', + }; + + const wrapper = shallow(); + + // maintenance page should be rendered + expect(wrapper.find('MaintenancePage')).toHaveLength(1); + }); }); diff --git a/src/pages/PrimeUI/Shipment/PrimeUIShipmentCreate.jsx b/src/pages/PrimeUI/Shipment/PrimeUIShipmentCreate.jsx index 35d2356baa1..36139c32e23 100644 --- a/src/pages/PrimeUI/Shipment/PrimeUIShipmentCreate.jsx +++ b/src/pages/PrimeUI/Shipment/PrimeUIShipmentCreate.jsx @@ -31,6 +31,18 @@ const PrimeUIShipmentCreate = ({ setFlashMessage }) => { const handleClose = () => { navigate(generatePath(primeSimulatorRoutes.VIEW_MOVE_PATH, { moveCodeOrID })); }; + + const handleSetError = (error, invalidFieldsStr) => { + let detailedMessage = `${error?.response?.body.detail}${invalidFieldsStr}\n\nPlease refresh and Update Shipment again`; + if (error?.response?.body?.detail !== null && error?.response?.body?.detail !== undefined) { + detailedMessage = `${error?.response?.body.detail}\n\nPlease refresh and Update Shipment again`; + } + setErrorMessage({ + title: `Prime API: ${error?.response?.body.title} `, + detail: detailedMessage, + }); + }; + const { mutateAsync: mutateCreateMTOShipment } = useMutation(createPrimeMTOShipmentV3, { onSuccess: (createdMTOShipment) => { setFlashMessage( @@ -53,10 +65,7 @@ const PrimeUIShipmentCreate = ({ setFlashMessage }) => { invalidFieldsStr += `\n${key} - ${value && value.length > 0 ? value[0] : ''} ;`; }); } - setErrorMessage({ - title: `Prime API: ${body.title} `, - detail: `${body.detail}${invalidFieldsStr}\n\nPlease try again`, - }); + handleSetError(error, invalidFieldsStr); } else { setErrorMessage({ title: 'Unexpected error', diff --git a/src/pages/PrimeUI/Shipment/PrimeUIShipmentCreate.test.jsx b/src/pages/PrimeUI/Shipment/PrimeUIShipmentCreate.test.jsx index 50884157c1a..eec912b1793 100644 --- a/src/pages/PrimeUI/Shipment/PrimeUIShipmentCreate.test.jsx +++ b/src/pages/PrimeUI/Shipment/PrimeUIShipmentCreate.test.jsx @@ -117,6 +117,22 @@ describe('Error when submitting', () => { ).toBeInTheDocument(); }); }); + + it('Correctly displays a specific error message when an error response is returned', async () => { + createPrimeMTOShipmentV3.mockRejectedValue({ body: { title: 'Error', detail: 'The data entered no good.' } }); + render(mockedComponent); + + waitFor(async () => { + await userEvent.selectOptions(screen.getByLabelText('Shipment type'), 'HHG'); + + const saveButton = await screen.getByRole('button', { name: 'Save' }); + + expect(saveButton).not.toBeDisabled(); + await userEvent.click(saveButton); + expect(screen.getByText('Prime API: Error')).toBeInTheDocument(); + expect(screen.getByText('The data entered no good.')).toBeInTheDocument(); + }); + }); }); describe('Create PPM', () => { diff --git a/src/pages/PrimeUI/Shipment/PrimeUIShipmentCreateForm.jsx b/src/pages/PrimeUI/Shipment/PrimeUIShipmentCreateForm.jsx index 907e77012e3..3f70fb3fb53 100644 --- a/src/pages/PrimeUI/Shipment/PrimeUIShipmentCreateForm.jsx +++ b/src/pages/PrimeUI/Shipment/PrimeUIShipmentCreateForm.jsx @@ -121,7 +121,7 @@ const PrimeUIShipmentCreateForm = () => { <>

    What address are the movers picking up from?

    {fields} -

    Second pickup location

    +

    Second Pickup Address

    Will the movers pick up any belongings from a second address? (Must be near the pickup address. @@ -135,7 +135,7 @@ const PrimeUIShipmentCreateForm = () => { label="Yes" name="ppmShipment.hasSecondaryPickupAddress" value="true" - title="Yes, there is a second pickup location" + title="Yes, there is a second pickup address" checked={hasSecondaryPickupAddress === 'true'} /> { label="No" name="ppmShipment.hasSecondaryPickupAddress" value="false" - title="No, there is not a second pickup location" + title="No, there is not a second pickup address" checked={hasSecondaryPickupAddress !== 'true' && hasTertiaryPickupAddress !== 'true'} />

    @@ -155,7 +155,7 @@ const PrimeUIShipmentCreateForm = () => {
    Second Pickup Address
    -

    Third pickup location

    +

    Third Pickup Address

    Will the movers pick up any belongings from a third address? (Must be near the pickup address. @@ -169,7 +169,7 @@ const PrimeUIShipmentCreateForm = () => { label="Yes" name="ppmShipment.hasTertiaryPickupAddress" value="true" - title="Yes, there is a tertiary pickup location" + title="Yes, there is a tertiary pickup address" checked={hasTertiaryPickupAddress === 'true'} /> { label="No" name="ppmShipment.hasTertiaryPickupAddress" value="false" - title="No, there is not a tertiary pickup location" + title="No, there is not a tertiary pickup address" checked={hasTertiaryPickupAddress !== 'true'} />

    @@ -198,15 +198,15 @@ const PrimeUIShipmentCreateForm = () => {

    Destination Info

    ( <> {fields} -

    Second destination address

    +

    Second Delivery Address

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

    @@ -234,10 +234,10 @@ const PrimeUIShipmentCreateForm = () => { {hasSecondaryDestinationAddress === 'true' && ( <> -
    Second Destination Address
    +
    Second Delivery Address
    -

    Third delivery location

    +

    Third Delivery Address

    Will the movers pick up any belongings from a third address? (Must be near the pickup address. @@ -251,7 +251,7 @@ const PrimeUIShipmentCreateForm = () => { label="Yes" name="ppmShipment.hasTertiaryDestinationAddress" value="true" - title="Yes, there is a third delivery location" + title="Yes, there is a third delivery address" checked={hasTertiaryDestinationAddress === 'true'} /> { label="No" name="ppmShipment.hasTertiaryDestinationAddress" value="false" - title="No, there is not a third delivery location" + title="No, there is not a third delivery address" checked={hasTertiaryDestinationAddress !== 'true'} />

    @@ -270,7 +270,7 @@ const PrimeUIShipmentCreateForm = () => { )} {hasTertiaryDestinationAddress === 'true' && hasSecondaryDestinationAddress === 'true' && ( <> -
    Third Destination Address
    +
    Third Delivery Address
    )} @@ -395,7 +395,7 @@ const PrimeUIShipmentCreateForm = () => { render={(fields) => ( <> {fields} -

    Second pickup location

    +

    Second Pickup Address

    Will the movers pick up any belongings from a second address? (Must be near the pickup address. @@ -409,7 +409,7 @@ const PrimeUIShipmentCreateForm = () => { label="Yes" name="hasSecondaryPickupAddress" value="true" - title="Yes, there is a second pickup location" + title="Yes, there is a second pickup address" checked={hasSecondaryPickupAddress === 'true'} /> { label="No" name="hasSecondaryPickupAddress" value="false" - title="No, there is not a second pickup location" + title="No, there is not a second pickup address" checked={hasSecondaryPickupAddress !== 'true'} />

    @@ -429,7 +429,7 @@ const PrimeUIShipmentCreateForm = () => {
    Second Pickup Address
    -

    Third pickup location

    +

    Third Pickup Address

    Will the movers pick up any belongings from a third address? (Must be near the pickup address. @@ -443,7 +443,7 @@ const PrimeUIShipmentCreateForm = () => { label="Yes" name="hasTertiaryPickupAddress" value="true" - title="Yes, there is a tertiary pickup location" + title="Yes, there is a tertiary pickup address" checked={hasTertiaryPickupAddress === 'true'} /> { label="No" name="hasTertiaryPickupAddress" value="false" - title="No, there is not a tertiary pickup location" + title="No, there is not a tertiary pickup address" checked={hasTertiaryPickupAddress !== 'true'} />

    @@ -473,12 +473,12 @@ const PrimeUIShipmentCreateForm = () => {

    Destination Info

    ( <> {fields} -

    Second delivery location

    +

    Second Delivery Address

    Will the movers pick up any belongings from a second address? (Must be near the pickup address. @@ -492,7 +492,7 @@ const PrimeUIShipmentCreateForm = () => { label="Yes" name="hasSecondaryDestinationAddress" value="true" - title="Yes, there is a second delivery location" + title="Yes, there is a second delivery address" checked={hasSecondaryDestinationAddress === 'true'} /> { label="No" name="hasSecondaryDestinationAddress" value="false" - title="No, there is not a second delivery location" + title="No, there is not a second delivery address" checked={hasSecondaryDestinationAddress !== 'true'} />

    {hasSecondaryDestinationAddress === 'true' && ( <> -
    Second Destination Address
    +
    Second Delivery Address
    -

    Third delivery location

    +

    Third Delivery Address

    Will the movers pick up any belongings from a third address? (Must be near the pickup address. @@ -526,7 +526,7 @@ const PrimeUIShipmentCreateForm = () => { label="Yes" name="hasTertiaryDestinationAddress" value="true" - title="Yes, there is a third delivery location" + title="Yes, there is a third delivery address" checked={hasTertiaryDestinationAddress === 'true'} /> { label="No" name="hasTertiaryDestinationAddress" value="false" - title="No, there is not a third delivery location" + title="No, there is not a third delivery address" checked={hasTertiaryDestinationAddress !== 'true'} />

    @@ -545,7 +545,7 @@ const PrimeUIShipmentCreateForm = () => { )} {hasTertiaryDestinationAddress === 'true' && hasSecondaryDestinationAddress === 'true' && ( <> -
    Third Destination Address
    +
    Third Delivery Address
    )} diff --git a/src/pages/PrimeUI/Shipment/PrimeUIShipmentCreateForm.test.jsx b/src/pages/PrimeUI/Shipment/PrimeUIShipmentCreateForm.test.jsx index a0fd1bd78c2..80ea98a6f8a 100644 --- a/src/pages/PrimeUI/Shipment/PrimeUIShipmentCreateForm.test.jsx +++ b/src/pages/PrimeUI/Shipment/PrimeUIShipmentCreateForm.test.jsx @@ -414,7 +414,7 @@ describe('PrimeUIShipmentCreateForm', () => { expect(await screen.findByText('Pickup Address')).toBeInTheDocument(); expect(screen.getAllByLabelText('Address 1')[0]).toHaveValue(''); - expect(await screen.findByText('Destination Address')).toBeInTheDocument(); + expect(await screen.findByText('Delivery Address')).toBeInTheDocument(); expect(screen.getAllByLabelText('Address 1')[1]).toHaveValue(''); }, ); @@ -452,7 +452,7 @@ describe('PrimeUIShipmentCreateForm', () => { await userEvent.click(hasTertiaryPickup); expect(screen.getAllByLabelText('Address 1')[2]).toHaveValue(''); - expect(await screen.findByText('Destination Address')).toBeInTheDocument(); + expect(await screen.findByText('Delivery Address')).toBeInTheDocument(); expect(screen.getAllByLabelText('Address 1')[3]).toHaveValue(''); const hasSecondaryDestination = await screen.findByTestId('has-secondary-destination'); diff --git a/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdate.jsx b/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdate.jsx index 0aa9f5e0e2a..0a5a5d180b8 100644 --- a/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdate.jsx +++ b/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdate.jsx @@ -44,6 +44,17 @@ const PrimeUIShipmentUpdate = ({ setFlashMessage }) => { navigate(generatePath(primeSimulatorRoutes.VIEW_MOVE_PATH, { moveCodeOrID })); }; + const handleSetError = (error, invalidFieldsStr) => { + let detailedMessage = `${error?.response?.body.detail}${invalidFieldsStr}\n\nPlease refresh and Update Shipment again`; + if (error?.response?.body?.detail !== null && error?.response?.body?.detail !== undefined) { + detailedMessage = `${error?.response?.body.detail}\n\nPlease refresh and Update Shipment again`; + } + setErrorMessage({ + title: `Prime API: ${error?.response?.body.title} `, + detail: detailedMessage, + }); + }; + const { mutateAsync: mutateMTOShipmentStatus } = useMutation(updatePrimeMTOShipmentStatus, { onSuccess: (updatedMTOShipment) => { mtoShipments[mtoShipments.findIndex((mtoShipment) => mtoShipment.id === updatedMTOShipment.id)] = @@ -62,10 +73,7 @@ const PrimeUIShipmentUpdate = ({ setFlashMessage }) => { invalidFieldsStr += `\n${key} - ${value && value.length > 0 ? value[0] : ''} ;`; }); } - setErrorMessage({ - title: `Prime API: ${body.title} `, - detail: `${body.detail}${invalidFieldsStr}\n\nPlease cancel and Update Shipment again`, - }); + handleSetError(error, invalidFieldsStr); } else { setErrorMessage({ title: 'Unexpected error', @@ -93,10 +101,7 @@ const PrimeUIShipmentUpdate = ({ setFlashMessage }) => { invalidFieldsStr += `\n${key} - ${value && value.length > 0 ? value[0] : ''} ;`; }); } - setErrorMessage({ - title: `Prime API: ${body.title} `, - detail: `${body.detail}${invalidFieldsStr}\n\nPlease cancel and Update Shipment again`, - }); + handleSetError(error, invalidFieldsStr); } else { setErrorMessage({ title: 'Unexpected error', diff --git a/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdate.test.jsx b/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdate.test.jsx index b28b3c1bd1a..f4c26b8a1ba 100644 --- a/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdate.test.jsx +++ b/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdate.test.jsx @@ -513,3 +513,21 @@ describe('Update Shipment Page for PPM', () => { }); }); }); + +describe('Error Handling', () => { + it('Correctly displays a specific error message when an error response is returned', async () => { + updatePrimeMTOShipmentV3.mockRejectedValue({ body: { title: 'Error', detail: 'The data entered no good.' } }); + render(mockedComponent); + + waitFor(async () => { + await userEvent.selectOptions(screen.getByLabelText('Shipment type'), 'HHG'); + + const saveButton = await screen.getByRole('button', { name: 'Save' }); + + expect(saveButton).not.toBeDisabled(); + await userEvent.click(saveButton); + expect(screen.getByText('Prime API: Error')).toBeInTheDocument(); + expect(screen.getByText('The data entered no good.')).toBeInTheDocument(); + }); + }); +}); diff --git a/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateAddress.test.jsx b/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateAddress.test.jsx index 5e2083a7fd7..ed9eee256f3 100644 --- a/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateAddress.test.jsx +++ b/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateAddress.test.jsx @@ -115,7 +115,7 @@ describe('PrimeUIShipmentUpdateAddress page', () => { }); describe('displaying shipment address information', () => { - it('displays shipment pickup and destination address', async () => { + it('displays shipment pickup and delivery address', async () => { usePrimeSimulatorGetMove.mockReturnValue(moveReturnValue); renderComponent(); diff --git a/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateDestinationAddress.test.jsx b/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateDestinationAddress.test.jsx index 1ce01af07b4..9e0c7c7a417 100644 --- a/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateDestinationAddress.test.jsx +++ b/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateDestinationAddress.test.jsx @@ -138,13 +138,13 @@ describe('PrimeUIShipmentUpdateAddress page', () => { }); describe('displaying shipment address information', () => { - it('displays the destination address form', async () => { + it('displays the delivery address form', async () => { usePrimeSimulatorGetMove.mockReturnValue(testShipmentReturnValue); renderComponent(); const pageHeading = await screen.getByRole('heading', { - name: 'Update Shipment Destination Address', + name: 'Update Shipment Delivery Address', level: 2, }); expect(pageHeading).toBeInTheDocument(); diff --git a/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateDestinationAddressForm.jsx b/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateDestinationAddressForm.jsx index 0b923d4aa14..5ad4224990d 100644 --- a/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateDestinationAddressForm.jsx +++ b/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateDestinationAddressForm.jsx @@ -31,15 +31,15 @@ const PrimeUIShipmentUpdateDestinationAddressForm = ({
    0 ? 1 : 0}> -

    Update Shipment Destination Address

    +

    Update Shipment Delivery Address

    - This is used to update the destination address on an{' '} - already approved shipment.
    - This also updates the final destination address for destination SIT service items in the shipment. + This is used to update the delivery address on an already approved{' '} + shipment.
    + This also updates the final delivery address for destination SIT service items in the shipment.

    - This endpoint should be used for changing the destination address of HHG & NTSR shipments. + This endpoint should be used for changing the delivery address of HHG & NTSR shipments.

    The address update will be automatically approved unless it changes any of the following: @@ -62,7 +62,7 @@ const PrimeUIShipmentUpdateDestinationAddressForm = ({ )} -
    Destination Address
    +
    Delivery Address
    {editableDestinationAddress && } {!editableDestinationAddress && formatAddress(destinationAddress)} {!isNTS && ( diff --git a/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdatePPMForm.jsx b/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdatePPMForm.jsx index 33286b9abfc..daf02c6b903 100644 --- a/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdatePPMForm.jsx +++ b/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdatePPMForm.jsx @@ -40,7 +40,7 @@ const PrimeUIShipmentUpdatePPMForm = () => { <>

    What address are the movers picking up from?

    {fields} -

    Second pickup location

    +

    Second Pickup Address

    Will the movers pick up any belongings from a second address? (Must be near the pickup address. Subject @@ -54,7 +54,7 @@ const PrimeUIShipmentUpdatePPMForm = () => { label="Yes" name="ppmShipment.hasSecondaryPickupAddress" value="true" - title="Yes, there is a second pickup location" + title="Yes, there is a second pickup address" checked={hasSecondaryPickupAddress === 'true'} /> { label="No" name="ppmShipment.hasSecondaryPickupAddress" value="false" - title="No, there is not a second pickup location" + title="No, there is not a second pickup address" checked={hasSecondaryPickupAddress !== 'true'} />

    @@ -72,7 +72,7 @@ const PrimeUIShipmentUpdatePPMForm = () => { {hasSecondaryPickupAddress === 'true' && ( <> -

    Third pickup location

    +

    Third Pickup Address

    Will the movers pick up any belongings from a third address? (Must be near the pickup address. @@ -86,7 +86,7 @@ const PrimeUIShipmentUpdatePPMForm = () => { label="Yes" name="ppmShipment.hasTertiaryPickupAddress" value="true" - title="Yes, there is a third pickup location" + title="Yes, there is a third pickup address" checked={hasTertiaryPickupAddress === 'true'} /> { label="No" name="ppmShipment.hasTertiaryPickupAddress" value="false" - title="No, there is not a third pickup location" + title="No, there is not a third pickup address" checked={hasTertiaryPickupAddress !== 'true'} />

    @@ -110,16 +110,16 @@ const PrimeUIShipmentUpdatePPMForm = () => {

    Destination Info

    ( <> {fields} -

    Second destination address

    +

    Second Delivery Address

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

    {

    Third destination location

    - Will the movers pick up any belongings from a third address? (Must be near the Destination address. + Will the movers pick up any belongings from a third address? (Must be near the Delivery Address. Subject to approval.)

    diff --git a/src/sagas/auth.js b/src/sagas/auth.js index 66eae5e7bd2..fd8b9fc29d4 100644 --- a/src/sagas/auth.js +++ b/src/sagas/auth.js @@ -1,7 +1,13 @@ import { takeLatest, put, call } from 'redux-saga/effects'; import { normalize } from 'normalizr'; -import { LOAD_USER, getLoggedInUserStart, getLoggedInUserSuccess, getLoggedInUserFailure } from 'store/auth/actions'; +import { + LOAD_USER, + getLoggedInUserStart, + getLoggedInUserSuccess, + getLoggedInUserFailure, + setUnderMaintenance, +} from 'store/auth/actions'; import { setFlashMessage } from 'store/flash/actions'; import { GetAdminUser, GetIsLoggedIn, GetLoggedInUser, GetOktaUser } from 'utils/api'; import { loggedInUser } from 'shared/Entities/schema'; @@ -17,7 +23,12 @@ export function* fetchUser() { try { // The `GetIsLoggedIn` call returns a object with a parameter isLoggedIn - const { isLoggedIn } = yield call(GetIsLoggedIn); + const { isLoggedIn, underMaintenance } = yield call(GetIsLoggedIn); + + if (underMaintenance) { + yield put(setUnderMaintenance()); + } + if (isLoggedIn) { try { const user = yield call(GetLoggedInUser); // make user API call diff --git a/src/sagas/auth.test.js b/src/sagas/auth.test.js index 0167d830eb3..72bcb978881 100644 --- a/src/sagas/auth.test.js +++ b/src/sagas/auth.test.js @@ -157,3 +157,14 @@ describe('fetchUser saga', () => { }); }); }); + +describe('fetch underMaintenance', () => { + const generator = fetchUser(); + + it('makes the GetIsLoggedIn API call', () => { + expect(generator.next().value).toEqual(put(getLoggedInUserStart())); + expect(generator.next().value).toEqual(call(GetIsLoggedIn)); + expect(generator.next({ isLoggedIn: true, underMaintenance: false }).value).toEqual(call(GetLoggedInUser)); + // expect(generator.next().value).toEqual(put(setUnderMaintenance)); + }); +}); diff --git a/src/scenes/MyMove/index.jsx b/src/scenes/MyMove/index.jsx index 4416a84bf97..cd11158f72a 100644 --- a/src/scenes/MyMove/index.jsx +++ b/src/scenes/MyMove/index.jsx @@ -28,7 +28,12 @@ import { no_op } from 'shared/utils'; import { generatePageTitle } from 'hooks/custom'; import { loadUser as loadUserAction } from 'store/auth/actions'; import { initOnboarding as initOnboardingAction } from 'store/onboarding/actions'; -import { selectCacValidated, selectGetCurrentUserIsLoading, selectIsLoggedIn } from 'store/auth/selectors'; +import { + selectCacValidated, + selectGetCurrentUserIsLoading, + selectIsLoggedIn, + selectUnderMaintenance, +} from 'store/auth/selectors'; import { selectConusStatus } from 'store/onboarding/selectors'; import { selectServiceMemberFromLoggedInUser, @@ -53,6 +58,7 @@ import AddOrders from 'pages/MyMove/AddOrders'; import UploadOrders from 'pages/MyMove/UploadOrders'; import SmartCardRedirect from 'shared/SmartCardRedirect/SmartCardRedirect'; import OktaErrorBanner from 'components/OktaErrorBanner/OktaErrorBanner'; +import MaintenancePage from 'pages/Maintenance/MaintenancePage'; // Pages should be lazy-loaded (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')); @@ -145,7 +151,7 @@ export class CustomerApp extends Component { render() { const { props } = this; - const { userIsLoggedIn, loginIsLoading, cacValidated } = props; + const { userIsLoggedIn, loginIsLoading, cacValidated, underMaintenance } = props; const { hasError, multiMoveFeatureFlag, cacValidatedFeatureFlag, oktaErrorBanner } = this.state; const script = document.createElement('script'); @@ -153,6 +159,10 @@ export class CustomerApp extends Component { script.async = true; document.body.appendChild(script); + if (underMaintenance) { + return ; + } + return ( <>
    @@ -476,6 +486,7 @@ const mapStateToProps = (state) => { moveId: move?.id, conusStatus: selectConusStatus(state), swaggerError: state.swaggerInternal.hasErrored, + underMaintenance: selectUnderMaintenance(state), }; }; const mapDispatchToProps = { diff --git a/src/scenes/MyMove/index.test.js b/src/scenes/MyMove/index.test.js index 35cdd80ab73..b908b442eac 100644 --- a/src/scenes/MyMove/index.test.js +++ b/src/scenes/MyMove/index.test.js @@ -232,4 +232,23 @@ describe('CustomerApp tests', () => { expect(wrapper.find(OktaErrorBanner)).toHaveLength(1); }); }); + + it('renders the Maintenance page flag is true', async () => { + const minPropsWithMaintenance = { + initOnboarding: jest.fn(), + loadInternalSchema: jest.fn(), + loadUser: jest.fn(), + underMaintenance: true, + context: { + flags: { + hhgFlow: false, + }, + }, + }; + + const wrapper = shallow(); + + // maintenance page should be rendered + expect(wrapper.find('MaintenancePage')).toHaveLength(1); + }); }); diff --git a/src/scenes/SystemAdmin/index.jsx b/src/scenes/SystemAdmin/index.jsx index 82f00d7fe6f..1ebca08d4a3 100644 --- a/src/scenes/SystemAdmin/index.jsx +++ b/src/scenes/SystemAdmin/index.jsx @@ -18,6 +18,8 @@ import { loadPublicSchema as loadPublicSchemaAction, } from 'shared/Swagger/ducks'; import withRouter from 'utils/routing'; +import { selectUnderMaintenance } from 'store/auth/selectors'; +import MaintenancePage from 'pages/Maintenance/MaintenancePage'; // 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')); @@ -68,6 +70,8 @@ class AdminWrapper extends Component { } render() { + const { props } = this; + const { underMaintenance } = props; const { oktaLoggedOut, oktaNeedsLoggedOut } = this.state; const script = document.createElement('script'); @@ -75,6 +79,11 @@ class AdminWrapper extends Component { script.async = true; document.body.appendChild(script); + + if (underMaintenance) { + return ; + } + return ( <>
    @@ -96,8 +105,10 @@ class AdminWrapper extends Component { } } -const mapStateToProps = () => { - return {}; +const mapStateToProps = (state) => { + return { + underMaintenance: selectUnderMaintenance(state), + }; }; const mapDispatchToProps = { diff --git a/src/services/ghcApi.js b/src/services/ghcApi.js index 290a7a3be98..bc8353ca653 100644 --- a/src/services/ghcApi.js +++ b/src/services/ghcApi.js @@ -889,6 +889,12 @@ export async function updateAssignedOfficeUserForMove({ moveID, officeUserId, ro }); } +export async function checkForLockedMovesAndUnlock(key, officeUserID) { + return makeGHCRequest('move.checkForLockedMovesAndUnlock', { + officeUserID, + }); +} + export async function deleteAssignedOfficeUserForMove({ moveID, roleType }) { return makeGHCRequest('move.deleteAssignedOfficeUser', { moveID, diff --git a/src/shared/Entities/schema.js b/src/shared/Entities/schema.js index 23379c7843e..86b394422ad 100644 --- a/src/shared/Entities/schema.js +++ b/src/shared/Entities/schema.js @@ -199,3 +199,5 @@ export const searchMoves = new schema.Array(searchMove); export const searchMovesResult = new schema.Entity('searchMovesResult'); export const officeUser = new schema.Entity('officeUser'); + +export const unlockedMoves = new schema.Entity('moves', officeUser.id); diff --git a/src/shared/Inaccessible/index.jsx b/src/shared/Inaccessible/index.jsx new file mode 100644 index 00000000000..363608d2d4f --- /dev/null +++ b/src/shared/Inaccessible/index.jsx @@ -0,0 +1,25 @@ +import React from 'react'; + +import sadComputer from 'shared/images/sad-computer.png'; + +const Inaccessible = () => ( +
    +
    +

    + +

    +

    Page is not accessible.

    +

    + If you feel this message was received in error, please call (800) 462-2176, Option 2 or{' '} + + email us + + . +

    +
    +
    +); + +export const INACCESSIBLE_API_RESPONSE = 'Page is inaccessible'; + +export default Inaccessible; diff --git a/src/shared/Inaccessible/index.test.jsx b/src/shared/Inaccessible/index.test.jsx new file mode 100644 index 00000000000..a65b59e38f4 --- /dev/null +++ b/src/shared/Inaccessible/index.test.jsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; + +import Inaccessible from './index'; + +describe('Inaccessible tests', () => { + it('renders without crashing', async () => { + const { container } = render(); + + const errorPage = await container.querySelector('.usa-grid'); + expect(errorPage).toBeInTheDocument(); + }); + + it('should render the correct image on the page', () => { + render(); + + const image = screen.getByRole('img'); + expect(image).toBeInTheDocument(); + expect(image).toHaveAttribute('src', 'sad-computer.png'); + }); + + it('should render the correct text on the page', () => { + render(); + + const inaccessibleMsg = screen.getByRole('heading', { level: 2 }); + expect(inaccessibleMsg).toBeInTheDocument(); + expect(inaccessibleMsg).toHaveTextContent('Page is not accessible.'); + + const contactMsg = screen.getByTestId('contactMsg'); + expect(contactMsg).toBeInTheDocument(); + expect(contactMsg).toHaveTextContent( + 'If you feel this message was received in error, please call (800) 462-2176, Option 2 or email us.', + ); + + const email = screen.getByRole('link', { name: 'email us' }); + expect(email).toBeInTheDocument(); + expect(email).toHaveAttribute('href', 'mailto:usarmy.scott.sddc.mbx.G6-SRC-MilMove-HD@army.mil'); + }); +}); diff --git a/src/shared/constants.js b/src/shared/constants.js index 94903bd65c4..58047bd2c15 100644 --- a/src/shared/constants.js +++ b/src/shared/constants.js @@ -203,6 +203,7 @@ export const FEATURE_FLAG_KEYS = { BOAT: 'boat', MOBILE_HOME: 'mobile_home', UNACCOMPANIED_BAGGAGE: 'unaccompanied_baggage', + ENABLE_ALASKA: 'enable_alaska', }; export const MOVE_DOCUMENT_TYPE = { @@ -224,9 +225,9 @@ const ADDRESS_LABELS_MAP = { [ADDRESS_TYPES.PICKUP]: 'Pickup Address', [ADDRESS_TYPES.SECOND_PICKUP]: 'Second Pickup Address', [ADDRESS_TYPES.THIRD_PICKUP]: 'Third Pickup Address', - [ADDRESS_TYPES.DESTINATION]: 'Destination Address', - [ADDRESS_TYPES.SECOND_DESTINATION]: 'Second Destination Address', - [ADDRESS_TYPES.THIRD_DESTINATION]: 'Third Destination Address', + [ADDRESS_TYPES.DESTINATION]: 'Delivery Address', + [ADDRESS_TYPES.SECOND_DESTINATION]: 'Second Delivery Address', + [ADDRESS_TYPES.THIRD_DESTINATION]: 'Third Delivery Address', }; export const getAddressLabel = (type) => ADDRESS_LABELS_MAP[type]; diff --git a/src/store/auth/actions.js b/src/store/auth/actions.js index a2b0c4abbe9..0982eb14514 100644 --- a/src/store/auth/actions.js +++ b/src/store/auth/actions.js @@ -36,3 +36,9 @@ export const getLoggedInUserFailure = (error) => ({ type: GET_LOGGED_IN_USER_FAILURE, error, }); + +export const SET_UNDER_MAINTENANCE = 'SET_UNDER_MAINTENANCE'; + +export const setUnderMaintenance = () => ({ + type: SET_UNDER_MAINTENANCE, +}); diff --git a/src/store/auth/reducer.js b/src/store/auth/reducer.js index 020197263bd..2b6f9c3b1c8 100644 --- a/src/store/auth/reducer.js +++ b/src/store/auth/reducer.js @@ -8,6 +8,7 @@ export const initialState = { hasSucceeded: false, hasErrored: false, isLoading: true, + underMaintenance: false, }; const authReducer = (state = initialState, action = {}) => { @@ -15,6 +16,13 @@ const authReducer = (state = initialState, action = {}) => { case LOG_OUT: { return initialState; } + case 'SET_UNDER_MAINTENANCE': { + return { + ...state, + isLoading: false, + underMaintenance: true, + }; + } case 'GET_LOGGED_IN_USER_START': { return { ...state, diff --git a/src/store/auth/reducer.test.js b/src/store/auth/reducer.test.js index 695273e1491..dd8abbb1c59 100644 --- a/src/store/auth/reducer.test.js +++ b/src/store/auth/reducer.test.js @@ -1,6 +1,6 @@ import authReducer, { initialState } from './reducer'; import { setActiveRole, logOut } from './actions'; -import { selectIsLoggedIn } from './selectors'; +import { selectIsLoggedIn, selectUnderMaintenance } from './selectors'; import { roleTypes } from 'constants/userRoles'; @@ -96,3 +96,13 @@ describe('selectIsLoggedIn', () => { expect(selectIsLoggedIn(testState)).toEqual(testState.auth.isLoggedIn); }); }); + +describe('setUnderMaintenance', () => { + it('returns boolean as to whether or not app is under maintenance', () => { + const testState = { + auth: { underMaintenance: true }, + }; + + expect(selectUnderMaintenance(testState)).toEqual(testState.auth.underMaintenance); + }); +}); diff --git a/src/store/auth/selectors.js b/src/store/auth/selectors.js index 1c095d87b99..d38eb8f3097 100644 --- a/src/store/auth/selectors.js +++ b/src/store/auth/selectors.js @@ -17,3 +17,7 @@ export function selectGetCurrentUserIsError(state) { export const selectCacValidated = (serviceMember) => { return serviceMember?.cac_validated || false; }; + +export const selectUnderMaintenance = (state) => { + return state.auth.underMaintenance; +}; diff --git a/src/types/order.js b/src/types/order.js index a178a3295dd..cefcc67af2a 100644 --- a/src/types/order.js +++ b/src/types/order.js @@ -2,6 +2,7 @@ import PropTypes from 'prop-types'; import { AddressShape } from './address'; import { BackupContactShape } from './backupContact'; +import { ExistingUploadsShape } from './uploads'; import customerContactTypes from 'constants/customerContactTypes'; import dimensionTypes from 'constants/dimensionTypes'; @@ -48,6 +49,7 @@ export const OrdersInfoShape = PropTypes.shape({ ordersNumber: PropTypes.string, ordersType: PropTypes.string, ordersTypeDetail: PropTypes.string, + ordersDocuments: ExistingUploadsShape, tacMDC: PropTypes.string, sacSDN: PropTypes.string, }); diff --git a/src/utils/shipmentInfo.js b/src/utils/shipmentInfo.js index 3b7dbcc87a5..5b57373d305 100644 --- a/src/utils/shipmentInfo.js +++ b/src/utils/shipmentInfo.js @@ -17,6 +17,7 @@ const determineShipmentInfo = (move, mtoShipments) => { isPPMSelectable: ppmCount === 0, isBoatSelectable: isMoveDraft, isMobileHomeSelectable: isMoveDraft, + isUBSelectable: isMoveDraft, shipmentNumber: existingShipmentCount + 1, }; }; diff --git a/src/utils/test/api.js b/src/utils/test/api.js new file mode 100644 index 00000000000..7f8b10ed66e --- /dev/null +++ b/src/utils/test/api.js @@ -0,0 +1,23 @@ +import { INACCESSIBLE_API_RESPONSE } from 'shared/Inaccessible'; + +// Expected return from a query when an office user tries to access a safety move without the adequate permissions +export const INACCESSIBLE_RETURN_VALUE = { + isLoading: false, + isError: true, + isSuccess: false, + errors: [{ response: { body: { message: INACCESSIBLE_API_RESPONSE } } }], +}; + +// Expected generic response for server side errors +export const ERROR_RETURN_VALUE = { + isLoading: false, + isError: true, + isSuccess: false, +}; + +// Expected response when a query is still running on the backend +export const LOADING_RETURN_VALUE = { + isLoading: true, + isError: false, + isSuccess: false, +}; diff --git a/swagger-def/definitions/MTOServiceItem.yaml b/swagger-def/definitions/MTOServiceItem.yaml index 829c57a6f6a..9cd6119df81 100644 --- a/swagger-def/definitions/MTOServiceItem.yaml +++ b/swagger-def/definitions/MTOServiceItem.yaml @@ -132,6 +132,9 @@ properties: standaloneCrate: type: boolean x-nullable: true + externalCrate: + type: boolean + x-nullable: true serviceRequestDocuments: $ref: 'ServiceRequestDocuments.yaml' estimatedPrice: @@ -142,3 +145,11 @@ properties: type: integer format: cents x-nullable: true + market: + type: string + enum: + - CONUS + - OCONUS + example: CONUS + description: 'To identify whether the service was provided within (CONUS) or (OCONUS)' + x-nullable: true diff --git a/swagger-def/definitions/MTOShipment.yaml b/swagger-def/definitions/MTOShipment.yaml index 2f7e723ab16..2e3f55c6fbc 100644 --- a/swagger-def/definitions/MTOShipment.yaml +++ b/swagger-def/definitions/MTOShipment.yaml @@ -54,7 +54,7 @@ properties: type: string actualDeliveryDate: x-nullable: true - description: The actual date that the shipment was delivered to the destination address by the Prime + description: The actual date that the shipment was delivered to the delivery address by the Prime format: date type: string requestedDeliveryDate: @@ -217,4 +217,3 @@ properties: - 'i' example: 'd' description: 'Single-letter designator for domestic (d) or international (i) shipments' - diff --git a/swagger-def/definitions/PPMShipment.yaml b/swagger-def/definitions/PPMShipment.yaml index cb56b9cc48f..adda81b2a61 100644 --- a/swagger-def/definitions/PPMShipment.yaml +++ b/swagger-def/definitions/PPMShipment.yaml @@ -153,6 +153,12 @@ properties: format: cents x-nullable: true x-omitempty: false + maxIncentive: + description: The max amount the government will pay the service member to move their belongings based on the moving date, locations, and shipment weight. + type: integer + format: cents + x-nullable: true + x-omitempty: false finalIncentive: description: > The final calculated incentive for the PPM shipment. This does not include **SIT** as it is a reimbursement. diff --git a/swagger-def/definitions/ReServiceCode.yaml b/swagger-def/definitions/ReServiceCode.yaml index 80fd6174903..70dd60d786b 100644 --- a/swagger-def/definitions/ReServiceCode.yaml +++ b/swagger-def/definitions/ReServiceCode.yaml @@ -32,7 +32,6 @@ enum: - ICOLH - ICOUB - ICRT - - ICRTSA - IDASIT - IDDSIT - IDFSIT diff --git a/swagger-def/definitions/ShipmentAddressUpdate.yaml b/swagger-def/definitions/ShipmentAddressUpdate.yaml index 95ed46356b9..09ac9ca1b08 100644 --- a/swagger-def/definitions/ShipmentAddressUpdate.yaml +++ b/swagger-def/definitions/ShipmentAddressUpdate.yaml @@ -1,5 +1,5 @@ description: > - This represents a destination address change request made by the Prime that is either auto-approved or requires review if the pricing criteria has changed. If criteria has changed, then it must be approved or rejected by a TOO. + This represents a delivery address change request made by the Prime that is either auto-approved or requires review if the pricing criteria has changed. If criteria has changed, then it must be approved or rejected by a TOO. type: object properties: id: @@ -33,12 +33,12 @@ properties: sitOriginalAddress: $ref: 'Address.yaml' oldSitDistanceBetween: - description: The distance between the original SIT address and the previous/old destination address of shipment + description: The distance between the original SIT address and the previous/old delivery address of shipment example: 50 minimum: 0 type: integer newSitDistanceBetween: - description: The distance between the original SIT address and requested new destination address of shipment + description: The distance between the original SIT address and requested new delivery address of shipment example: 88 minimum: 0 type: integer diff --git a/swagger-def/definitions/prime/MTOServiceItemInternationalCrating.yaml b/swagger-def/definitions/prime/MTOServiceItemInternationalCrating.yaml new file mode 100644 index 00000000000..998fa75299a --- /dev/null +++ b/swagger-def/definitions/prime/MTOServiceItemInternationalCrating.yaml @@ -0,0 +1,49 @@ +description: Describes a international crating/uncrating service item subtype of a MTOServiceItem. +allOf: + - $ref: 'MTOServiceItem.yaml' + - type: object + properties: + reServiceCode: + type: string + description: A unique code for the service item. Indicates if the service is for crating (ICRT) or uncrating (IUCRT). + enum: + - ICRT # International Crating + - IUCRT # International Uncrating + item: + description: The dimensions of the item being crated. + allOf: + - $ref: 'MTOServiceItemDimension.yaml' + crate: + description: The dimensions for the crate the item will be shipped in. + allOf: + - $ref: 'MTOServiceItemDimension.yaml' + description: + type: string + example: Decorated horse head to be crated. + description: A description of the item being crated. + reason: + type: string + example: Storage items need to be picked up + description: > + The contractor's explanation for why an item needed to be crated or uncrated. Used by the TOO while + deciding to approve or reject the service item. + x-nullable: true + x-omitempty: false + standaloneCrate: + type: boolean + x-nullable: true + externalCrate: + type: boolean + x-nullable: true + market: + type: string + enum: + - CONUS + - OCONUS + example: CONUS + description: 'To identify whether the service was provided within (CONUS) or (OCONUS)' + required: + - reServiceCode + - item + - crate + - description diff --git a/swagger-def/definitions/prime/MTOServiceItemModelType.yaml b/swagger-def/definitions/prime/MTOServiceItemModelType.yaml index 05fb6a2f5fd..577d3d55463 100644 --- a/swagger-def/definitions/prime/MTOServiceItemModelType.yaml +++ b/swagger-def/definitions/prime/MTOServiceItemModelType.yaml @@ -8,6 +8,7 @@ description: > * DDFSIT, DDASIT - MTOServiceItemDestSIT * DOSHUT, DDSHUT - MTOServiceItemShuttle * DCRT, DUCRT - MTOServiceItemDomesticCrating + * ICRT, IUCRT - MTOServiceItemInternationalCrating The documentation will then update with the supported fields. type: string @@ -17,3 +18,4 @@ enum: - MTOServiceItemDestSIT - MTOServiceItemShuttle - MTOServiceItemDomesticCrating + - MTOServiceItemInternationalCrating diff --git a/swagger-def/definitions/prime/PPMShipment.yaml b/swagger-def/definitions/prime/PPMShipment.yaml index 1f77972c88e..9c0f9268d08 100644 --- a/swagger-def/definitions/prime/PPMShipment.yaml +++ b/swagger-def/definitions/prime/PPMShipment.yaml @@ -108,6 +108,12 @@ properties: format: cents x-nullable: true x-omitempty: false + maxIncentive: + description: The max amount the government will pay the service member to move their belongings based on the moving date, locations, and shipment weight. + type: integer + format: cents + x-nullable: true + x-omitempty: false hasRequestedAdvance: description: > Indicates whether an advance has been requested for the PPM shipment. diff --git a/swagger-def/definitions/prime/v3/PPMShipment.yaml b/swagger-def/definitions/prime/v3/PPMShipment.yaml index 6ea324e438f..8923694af0b 100644 --- a/swagger-def/definitions/prime/v3/PPMShipment.yaml +++ b/swagger-def/definitions/prime/v3/PPMShipment.yaml @@ -136,6 +136,12 @@ properties: format: cents x-nullable: true x-omitempty: false + maxIncentive: + description: The max amount the government will pay the service member to move their belongings based on the moving date, locations, and shipment weight. + type: integer + format: cents + x-nullable: true + x-omitempty: false hasRequestedAdvance: description: > Indicates whether an advance has been requested for the PPM shipment. diff --git a/swagger-def/ghc.yaml b/swagger-def/ghc.yaml index 5e25333caa6..b8876b5c4e7 100644 --- a/swagger-def/ghc.yaml +++ b/swagger-def/ghc.yaml @@ -197,7 +197,7 @@ paths: description: requested page of results perPage: type: integer - dodID: + edipi: description: DOD ID type: string minLength: 10 @@ -221,7 +221,7 @@ paths: sort: type: string x-nullable: true - enum: [customerName, dodID, emplid, branch, personalEmail, telephone] + enum: [customerName, edipi, emplid, branch, personalEmail, telephone] order: type: string x-nullable: true @@ -3061,7 +3061,7 @@ paths: format: uuid post: summary: Flags a move for financial office review - description: This sets a flag which indicates that the move should be reviewed by a fincancial office. For example, if the origin or destination address of a shipment is far from the duty location and may incur excess costs to the customer. + description: This sets a flag which indicates that the move should be reviewed by a fincancial office. For example, if the origin or delivery address of a shipment is far from the duty location and may incur excess costs to the customer. operationId: setFinancialReviewFlag tags: - move @@ -3337,7 +3337,7 @@ paths: enum: [ customerName, - dodID, + edipi, emplid, branch, locator, @@ -3377,7 +3377,7 @@ paths: type: string description: filters using a counselingOffice name of the move - in: query - name: dodID + name: edipi type: string description: filters to match the unique service member's DoD ID - in: query @@ -3567,7 +3567,7 @@ paths: enum: [ customerName, - dodID, + edipi, emplid, branch, locator, @@ -3577,6 +3577,7 @@ paths: requestedMoveDate, appearedInTooAt, assignedTo, + counselingOffice, ] description: field that results should be sorted by - in: query @@ -3594,7 +3595,7 @@ paths: name: customerName type: string - in: query - name: dodID + name: edipi type: string - in: query name: emplid @@ -3642,6 +3643,10 @@ paths: type: string description: | Used to illustrate which user is assigned to this move. + - in: query + name: counselingOffice + type: string + description: filters using a counselingOffice name of the move responses: '200': description: Successfully returned all moves matching the criteria @@ -3665,7 +3670,7 @@ paths: - in: query name: sort type: string - enum: [customerName, locator, submittedAt, branch, status, dodID, emplid, age, originDutyLocation, assignedTo] + enum: [customerName, locator, submittedAt, branch, status, edipi, emplid, age, originDutyLocation, assignedTo, counselingOffice] description: field that results should be sorted by - in: query name: order @@ -3695,7 +3700,7 @@ paths: name: customerName type: string - in: query - name: dodID + name: edipi type: string - in: query name: emplid @@ -3711,6 +3716,10 @@ paths: type: string description: | Used to illustrate which user is assigned to this payment request. + - in: query + name: counselingOffice + type: string + description: filters using a counselingOffice name of the move - in: query name: status type: array @@ -3771,7 +3780,7 @@ paths: minLength: 6 maxLength: 6 x-nullable: true - dodID: + edipi: description: DOD ID type: string minLength: 10 @@ -3832,7 +3841,7 @@ paths: enum: [ customerName, - dodID, + edipi, emplid, branch, locator, @@ -4275,6 +4284,35 @@ paths: - move description: unassigns either a services counselor, task ordering officer, or task invoicing officer from the move operationId: deleteAssignedOfficeUser + /moves/{officeUserID}/CheckForLockedMovesAndUnlock: + parameters: + - description: ID of the move's officer + in: path + name: officeUserID + required: true + format: uuid + type: string + patch: + consumes: + - application/json + produces: + - application/json + responses: + '200': + description: Successfully unlocked officer's move(s). + schema: + type: object + properties: + successMessage: + type: string + example: OK + '500': + $ref: '#/responses/ServerError' + tags: + - move + description: >- + Finds and unlocks any locked moves by an office user + operationId: checkForLockedMovesAndUnlock definitions: ApplicationParameters: type: object @@ -4815,7 +4853,7 @@ definitions: type: string example: Doe x-nullable: true - dodID: + edipi: type: string x-nullable: true emplid: @@ -5025,7 +5063,7 @@ definitions: readOnly: true financialReviewRemarks: type: string - example: Destination address is too far from duty location + example: Delivery Address is too far from duty location x-nullable: true readOnly: true closeoutOffice: @@ -7025,6 +7063,9 @@ definitions: $ref: '#/definitions/AvailableOfficeUsers' assignable: type: boolean + counselingOffice: + type: string + x-nullable: true QueuePaymentRequests: type: array items: @@ -7066,7 +7107,7 @@ definitions: type: string example: Doe x-nullable: true - dodID: + edipi: type: string example: 1234567890 x-nullable: true diff --git a/swagger-def/prime.yaml b/swagger-def/prime.yaml index 2122b1f2cb5..23016ac2b1e 100644 --- a/swagger-def/prime.yaml +++ b/swagger-def/prime.yaml @@ -269,9 +269,9 @@ paths: summary: updateMTOShipmentAddress description: | ### Functionality - This endpoint is used to **update** the pickup, secondary, and destination addresses on an MTO Shipment. mto-shipments/{mtoShipmentID}/shipment-address-updates is for updating a delivery address. The address details completely replace the original, except for the UUID. + This endpoint is used to **update** the pickup, secondary, and delivery addresses on an MTO Shipment. mto-shipments/{mtoShipmentID}/shipment-address-updates is for updating a delivery address. The address details completely replace the original, except for the UUID. Therefore a complete address should be sent in the request. - When a destination address on a shipment is updated, the destination SIT service items address ID will also be updated so that shipment and service item final destinations match. + When a delivery address on a shipment is updated, the destination SIT service items address ID will also be updated so that shipment and service item final destinations match. This endpoint **cannot create** an address. To create an address on an MTO shipment, the caller must use [updateMTOShipment](#operation/updateMTOShipment) as the parent shipment has to be updated with the appropriate link to the address. @@ -336,14 +336,14 @@ paths: summary: updateShipmentDestinationAddress description: | ### Functionality - This endpoint is used so the Prime can request an **update** for the destination address on an MTO Shipment, - after the destination address has already been approved. + This endpoint is used so the Prime can request an **update** for the delivery address on an MTO Shipment, + after the delivery address has already been approved. This endpoint and operation only supports the following shipment types: - HHG - NTSR - For HHG shipments, if automatically approved or TOO approves, this will update the final destination address values for destination SIT service items to be the same as the changed destination address that was approved. + For HHG shipments, if automatically approved or TOO approves, this will update the final delivery address values for destination SIT service items to be the same as the changed delivery address that was approved. Address updates will be automatically approved unless they change: - The service area @@ -798,7 +798,7 @@ paths: This endpoint supports different body definitions. In the modelType field below, select the modelType corresponding to the service item you wish to update and the documentation will update with the new definition. - * Addresses: To update a destination service item's SIT destination final address, update the shipment destination address. + * Addresses: To update a destination service item's SIT destination final address, update the shipment delivery address. For approved shipments, please use [updateShipmentDestinationAddress](#mtoShipment/updateShipmentDestinationAddress). For shipments not yet approved, please use [updateMTOShipmentAddress](#mtoShipment/updateMTOShipmentAddress). @@ -1511,6 +1511,8 @@ definitions: $ref: 'definitions/prime/MTOServiceItemDestSIT.yaml' MTOServiceItemDomesticCrating: # spectral oas2-unused-definition is OK here due to polymorphism $ref: 'definitions/prime/MTOServiceItemDomesticCrating.yaml' + MTOServiceItemInternationalCrating: # spectral oas2-unused-definition is OK here due to polymorphism + $ref: 'definitions/prime/MTOServiceItemInternationalCrating.yaml' MTOServiceItemOriginSIT: # spectral oas2-unused-definition is OK here due to polymorphism $ref: 'definitions/prime/MTOServiceItemOriginSIT.yaml' MTOServiceItemShuttle: # spectral oas2-unused-definition is OK here due to polymorphism @@ -1527,7 +1529,7 @@ definitions: allOf: - $ref: 'definitions/prime/MTOShipmentWithoutServiceItems.yaml' UpdateShipmentDestinationAddress: - description: UpdateShipmentDestinationAddress contains the fields required for the prime to request an update for the destination address on an MTO Shipment. + description: UpdateShipmentDestinationAddress contains the fields required for the prime to request an update for the delivery address on an MTO Shipment. type: object properties: newAddress: diff --git a/swagger-def/prime_v2.yaml b/swagger-def/prime_v2.yaml index d49a48f3139..3ff714d6155 100644 --- a/swagger-def/prime_v2.yaml +++ b/swagger-def/prime_v2.yaml @@ -351,6 +351,8 @@ definitions: $ref: 'definitions/prime/MTOServiceItemDestSIT.yaml' MTOServiceItemDomesticCrating: # spectral oas2-unused-definition is OK here due to polymorphism $ref: 'definitions/prime/MTOServiceItemDomesticCrating.yaml' + MTOServiceItemInternationalCrating: # spectral oas2-unused-definition is OK here due to polymorphism + $ref: 'definitions/prime/MTOServiceItemInternationalCrating.yaml' MTOServiceItemOriginSIT: # spectral oas2-unused-definition is OK here due to polymorphism $ref: 'definitions/prime/MTOServiceItemOriginSIT.yaml' MTOServiceItemShuttle: # spectral oas2-unused-definition is OK here due to polymorphism @@ -851,7 +853,7 @@ definitions: x-nullable: true x-omitempty: false UpdateShipmentDestinationAddress: - description: UpdateShipmentDestinationAddress contains the fields required for the prime to request an update for the destination address on an MTO Shipment. + description: UpdateShipmentDestinationAddress contains the fields required for the prime to request an update for the delivery address on an MTO Shipment. type: object properties: newAddress: diff --git a/swagger-def/prime_v3.yaml b/swagger-def/prime_v3.yaml index 67af4ef4228..67cf77df908 100644 --- a/swagger-def/prime_v3.yaml +++ b/swagger-def/prime_v3.yaml @@ -189,20 +189,21 @@ paths: summary: Mobile Home Shipment value: { - "mobileHomeShipment": { - "heightFeet": 2, - "heightInches": 2, - "lengthFeet": 2, - "lengthInches": 0, - "make": "make", - "model": "model", - "widthFeet": 2, - "widthInches": 2, - "year": 1999 - }, - "counselorRemarks": "test", - "moveTaskOrderID": "d4d95b22-2d9d-428b-9a11-284455aa87ba", - "shipmentType": "MOBILE_HOME" + 'mobileHomeShipment': + { + 'heightFeet': 2, + 'heightInches': 2, + 'lengthFeet': 2, + 'lengthInches': 0, + 'make': 'make', + 'model': 'model', + 'widthFeet': 2, + 'widthInches': 2, + 'year': 1999, + }, + 'counselorRemarks': 'test', + 'moveTaskOrderID': 'd4d95b22-2d9d-428b-9a11-284455aa87ba', + 'shipmentType': 'MOBILE_HOME', } responses: '200': @@ -341,6 +342,8 @@ definitions: $ref: 'definitions/prime/MTOServiceItemDestSIT.yaml' MTOServiceItemDomesticCrating: # spectral oas2-unused-definition is OK here due to polymorphism $ref: 'definitions/prime/MTOServiceItemDomesticCrating.yaml' + MTOServiceItemInternationalCrating: # spectral oas2-unused-definition is OK here due to polymorphism + $ref: 'definitions/prime/MTOServiceItemInternationalCrating.yaml' MTOServiceItemOriginSIT: # spectral oas2-unused-definition is OK here due to polymorphism $ref: 'definitions/prime/MTOServiceItemOriginSIT.yaml' MTOServiceItemShuttle: # spectral oas2-unused-definition is OK here due to polymorphism @@ -389,11 +392,7 @@ definitions: items: $ref: 'definitions/prime/MTOServiceItem.yaml' pickupAddress: - description: The address where the movers should pick up this shipment. - allOf: - - $ref: 'definitions/Address.yaml' - destinationAddress: - description: Where the movers should deliver this shipment. + description: The primary address where the movers should pick up this shipment. allOf: - $ref: 'definitions/Address.yaml' secondaryPickupAddress: @@ -404,12 +403,16 @@ definitions: description: The third address where the movers should pick up this shipment. allOf: - $ref: 'definitions/Address.yaml' + destinationAddress: + description: primary location the movers should deliver this shipment. + allOf: + - $ref: 'definitions/Address.yaml' secondaryDestinationAddress: - description: The second address where the movers should deliver this shipment. + description: second location where the movers should deliver this shipment. allOf: - $ref: 'definitions/Address.yaml' tertiaryDestinationAddress: - description: The third address where the movers should deliver this shipment. + description: third location where the movers should deliver this shipment. allOf: - $ref: 'definitions/Address.yaml' shipmentType: @@ -460,11 +463,11 @@ definitions: allOf: - $ref: 'definitions/Address.yaml' secondaryPickupAddress: - description: An optional secondary pickup location address near the origin where additional goods exist. + description: An optional secondary Pickup Address address near the origin where additional goods exist. allOf: - $ref: 'definitions/Address.yaml' tertiaryPickupAddress: - description: An optional tertiary pickup location address near the origin where additional goods exist. + description: An optional tertiary Pickup Address address near the origin where additional goods exist. allOf: - $ref: 'definitions/Address.yaml' destinationAddress: @@ -699,7 +702,7 @@ definitions: x-nullable: true secondaryPickupAddress: description: > - An optional secondary pickup location near the origin where additional goods exist. + An optional secondary Pickup Address near the origin where additional goods exist. allOf: - $ref: 'definitions/Address.yaml' hasTertiaryPickupAddress: @@ -708,7 +711,7 @@ definitions: x-nullable: true tertiaryPickupAddress: description: > - An optional third pickup location near the origin where additional goods exist. + An optional third Pickup Address near the origin where additional goods exist. allOf: - $ref: 'definitions/Address.yaml' destinationAddress: @@ -855,6 +858,14 @@ definitions: when they enter shipment details. allOf: - $ref: 'definitions/Address.yaml' + secondaryPickupAddress: + description: A second pickup address for this shipment, if the customer entered one. An optional field. + allOf: + - $ref: 'definitions/Address.yaml' + tertiaryPickupAddress: + description: A third pickup address for this shipment, if the customer entered one. An optional field. + allOf: + - $ref: 'definitions/Address.yaml' destinationAddress: description: | Where the movers should deliver this shipment. Often provided by the customer when they enter shipment details @@ -864,24 +875,16 @@ definitions: final destination due to the shipment being diverted or placed in SIT. allOf: - $ref: 'definitions/Address.yaml' - destinationType: - $ref: 'definitions/DestinationType.yaml' - secondaryPickupAddress: - description: A second pickup address for this shipment, if the customer entered one. An optional field. - allOf: - - $ref: 'definitions/Address.yaml' secondaryDeliveryAddress: description: A second delivery address for this shipment, if the customer entered one. An optional field. allOf: - $ref: 'definitions/Address.yaml' - tertiaryPickupAddress: - description: A third pickup address for this shipment, if the customer entered one. An optional field. - allOf: - - $ref: 'definitions/Address.yaml' tertiaryDeliveryAddress: description: A third delivery address for this shipment, if the customer entered one. An optional field. allOf: - $ref: 'definitions/Address.yaml' + destinationType: + $ref: 'definitions/DestinationType.yaml' storageFacility: allOf: - x-nullable: true @@ -931,7 +934,7 @@ definitions: x-nullable: true x-omitempty: false UpdateShipmentDestinationAddress: - description: UpdateShipmentDestinationAddress contains the fields required for the prime to request an update for the destination address on an MTO Shipment. + description: UpdateShipmentDestinationAddress contains the fields required for the prime to request an update for the delivery address on an MTO Shipment. type: object properties: newAddress: diff --git a/swagger-def/support.yaml b/swagger-def/support.yaml index f2bdcbccc6d..1f43ca2d177 100644 --- a/swagger-def/support.yaml +++ b/swagger-def/support.yaml @@ -1284,6 +1284,7 @@ definitions: * DDFSIT, DDASIT - MTOServiceItemDestSIT * DOSHUT, DDSHUT - MTOServiceItemShuttle * DCRT, DUCRT - MTOServiceItemDomesticCrating + * ICRT, IUCRT - MTOServiceItemInternationalCrating The documentation will then update with the supported fields. @@ -1294,6 +1295,7 @@ definitions: - MTOServiceItemDestSIT - MTOServiceItemShuttle - MTOServiceItemDomesticCrating + - MTOServiceItemInternationalCrating MTOServiceItemOriginSIT: # spectral oas2-unused-definition is OK here due to polymorphism description: Describes a domestic origin SIT service item. Subtype of a MTOServiceItem. allOf: diff --git a/swagger/ghc.yaml b/swagger/ghc.yaml index 0e878b3ff21..bd583c9af00 100644 --- a/swagger/ghc.yaml +++ b/swagger/ghc.yaml @@ -217,7 +217,7 @@ paths: description: requested page of results perPage: type: integer - dodID: + edipi: description: DOD ID type: string minLength: 10 @@ -243,7 +243,7 @@ paths: x-nullable: true enum: - customerName - - dodID + - edipi - emplid - branch - personalEmail @@ -3157,9 +3157,9 @@ paths: summary: Flags a move for financial office review description: >- This sets a flag which indicates that the move should be reviewed by a - fincancial office. For example, if the origin or destination address of - a shipment is far from the duty location and may incur excess costs to - the customer. + fincancial office. For example, if the origin or delivery address of a + shipment is far from the duty location and may incur excess costs to the + customer. operationId: setFinancialReviewFlag tags: - move @@ -3458,7 +3458,7 @@ paths: type: string enum: - customerName - - dodID + - edipi - emplid - branch - locator @@ -3499,7 +3499,7 @@ paths: type: string description: filters using a counselingOffice name of the move - in: query - name: dodID + name: edipi type: string description: filters to match the unique service member's DoD ID - in: query @@ -3717,7 +3717,7 @@ paths: type: string enum: - customerName - - dodID + - edipi - emplid - branch - locator @@ -3727,6 +3727,7 @@ paths: - requestedMoveDate - appearedInTooAt - assignedTo + - counselingOffice description: field that results should be sorted by - in: query name: order @@ -3745,7 +3746,7 @@ paths: name: customerName type: string - in: query - name: dodID + name: edipi type: string - in: query name: emplid @@ -3795,6 +3796,10 @@ paths: type: string description: | Used to illustrate which user is assigned to this move. + - in: query + name: counselingOffice + type: string + description: filters using a counselingOffice name of the move responses: '200': description: Successfully returned all moves matching the criteria @@ -3826,11 +3831,12 @@ paths: - submittedAt - branch - status - - dodID + - edipi - emplid - age - originDutyLocation - assignedTo + - counselingOffice description: field that results should be sorted by - in: query name: order @@ -3864,7 +3870,7 @@ paths: name: customerName type: string - in: query - name: dodID + name: edipi type: string - in: query name: emplid @@ -3880,6 +3886,10 @@ paths: type: string description: | Used to illustrate which user is assigned to this payment request. + - in: query + name: counselingOffice + type: string + description: filters using a counselingOffice name of the move - in: query name: status type: array @@ -3943,7 +3953,7 @@ paths: minLength: 6 maxLength: 6 x-nullable: true - dodID: + edipi: description: DOD ID type: string minLength: 10 @@ -4003,7 +4013,7 @@ paths: x-nullable: true enum: - customerName - - dodID + - edipi - emplid - branch - locator @@ -4476,6 +4486,34 @@ paths: unassigns either a services counselor, task ordering officer, or task invoicing officer from the move operationId: deleteAssignedOfficeUser + /moves/{officeUserID}/CheckForLockedMovesAndUnlock: + parameters: + - description: ID of the move's officer + in: path + name: officeUserID + required: true + format: uuid + type: string + patch: + consumes: + - application/json + produces: + - application/json + responses: + '200': + description: Successfully unlocked officer's move(s). + schema: + type: object + properties: + successMessage: + type: string + example: OK + '500': + $ref: '#/responses/ServerError' + tags: + - move + description: Finds and unlocks any locked moves by an office user + operationId: checkForLockedMovesAndUnlock definitions: ApplicationParameters: type: object @@ -5019,7 +5057,7 @@ definitions: type: string example: Doe x-nullable: true - dodID: + edipi: type: string x-nullable: true emplid: @@ -5235,7 +5273,7 @@ definitions: readOnly: true financialReviewRemarks: type: string - example: Destination address is too far from duty location + example: Delivery Address is too far from duty location x-nullable: true readOnly: true closeoutOffice: @@ -7323,6 +7361,9 @@ definitions: $ref: '#/definitions/AvailableOfficeUsers' assignable: type: boolean + counselingOffice: + type: string + x-nullable: true QueuePaymentRequests: type: array items: @@ -7364,7 +7405,7 @@ definitions: type: string example: Doe x-nullable: true - dodID: + edipi: type: string example: 1234567890 x-nullable: true @@ -8481,6 +8522,9 @@ definitions: standaloneCrate: type: boolean x-nullable: true + externalCrate: + type: boolean + x-nullable: true serviceRequestDocuments: $ref: '#/definitions/ServiceRequestDocuments' estimatedPrice: @@ -8491,6 +8535,16 @@ definitions: type: integer format: cents x-nullable: true + market: + type: string + enum: + - CONUS + - OCONUS + example: CONUS + description: >- + To identify whether the service was provided within (CONUS) or + (OCONUS) + x-nullable: true MTOServiceItems: description: A list of service items connected to this shipment. type: array @@ -9800,6 +9854,15 @@ definitions: format: cents x-nullable: true x-omitempty: false + maxIncentive: + description: >- + The max amount the government will pay the service member to move + their belongings based on the moving date, locations, and shipment + weight. + type: integer + format: cents + x-nullable: true + x-omitempty: false finalIncentive: description: > The final calculated incentive for the PPM shipment. This does not @@ -10043,10 +10106,10 @@ definitions: - APPROVED ShipmentAddressUpdate: description: > - This represents a destination address change request made by the Prime - that is either auto-approved or requires review if the pricing criteria - has changed. If criteria has changed, then it must be approved or rejected - by a TOO. + This represents a delivery address change request made by the Prime that + is either auto-approved or requires review if the pricing criteria has + changed. If criteria has changed, then it must be approved or rejected by + a TOO. type: object properties: id: @@ -10082,14 +10145,14 @@ definitions: oldSitDistanceBetween: description: >- The distance between the original SIT address and the previous/old - destination address of shipment + delivery address of shipment example: 50 minimum: 0 type: integer newSitDistanceBetween: description: >- The distance between the original SIT address and requested new - destination address of shipment + delivery address of shipment example: 88 minimum: 0 type: integer @@ -10161,7 +10224,7 @@ definitions: actualDeliveryDate: x-nullable: true description: >- - The actual date that the shipment was delivered to the destination + The actual date that the shipment was delivered to the delivery address by the Prime format: date type: string diff --git a/swagger/internal.yaml b/swagger/internal.yaml index 419ede47b9d..0d6ddf436e0 100644 --- a/swagger/internal.yaml +++ b/swagger/internal.yaml @@ -3702,6 +3702,15 @@ definitions: format: cents x-nullable: true x-omitempty: false + maxIncentive: + description: >- + The max amount the government will pay the service member to move + their belongings based on the moving date, locations, and shipment + weight. + type: integer + format: cents + x-nullable: true + x-omitempty: false finalIncentive: description: > The final calculated incentive for the PPM shipment. This does not diff --git a/swagger/prime.yaml b/swagger/prime.yaml index 1d173ebf5f0..492350a4fd4 100644 --- a/swagger/prime.yaml +++ b/swagger/prime.yaml @@ -357,15 +357,15 @@ paths: description: > ### Functionality - This endpoint is used to **update** the pickup, secondary, and - destination addresses on an MTO Shipment. + This endpoint is used to **update** the pickup, secondary, and delivery + addresses on an MTO Shipment. mto-shipments/{mtoShipmentID}/shipment-address-updates is for updating a delivery address. The address details completely replace the original, except for the UUID. Therefore a complete address should be sent in the request. - When a destination address on a shipment is updated, the destination SIT + When a delivery address on a shipment is updated, the destination SIT service items address ID will also be updated so that shipment and service item final destinations match. @@ -447,9 +447,9 @@ paths: ### Functionality This endpoint is used so the Prime can request an **update** for the - destination address on an MTO Shipment, + delivery address on an MTO Shipment, - after the destination address has already been approved. + after the delivery address has already been approved. This endpoint and operation only supports the following shipment types: @@ -460,9 +460,8 @@ paths: For HHG shipments, if automatically approved or TOO approves, this will - update the final destination address values for destination SIT service - items to be the same as the changed destination address that was - approved. + update the final delivery address values for destination SIT service + items to be the same as the changed delivery address that was approved. Address updates will be automatically approved unless they change: @@ -1003,7 +1002,7 @@ paths: to the service item you wish to update and the documentation will update with the new definition. * Addresses: To update a destination service item's SIT destination - final address, update the shipment destination address. + final address, update the shipment delivery address. For approved shipments, please use [updateShipmentDestinationAddress](#mtoShipment/updateShipmentDestinationAddress). @@ -2081,6 +2080,63 @@ definitions: - item - crate - description + MTOServiceItemInternationalCrating: + description: >- + Describes a international crating/uncrating service item subtype of a + MTOServiceItem. + allOf: + - $ref: '#/definitions/MTOServiceItem' + - type: object + properties: + reServiceCode: + type: string + description: >- + A unique code for the service item. Indicates if the service is + for crating (ICRT) or uncrating (IUCRT). + enum: + - ICRT + - IUCRT + item: + description: The dimensions of the item being crated. + allOf: + - $ref: '#/definitions/MTOServiceItemDimension' + crate: + description: The dimensions for the crate the item will be shipped in. + allOf: + - $ref: '#/definitions/MTOServiceItemDimension' + description: + type: string + example: Decorated horse head to be crated. + description: A description of the item being crated. + reason: + type: string + example: Storage items need to be picked up + description: > + The contractor's explanation for why an item needed to be crated + or uncrated. Used by the TOO while deciding to approve or reject + the service item. + x-nullable: true + x-omitempty: false + standaloneCrate: + type: boolean + x-nullable: true + externalCrate: + type: boolean + x-nullable: true + market: + type: string + enum: + - CONUS + - OCONUS + example: CONUS + description: >- + To identify whether the service was provided within (CONUS) or + (OCONUS) + required: + - reServiceCode + - item + - crate + - description MTOServiceItemOriginSIT: description: Describes a domestic origin SIT service item. Subtype of a MTOServiceItem. allOf: @@ -2192,7 +2248,7 @@ definitions: UpdateShipmentDestinationAddress: description: >- UpdateShipmentDestinationAddress contains the fields required for the - prime to request an update for the destination address on an MTO Shipment. + prime to request an update for the delivery address on an MTO Shipment. type: object properties: newAddress: @@ -3300,6 +3356,7 @@ definitions: * DDFSIT, DDASIT - MTOServiceItemDestSIT * DOSHUT, DDSHUT - MTOServiceItemShuttle * DCRT, DUCRT - MTOServiceItemDomesticCrating + * ICRT, IUCRT - MTOServiceItemInternationalCrating The documentation will then update with the supported fields. type: string @@ -3309,6 +3366,7 @@ definitions: - MTOServiceItemDestSIT - MTOServiceItemShuttle - MTOServiceItemDomesticCrating + - MTOServiceItemInternationalCrating ServiceRequestDocument: properties: uploads: @@ -3782,6 +3840,15 @@ definitions: format: cents x-nullable: true x-omitempty: false + maxIncentive: + description: >- + The max amount the government will pay the service member to move + their belongings based on the moving date, locations, and shipment + weight. + type: integer + format: cents + x-nullable: true + x-omitempty: false hasRequestedAdvance: description: | Indicates whether an advance has been requested for the PPM shipment. @@ -3877,10 +3944,10 @@ definitions: - APPROVED ShipmentAddressUpdate: description: > - This represents a destination address change request made by the Prime - that is either auto-approved or requires review if the pricing criteria - has changed. If criteria has changed, then it must be approved or rejected - by a TOO. + This represents a delivery address change request made by the Prime that + is either auto-approved or requires review if the pricing criteria has + changed. If criteria has changed, then it must be approved or rejected by + a TOO. type: object properties: id: @@ -3916,14 +3983,14 @@ definitions: oldSitDistanceBetween: description: >- The distance between the original SIT address and the previous/old - destination address of shipment + delivery address of shipment example: 50 minimum: 0 type: integer newSitDistanceBetween: description: >- The distance between the original SIT address and requested new - destination address of shipment + delivery address of shipment example: 88 minimum: 0 type: integer @@ -4313,7 +4380,6 @@ definitions: - ICOLH - ICOUB - ICRT - - ICRTSA - IDASIT - IDDSIT - IDFSIT diff --git a/swagger/prime_v2.yaml b/swagger/prime_v2.yaml index 57b5627e1aa..3473807ad67 100644 --- a/swagger/prime_v2.yaml +++ b/swagger/prime_v2.yaml @@ -532,6 +532,63 @@ definitions: - item - crate - description + MTOServiceItemInternationalCrating: + description: >- + Describes a international crating/uncrating service item subtype of a + MTOServiceItem. + allOf: + - $ref: '#/definitions/MTOServiceItem' + - type: object + properties: + reServiceCode: + type: string + description: >- + A unique code for the service item. Indicates if the service is + for crating (ICRT) or uncrating (IUCRT). + enum: + - ICRT + - IUCRT + item: + description: The dimensions of the item being crated. + allOf: + - $ref: '#/definitions/MTOServiceItemDimension' + crate: + description: The dimensions for the crate the item will be shipped in. + allOf: + - $ref: '#/definitions/MTOServiceItemDimension' + description: + type: string + example: Decorated horse head to be crated. + description: A description of the item being crated. + reason: + type: string + example: Storage items need to be picked up + description: > + The contractor's explanation for why an item needed to be crated + or uncrated. Used by the TOO while deciding to approve or reject + the service item. + x-nullable: true + x-omitempty: false + standaloneCrate: + type: boolean + x-nullable: true + externalCrate: + type: boolean + x-nullable: true + market: + type: string + enum: + - CONUS + - OCONUS + example: CONUS + description: >- + To identify whether the service was provided within (CONUS) or + (OCONUS) + required: + - reServiceCode + - item + - crate + - description MTOServiceItemOriginSIT: description: Describes a domestic origin SIT service item. Subtype of a MTOServiceItem. allOf: @@ -1206,7 +1263,7 @@ definitions: UpdateShipmentDestinationAddress: description: >- UpdateShipmentDestinationAddress contains the fields required for the - prime to request an update for the destination address on an MTO Shipment. + prime to request an update for the delivery address on an MTO Shipment. type: object properties: newAddress: @@ -1993,6 +2050,7 @@ definitions: * DDFSIT, DDASIT - MTOServiceItemDestSIT * DOSHUT, DDSHUT - MTOServiceItemShuttle * DCRT, DUCRT - MTOServiceItemDomesticCrating + * ICRT, IUCRT - MTOServiceItemInternationalCrating The documentation will then update with the supported fields. type: string @@ -2002,6 +2060,7 @@ definitions: - MTOServiceItemDestSIT - MTOServiceItemShuttle - MTOServiceItemDomesticCrating + - MTOServiceItemInternationalCrating ServiceRequestDocument: properties: uploads: @@ -2475,6 +2534,15 @@ definitions: format: cents x-nullable: true x-omitempty: false + maxIncentive: + description: >- + The max amount the government will pay the service member to move + their belongings based on the moving date, locations, and shipment + weight. + type: integer + format: cents + x-nullable: true + x-omitempty: false hasRequestedAdvance: description: | Indicates whether an advance has been requested for the PPM shipment. @@ -2570,10 +2638,10 @@ definitions: - APPROVED ShipmentAddressUpdate: description: > - This represents a destination address change request made by the Prime - that is either auto-approved or requires review if the pricing criteria - has changed. If criteria has changed, then it must be approved or rejected - by a TOO. + This represents a delivery address change request made by the Prime that + is either auto-approved or requires review if the pricing criteria has + changed. If criteria has changed, then it must be approved or rejected by + a TOO. type: object properties: id: @@ -2609,14 +2677,14 @@ definitions: oldSitDistanceBetween: description: >- The distance between the original SIT address and the previous/old - destination address of shipment + delivery address of shipment example: 50 minimum: 0 type: integer newSitDistanceBetween: description: >- The distance between the original SIT address and requested new - destination address of shipment + delivery address of shipment example: 88 minimum: 0 type: integer @@ -3070,7 +3138,6 @@ definitions: - ICOLH - ICOUB - ICRT - - ICRTSA - IDASIT - IDDSIT - IDFSIT diff --git a/swagger/prime_v3.yaml b/swagger/prime_v3.yaml index af57117ebda..9bf156415ce 100644 --- a/swagger/prime_v3.yaml +++ b/swagger/prime_v3.yaml @@ -509,6 +509,63 @@ definitions: - item - crate - description + MTOServiceItemInternationalCrating: + description: >- + Describes a international crating/uncrating service item subtype of a + MTOServiceItem. + allOf: + - $ref: '#/definitions/MTOServiceItem' + - type: object + properties: + reServiceCode: + type: string + description: >- + A unique code for the service item. Indicates if the service is + for crating (ICRT) or uncrating (IUCRT). + enum: + - ICRT + - IUCRT + item: + description: The dimensions of the item being crated. + allOf: + - $ref: '#/definitions/MTOServiceItemDimension' + crate: + description: The dimensions for the crate the item will be shipped in. + allOf: + - $ref: '#/definitions/MTOServiceItemDimension' + description: + type: string + example: Decorated horse head to be crated. + description: A description of the item being crated. + reason: + type: string + example: Storage items need to be picked up + description: > + The contractor's explanation for why an item needed to be crated + or uncrated. Used by the TOO while deciding to approve or reject + the service item. + x-nullable: true + x-omitempty: false + standaloneCrate: + type: boolean + x-nullable: true + externalCrate: + type: boolean + x-nullable: true + market: + type: string + enum: + - CONUS + - OCONUS + example: CONUS + description: >- + To identify whether the service was provided within (CONUS) or + (OCONUS) + required: + - reServiceCode + - item + - crate + - description MTOServiceItemOriginSIT: description: Describes a domestic origin SIT service item. Subtype of a MTOServiceItem. allOf: @@ -654,11 +711,7 @@ definitions: items: $ref: '#/definitions/MTOServiceItem' pickupAddress: - description: The address where the movers should pick up this shipment. - allOf: - - $ref: '#/definitions/Address' - destinationAddress: - description: Where the movers should deliver this shipment. + description: The primary address where the movers should pick up this shipment. allOf: - $ref: '#/definitions/Address' secondaryPickupAddress: @@ -669,12 +722,16 @@ definitions: description: The third address where the movers should pick up this shipment. allOf: - $ref: '#/definitions/Address' + destinationAddress: + description: primary location the movers should deliver this shipment. + allOf: + - $ref: '#/definitions/Address' secondaryDestinationAddress: - description: The second address where the movers should deliver this shipment. + description: second location where the movers should deliver this shipment. allOf: - $ref: '#/definitions/Address' tertiaryDestinationAddress: - description: The third address where the movers should deliver this shipment. + description: third location where the movers should deliver this shipment. allOf: - $ref: '#/definitions/Address' shipmentType: @@ -732,13 +789,13 @@ definitions: - $ref: '#/definitions/Address' secondaryPickupAddress: description: >- - An optional secondary pickup location address near the origin where + An optional secondary Pickup Address address near the origin where additional goods exist. allOf: - $ref: '#/definitions/Address' tertiaryPickupAddress: description: >- - An optional tertiary pickup location address near the origin where + An optional tertiary Pickup Address address near the origin where additional goods exist. allOf: - $ref: '#/definitions/Address' @@ -1004,7 +1061,7 @@ definitions: x-nullable: true secondaryPickupAddress: description: > - An optional secondary pickup location near the origin where additional + An optional secondary Pickup Address near the origin where additional goods exist. allOf: - $ref: '#/definitions/Address' @@ -1014,7 +1071,7 @@ definitions: x-nullable: true tertiaryPickupAddress: description: > - An optional third pickup location near the origin where additional + An optional third Pickup Address near the origin where additional goods exist. allOf: - $ref: '#/definitions/Address' @@ -1191,6 +1248,18 @@ definitions: the customer during onboarding when they enter shipment details. allOf: - $ref: '#/definitions/Address' + secondaryPickupAddress: + description: >- + A second pickup address for this shipment, if the customer entered + one. An optional field. + allOf: + - $ref: '#/definitions/Address' + tertiaryPickupAddress: + description: >- + A third pickup address for this shipment, if the customer entered one. + An optional field. + allOf: + - $ref: '#/definitions/Address' destinationAddress: description: > Where the movers should deliver this shipment. Often provided by the @@ -1205,32 +1274,20 @@ definitions: final destination due to the shipment being diverted or placed in SIT. allOf: - $ref: '#/definitions/Address' - destinationType: - $ref: '#/definitions/DestinationType' - secondaryPickupAddress: - description: >- - A second pickup address for this shipment, if the customer entered - one. An optional field. - allOf: - - $ref: '#/definitions/Address' secondaryDeliveryAddress: description: >- A second delivery address for this shipment, if the customer entered one. An optional field. allOf: - $ref: '#/definitions/Address' - tertiaryPickupAddress: - description: >- - A third pickup address for this shipment, if the customer entered one. - An optional field. - allOf: - - $ref: '#/definitions/Address' tertiaryDeliveryAddress: description: >- A third delivery address for this shipment, if the customer entered one. An optional field. allOf: - $ref: '#/definitions/Address' + destinationType: + $ref: '#/definitions/DestinationType' storageFacility: allOf: - x-nullable: true @@ -1290,7 +1347,7 @@ definitions: UpdateShipmentDestinationAddress: description: >- UpdateShipmentDestinationAddress contains the fields required for the - prime to request an update for the destination address on an MTO Shipment. + prime to request an update for the delivery address on an MTO Shipment. type: object properties: newAddress: @@ -2077,6 +2134,7 @@ definitions: * DDFSIT, DDASIT - MTOServiceItemDestSIT * DOSHUT, DDSHUT - MTOServiceItemShuttle * DCRT, DUCRT - MTOServiceItemDomesticCrating + * ICRT, IUCRT - MTOServiceItemInternationalCrating The documentation will then update with the supported fields. type: string @@ -2086,6 +2144,7 @@ definitions: - MTOServiceItemDestSIT - MTOServiceItemShuttle - MTOServiceItemDomesticCrating + - MTOServiceItemInternationalCrating ServiceRequestDocument: properties: uploads: @@ -2745,6 +2804,15 @@ definitions: format: cents x-nullable: true x-omitempty: false + maxIncentive: + description: >- + The max amount the government will pay the service member to move + their belongings based on the moving date, locations, and shipment + weight. + type: integer + format: cents + x-nullable: true + x-omitempty: false hasRequestedAdvance: description: | Indicates whether an advance has been requested for the PPM shipment. @@ -2965,10 +3033,10 @@ definitions: - APPROVED ShipmentAddressUpdate: description: > - This represents a destination address change request made by the Prime - that is either auto-approved or requires review if the pricing criteria - has changed. If criteria has changed, then it must be approved or rejected - by a TOO. + This represents a delivery address change request made by the Prime that + is either auto-approved or requires review if the pricing criteria has + changed. If criteria has changed, then it must be approved or rejected by + a TOO. type: object properties: id: @@ -3004,14 +3072,14 @@ definitions: oldSitDistanceBetween: description: >- The distance between the original SIT address and the previous/old - destination address of shipment + delivery address of shipment example: 50 minimum: 0 type: integer newSitDistanceBetween: description: >- The distance between the original SIT address and requested new - destination address of shipment + delivery address of shipment example: 88 minimum: 0 type: integer @@ -3465,7 +3533,6 @@ definitions: - ICOLH - ICOUB - ICRT - - ICRTSA - IDASIT - IDDSIT - IDFSIT diff --git a/swagger/support.yaml b/swagger/support.yaml index 74b944d58b6..3746dcae0cc 100644 --- a/swagger/support.yaml +++ b/swagger/support.yaml @@ -1389,6 +1389,7 @@ definitions: * DDFSIT, DDASIT - MTOServiceItemDestSIT * DOSHUT, DDSHUT - MTOServiceItemShuttle * DCRT, DUCRT - MTOServiceItemDomesticCrating + * ICRT, IUCRT - MTOServiceItemInternationalCrating The documentation will then update with the supported fields. type: string @@ -1398,6 +1399,7 @@ definitions: - MTOServiceItemDestSIT - MTOServiceItemShuttle - MTOServiceItemDomesticCrating + - MTOServiceItemInternationalCrating MTOServiceItemOriginSIT: description: Describes a domestic origin SIT service item. Subtype of a MTOServiceItem. allOf: @@ -2105,7 +2107,6 @@ definitions: - ICOLH - ICOUB - ICRT - - ICRTSA - IDASIT - IDDSIT - IDFSIT diff --git a/yarn.lock b/yarn.lock index 4d2683f08bd..55c010e8079 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4583,9 +4583,9 @@ resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== -"@transcom/react-file-viewer@git+https://github.com/transcom/react-file-viewer#v1.2.4": - version "1.2.4" - resolved "git+https://github.com/transcom/react-file-viewer#d4396dc8bf0acdd646e93ea82d23acf3bc0d5b0f" +"@transcom/react-file-viewer@git+https://github.com/transcom/react-file-viewer#v1.2.5": + version "1.2.5" + resolved "git+https://github.com/transcom/react-file-viewer#4d9924ae58a6acdfcfb353235e034ff98fde5458" dependencies: pdfjs-dist "1.8.357" prop-types "^15.5.10" @@ -7187,9 +7187,9 @@ cross-fetch@^3.1.5: node-fetch "2.6.7" cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== dependencies: path-key "^3.1.0" shebang-command "^2.0.0"