diff --git a/.circleci/config.yml b/.circleci/config.yml index 8744b04c2ac..7a4fb382a9e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -945,6 +945,7 @@ commands: FEATURE_FLAG_UNACCOMPANIED_BAGGAGE: 'false' FEATURE_FLAG_ENABLE_ALASKA: 'false' FEATURE_FLAG_BULK_ASSIGNMENT: 'false' + command: | SHARD=$((${CIRCLE_NODE_INDEX}+1)) PLAYWRIGHT_JUNIT_OUTPUT_NAME=playwright-results.xml \ diff --git a/.envrc b/.envrc index e2aacb52680..beb0b08ea1f 100644 --- a/.envrc +++ b/.envrc @@ -100,7 +100,23 @@ export APPLICATION=app # Migration Path export MIGRATION_PATH="file://${MYMOVE_DIR}/migrations/app/schema;file://${MYMOVE_DIR}/migrations/app/secure" -export MIGRATION_MANIFEST="${MYMOVE_DIR}/migrations/app/migrations_manifest.txt" +export MIGRATION_MANIFEST="${MYMOVE_DIR}/migrations/app/migrations_manifest.txt" ##deprecated +export DML_MIGRATION_MANIFEST="${MYMOVE_DIR}/migrations/app/dml_migrations_manifest.txt" + +# DDL Migrations +export DDL_TYPES_MIGRATION_PATH="file://${MYMOVE_DIR}/migrations/app/ddl_migrations/ddl_types" +export DDL_TYPES_MIGRATION_MANIFEST="${MYMOVE_DIR}/migrations/app/ddl_types_manifest.txt" + +export DDL_TABLES_MIGRATION_PATH="file://${MYMOVE_DIR}/migrations/app/ddl_migrations/ddl_tables" +export DDL_TABLES_MIGRATION_MANIFEST="${MYMOVE_DIR}/migrations/app/ddl_tables_manifest.txt" + +export DDL_VIEWS_MIGRATION_PATH="file://${MYMOVE_DIR}/migrations/app/ddl_migrations/ddl_views" +export DDL_VIEWS_MIGRATION_MANIFEST="${MYMOVE_DIR}/migrations/app/ddl_views_manifest.txt" + +export DDL_FUNCTIONS_MIGRATION_PATH="file://${MYMOVE_DIR}/migrations/app/ddl_migrations/ddl_functions" +export DDL_FUNCTIONS_MIGRATION_MANIFEST="${MYMOVE_DIR}/migrations/app/ddl_functions_manifest.txt" + + # Default DB configuration export DB_PASSWORD=mysecretpassword diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f83d084326b..38013700a29 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -220,7 +220,16 @@ stages: export DB_HOST=localhost export DB_PORT=5432 export MIGRATION_MANIFEST='/builds/milmove/mymove/migrations/app/migrations_manifest.txt' + export DML_MIGRATION_MANIFEST='/builds/milmove/mymove/migrations/app/dml_migrations_manifest.txt' + export DDL_TYPES_MIGRATION_MANIFEST='/builds/milmove/mymove/migrations/app/ddl_types_manifest.txt' + export DDL_TABLES_MIGRATION_MANIFEST='/builds/milmove/mymove/migrations/app/ddl_types_manifest.txt' + export DDL_VIEWS_MIGRATION_MANIFEST='/builds/milmove/mymove/migrations/app/ddl_types_manifest.txt' + export DDL_FUNCTIONS_MIGRATION_MANIFEST='/builds/milmove/mymove/migrations/app/ddl_types_manifest.txt' export MIGRATION_PATH='file:///builds/milmove/mymove/migrations/app/schema;file:///builds/milmove/mymove/migrations/app/secure' + export DDL_TYPES_MIGRATION_PATH='file:///builds/milmove/mymove/migrations/app/ddl_migrations/ddl_types' + export DDL_TABLES_MIGRATION_PATH='file:///builds/milmove/mymove/migrations/app/ddl_migrations/ddl_tables' + export DDL_VIEWS_MIGRATION_PATH='file:///builds/milmove/mymove/migrations/app/ddl_migrations/ddl_views' + export DDL_FUNCTIONS_MIGRATION_PATH='file:///builds/milmove/mymove/migrations/app/ddl_migrations/ddl_functions' export EIA_KEY=db2522a43820268a41a802a16ae9fd26 .setup_devseed_env_variables: &setup_devseed_env_variables @@ -789,7 +798,16 @@ server_test: DB_NAME_TEST: test_db DTOD_USE_MOCK: 'true' MIGRATION_MANIFEST: '/builds/milmove/mymove/migrations/app/migrations_manifest.txt' + DML_MIGRATION_MANIFEST: '/builds/milmove/mymove/migrations/app/dml_migrations_manifest.txt' + DDL_TYPES_MIGRATION_MANIFEST: '/builds/milmove/mymove/migrations/app/ddl_types_manifest.txt' + DDL_TABLES_MIGRATION_MANIFEST: '/builds/milmove/mymove/migrations/app/ddl_tables_manifest.txt' + DDL_VIEWS_MIGRATION_MANIFEST: '/builds/milmove/mymove/migrations/app/ddl_views_manifest.txt' + DDL_FUNCTIONS_MIGRATION_MANIFEST: '/builds/milmove/mymove/migrations/app/ddl_functions_manifest.txt' MIGRATION_PATH: 'file:///builds/milmove/mymove/migrations/app/schema;file:///builds/milmove/mymove/migrations/app/secure' + DDL_TYPES_MIGRATION_PATH: 'file:///builds/milmove/mymove/migrations/app/ddl_migrations/ddl_types' + DDL_TABLES_MIGRATION_PATH: 'file:///builds/milmove/mymove/migrations/app/ddl_migrations/ddl_tables' + DDL_VIEWS_MIGRATION_PATH: 'file:///builds/milmove/mymove/migrations/app/ddl_migrations/ddl_views' + DDL_FUNCTIONS_MIGRATION_PATH: 'file:///builds/milmove/mymove/migrations/app/ddl_migrations/ddl_functions' EIA_KEY: db2522a43820268a41a802a16ae9fd26 # dummy key generated with openssl rand -hex 16 ENV: test ENVIRONMENT: test @@ -948,7 +966,16 @@ integration_test_devseed: DB_NAME: dev_db DB_NAME_DEV: dev_db MIGRATION_MANIFEST: '/builds/milmove/mymove/migrations/app/migrations_manifest.txt' + DML_MIGRATION_MANIFEST: '/builds/milmove/mymove/migrations/app/dml_migrations_manifest.txt' + DDL_TYPES_MIGRATION_MANIFEST: '/builds/milmove/mymove/migrations/app/ddl_types_manifest.txt' + DDL_TABLES_MIGRATION_MANIFEST: '/builds/milmove/mymove/migrations/app/ddl_tables_manifest.txt' + DDL_VIEWS_MIGRATION_MANIFEST: '/builds/milmove/mymove/migrations/app/ddl_views_manifest.txt' + DDL_FUNCTIONS_MIGRATION_MANIFEST: '/builds/milmove/mymove/migrations/app/ddl_functions_manifest.txt' MIGRATION_PATH: 'file:///builds/milmove/mymove/migrations/app/schema;file:///builds/milmove/mymove/migrations/app/secure' + DDL_TYPES_MIGRATION_PATH: 'file:///builds/milmove/mymove/migrations/app/ddl_migrations/ddl_types' + DDL_TABLES_MIGRATION_PATH: 'file:///builds/milmove/mymove/migrations/app/ddl_migrations/ddl_tables' + DDL_VIEWS_MIGRATION_PATH: 'file:///builds/milmove/mymove/migrations/app/ddl_migrations/ddl_views' + DDL_FUNCTIONS_MIGRATION_PATH: 'file:///builds/milmove/mymove/migrations/app/ddl_migrations/ddl_functions' EIA_KEY: db2522a43820268a41a802a16ae9fd26 # dummy key generated with openssl rand -hex 16 ENVIRONMENT: development DOD_CA_PACKAGE: /builds/milmove/mymove/config/tls/milmove-cert-bundle.p7b @@ -1023,7 +1050,16 @@ integration_test_mtls: DB_NAME: dev_db DB_NAME_DEV: dev_db MIGRATION_MANIFEST: '/builds/milmove/mymove/migrations/app/migrations_manifest.txt' + DML_MIGRATION_MANIFEST: '/builds/milmove/mymove/migrations/app/dml_migrations_manifest.txt' + DDL_TYPES_MIGRATION_MANIFEST: '/builds/milmove/mymove/migrations/app/ddl_types_manifest.txt' + DDL_TABLES_MIGRATION_MANIFEST: '/builds/milmove/mymove/migrations/app/ddl_tables_manifest.txt' + DDL_VIEWS_MIGRATION_MANIFEST: '/builds/milmove/mymove/migrations/app/ddl_views_manifest.txt' + DDL_FUNCTIONS_MIGRATION_MANIFEST: '/builds/milmove/mymove/migrations/app/ddl_functions_manifest.txt' MIGRATION_PATH: 'file:///builds/milmove/mymove/migrations/app/schema;file:///builds/milmove/mymove/migrations/app/secure' + DDL_TYPES_MIGRATION_PATH: 'file:///builds/milmove/mymove/migrations/app/ddl_migrations/ddl_types' + DDL_TABLES_MIGRATION_PATH: 'file:///builds/milmove/mymove/migrations/app/ddl_migrations/ddl_tables' + DDL_VIEWS_MIGRATION_PATH: 'file:///builds/milmove/mymove/migrations/app/ddl_migrations/ddl_views' + DDL_FUNCTIONS_MIGRATION_PATH: 'file:///builds/milmove/mymove/migrations/app/ddl_migrations/ddl_functions' EIA_KEY: db2522a43820268a41a802a16ae9fd26 # dummy key generated with openssl rand -hex 16 ENVIRONMENT: development DOD_CA_PACKAGE: /builds/milmove/mymove/config/tls/milmove-cert-bundle.p7b @@ -1076,7 +1112,16 @@ integration_test_admin: DB_NAME: dev_db DB_NAME_DEV: dev_db MIGRATION_MANIFEST: '/builds/milmove/mymove/migrations/app/migrations_manifest.txt' + DML_MIGRATION_MANIFEST: '/builds/milmove/mymove/migrations/app/dml_migrations_manifest.txt' + DDL_TYPES_MIGRATION_MANIFEST: '/builds/milmove/mymove/migrations/app/ddl_types_manifest.txt' + DDL_TABLES_MIGRATION_MANIFEST: '/builds/milmove/mymove/migrations/app/ddl_tables_manifest.txt' + DDL_VIEWS_MIGRATION_MANIFEST: '/builds/milmove/mymove/migrations/app/ddl_views_manifest.txt' + DDL_FUNCTIONS_MIGRATION_MANIFEST: '/builds/milmove/mymove/migrations/app/ddl_functions_manifest.txt' MIGRATION_PATH: 'file:///builds/milmove/mymove/migrations/app/schema;file:///builds/milmove/mymove/migrations/app/secure' + DDL_TYPES_MIGRATION_PATH: 'file:///builds/milmove/mymove/migrations/app/ddl_migrations/ddl_types' + DDL_TABLES_MIGRATION_PATH: 'file:///builds/milmove/mymove/migrations/app/ddl_migrations/ddl_tables' + DDL_VIEWS_MIGRATION_PATH: 'file:///builds/milmove/mymove/migrations/app/ddl_migrations/ddl_views' + DDL_FUNCTIONS_MIGRATION_PATH: 'file:///builds/milmove/mymove/migrations/app/ddl_migrations/ddl_functions' EIA_KEY: db2522a43820268a41a802a16ae9fd26 # dummy key generated with openssl rand -hex 16 ENVIRONMENT: development DOD_CA_PACKAGE: /builds/milmove/mymove/config/tls/milmove-cert-bundle.p7b @@ -1134,7 +1179,16 @@ integration_test_my: DB_NAME: dev_db DB_NAME_DEV: dev_db MIGRATION_MANIFEST: '/builds/milmove/mymove/migrations/app/migrations_manifest.txt' + DML_MIGRATION_MANIFEST: '/builds/milmove/mymove/migrations/app/dml_migrations_manifest.txt' + DDL_TYPES_MIGRATION_MANIFEST: '/builds/milmove/mymove/migrations/app/ddl_types_manifest.txt' + DDL_TABLES_MIGRATION_MANIFEST: '/builds/milmove/mymove/migrations/app/ddl_tables_manifest.txt' + DDL_VIEWS_MIGRATION_MANIFEST: '/builds/milmove/mymove/migrations/app/ddl_views_manifest.txt' + DDL_FUNCTIONS_MIGRATION_MANIFEST: '/builds/milmove/mymove/migrations/app/ddl_functions_manifest.txt' MIGRATION_PATH: 'file:///builds/milmove/mymove/migrations/app/schema;file:///builds/milmove/mymove/migrations/app/secure' + DDL_TYPES_MIGRATION_PATH: 'file:///builds/milmove/mymove/migrations/app/ddl_migrations/ddl_types' + DDL_TABLES_MIGRATION_PATH: 'file:///builds/milmove/mymove/migrations/app/ddl_migrations/ddl_tables' + DDL_VIEWS_MIGRATION_PATH: 'file:///builds/milmove/mymove/migrations/app/ddl_migrations/ddl_views' + DDL_FUNCTIONS_MIGRATION_PATH: 'file:///builds/milmove/mymove/migrations/app/ddl_migrations/ddl_functions' EIA_KEY: db2522a43820268a41a802a16ae9fd26 # dummy key generated with openssl rand -hex 16 ENVIRONMENT: development DOD_CA_PACKAGE: /builds/milmove/mymove/config/tls/milmove-cert-bundle.p7b @@ -1193,7 +1247,16 @@ integration_test_office: DB_NAME: dev_db DB_NAME_DEV: dev_db MIGRATION_MANIFEST: '/builds/milmove/mymove/migrations/app/migrations_manifest.txt' + DML_MIGRATION_MANIFEST: '/builds/milmove/mymove/migrations/app/dml_migrations_manifest.txt' + DDL_TYPES_MIGRATION_MANIFEST: '/builds/milmove/mymove/migrations/app/ddl_types_manifest.txt' + DDL_TABLES_MIGRATION_MANIFEST: '/builds/milmove/mymove/migrations/app/ddl_tables_manifest.txt' + DDL_VIEWS_MIGRATION_MANIFEST: '/builds/milmove/mymove/migrations/app/ddl_views_manifest.txt' + DDL_FUNCTIONS_MIGRATION_MANIFEST: '/builds/milmove/mymove/migrations/app/ddl_functions_manifest.txt' MIGRATION_PATH: 'file:///builds/milmove/mymove/migrations/app/schema;file:///builds/milmove/mymove/migrations/app/secure' + DDL_TYPES_MIGRATION_PATH: 'file:///builds/milmove/mymove/migrations/app/ddl_migrations/ddl_types' + DDL_TABLES_MIGRATION_PATH: 'file:///builds/milmove/mymove/migrations/app/ddl_migrations/ddl_tables' + DDL_VIEWS_MIGRATION_PATH: 'file:///builds/milmove/mymove/migrations/app/ddl_migrations/ddl_views' + DDL_FUNCTIONS_MIGRATION_PATH: 'file:///builds/milmove/mymove/migrations/app/ddl_migrations/ddl_functions' EIA_KEY: db2522a43820268a41a802a16ae9fd26 # dummy key generated with openssl rand -hex 16 ENVIRONMENT: development DOD_CA_PACKAGE: /builds/milmove/mymove/config/tls/milmove-cert-bundle.p7b diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a3a309e7285..50e4b31b0ec 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -159,18 +159,10 @@ repos: rev: v1.1.1 hooks: - id: gen-docs - args: ['docs/adr'] + args: ["docs/adr"] - id: markdown-toc - id: hadolint - - repo: local - hooks: - - id: migrations-manifest - name: migrations manifest - entry: scripts/update-migrations-manifest - language: script - pass_filenames: false - - repo: local hooks: - id: scripts-docs diff --git a/Dockerfile b/Dockerfile index 293ea88db39..61bb314fa6e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM harbor.csde.caci.com/docker.io/debian:stable AS build-env +FROM harbor.csde.caci.com/docker.io/library/debian:stable AS build-env COPY config/tls/dod-wcf-root-ca-1.pem /usr/local/share/ca-certificates/dod-wcf-root-ca-1.pem.crt COPY config/tls/dod-wcf-intermediate-ca-1.pem /usr/local/share/ca-certificates/dod-wcf-intermediate-ca-1.pem.crt diff --git a/Dockerfile.dp3 b/Dockerfile.dp3 index c2cafd6bd07..f02187cd763 100644 --- a/Dockerfile.dp3 +++ b/Dockerfile.dp3 @@ -1,4 +1,4 @@ -FROM harbor.csde.caci.com/docker.io/debian:stable AS build-env +FROM harbor.csde.caci.com/docker.io/library/debian:stable AS build-env # hadolint ignore=DL3007 FROM gcr.io/distroless/base-debian12@sha256:74ddbf52d93fafbdd21b399271b0b4aac1babf8fa98cab59e5692e01169a1348 diff --git a/Dockerfile.migrations b/Dockerfile.migrations index 5d4956df394..f6d81bfd325 100644 --- a/Dockerfile.migrations +++ b/Dockerfile.migrations @@ -1,4 +1,4 @@ -FROM harbor.csde.caci.com/docker.io/debian:stable AS build-env +FROM harbor.csde.caci.com/docker.io/library/debian:stable AS build-env COPY config/tls/dod-wcf-root-ca-1.pem /usr/local/share/ca-certificates/dod-wcf-root-ca-1.pem.crt COPY config/tls/dod-wcf-intermediate-ca-1.pem /usr/local/share/ca-certificates/dod-wcf-intermediate-ca-1.pem.crt @@ -20,6 +20,15 @@ COPY bin/rds-ca-2019-root.pem /bin/rds-ca-2019-root.pem COPY bin/milmove /bin/milmove COPY migrations/app/schema /migrate/schema +COPY migrations/app/ddl_migrations/ddl_types /migrate/ddl_migrations/ddl_types +COPY migrations/app/ddl_migrations/ddl_tables /migrate/ddl_migrations/ddl_tables +COPY migrations/app/ddl_migrations/ddl_views /migrate/ddl_migrations/ddl_views +COPY migrations/app/ddl_migrations/ddl_functions /migrate/ddl_migrations/ddl_functions COPY migrations/app/migrations_manifest.txt /migrate/migrations_manifest.txt - -ENTRYPOINT ["/bin/milmove", "migrate", "-p", "file:///migrate/migrations", "-m", "/migrate/migrations_manifest.txt"] \ No newline at end of file +COPY migrations/app/dml_migrations_manifest.txt /migrate/dml_migrations_manifest.txt +COPY migrations/app/ddl_types_manifest.txt /migrate/ddl_types_manifest.txt +COPY migrations/app/ddl_tables_manifest.txt /migrate/ddl_tables_manifest.txt +COPY migrations/app/ddl_views_manifest.txt /migrate/ddl_views_manifest.txt +COPY migrations/app/ddl_functions_manifest.txt /migrate/ddl_functions_manifest.txt +# hadolint ignore=DL3025 +ENTRYPOINT ["/bin/milmove", "migrate", "-p", "file:///migrate/migrations", "-m", "/migrate/migrations_manifest.txt", '-d', '/migrate/dml_migrations_manifest.txt', '-t', '/migrate/ddl_types_manifest.txt', '-T', '/migrate/ddl_tables_manifest.txt', '-V', '/migrate/ddl_views_manifest.txt', '-F', '/migrate/ddl_functions_manifest.txt'] \ No newline at end of file diff --git a/Dockerfile.tasks b/Dockerfile.tasks index d41ee54e808..7546db7c4c8 100644 --- a/Dockerfile.tasks +++ b/Dockerfile.tasks @@ -1,4 +1,4 @@ -FROM harbor.csde.caci.com/docker.io/debian:stable AS build-env +FROM harbor.csde.caci.com/docker.io/library/debian:stable AS build-env COPY config/tls/dod-wcf-root-ca-1.pem /usr/local/share/ca-certificates/dod-wcf-root-ca-1.pem.crt COPY config/tls/dod-wcf-intermediate-ca-1.pem /usr/local/share/ca-certificates/dod-wcf-intermediate-ca-1.pem.crt diff --git a/cmd/milmove/gen_migration.go b/cmd/milmove/gen_migration.go index a3df776d8ce..9fb3f48a279 100644 --- a/cmd/milmove/gen_migration.go +++ b/cmd/milmove/gen_migration.go @@ -69,7 +69,7 @@ func genMigrationFunction(cmd *cobra.Command, args []string) error { } migrationPath := v.GetString(cli.MigrationGenPathFlag) - migrationManifest := v.GetString(cli.MigrationManifestFlag) + migrationManifest := v.GetString(cli.DMLMigrationManifestFlag) migrationVersion := v.GetString(cli.MigrationVersionFlag) migrationName := v.GetString(cli.MigrationNameFlag) migrationType := v.GetString(cli.MigrationTypeFlag) diff --git a/cmd/milmove/migrate.go b/cmd/milmove/migrate.go index 507c747bf00..8e3abb98e78 100644 --- a/cmd/milmove/migrate.go +++ b/cmd/milmove/migrate.go @@ -174,7 +174,6 @@ func migrateFunction(cmd *cobra.Command, args []string) error { // Remove any extra quotes around path trimmedMigrationPaths := strings.Trim(v.GetString(cli.MigrationPathFlag), "\"") migrationPaths := expandPaths(strings.Split(trimmedMigrationPaths, ";")) - logger.Info(fmt.Sprintf("using migration paths %q", migrationPaths)) logger.Info("migration Path from s3") @@ -308,5 +307,136 @@ func migrateFunction(cmd *cobra.Command, args []string) error { return errors.Wrap(errUp, "error running migrations") } + // Begin DDL migrations + ddlTypesManifest := expandPath(v.GetString(cli.DDLTypesMigrationManifestFlag)) + ddlTypesManifestPath := expandPath(v.GetString(cli.DDLTypesMigrationPathFlag)) + + ddlTablesManifest := expandPath(v.GetString(cli.DDLTablesMigrationManifestFlag)) + ddlTablesPath := expandPath(v.GetString(cli.DDLTablesMigrationPathFlag)) + + ddlViewsManifest := expandPath(v.GetString(cli.DDLViewsMigrationManifestFlag)) + ddlViewsPath := expandPath(v.GetString(cli.DDLViewsMigrationPathFlag)) + + ddlFunctionsManifest := expandPath(v.GetString(cli.DDLFunctionsMigrationManifestFlag)) + ddlFunctionsPath := expandPath(v.GetString(cli.DDLFunctionsMigrationPathFlag)) + + ddlObjects := []struct { + name string + manifest string + path string + }{ + {"DDL Types", ddlTypesManifest, ddlTypesManifestPath}, + {"DDL Tables", ddlTablesManifest, ddlTablesPath}, + {"DDL Views", ddlViewsManifest, ddlViewsPath}, + {"DDL Functions", ddlFunctionsManifest, ddlFunctionsPath}, + } + + for _, ddlObj := range ddlObjects { + logger.Info(fmt.Sprintf("=== Processing %s ===", ddlObj.name)) + logger.Info(fmt.Sprintf("Using manifest %q", ddlObj.manifest)) + filenames, errListFiles := fileHelper.ListFiles(ddlObj.path, s3Client) + if errListFiles != nil { + logger.Fatal(fmt.Sprintf("Error listing %s directory %s", ddlObj.name, ddlObj.path), zap.Error(errListFiles)) + } + + ddlMigrationFiles := map[string][]string{ + ddlObj.path: filenames, + } + + manifest, err := os.Open(ddlObj.manifest[len("file://"):]) + if err != nil { + return errors.Wrap(err, fmt.Sprintf("error reading %s manifest", ddlObj.name)) + } + + scanner := bufio.NewScanner(manifest) + for scanner.Scan() { + target := scanner.Text() + if strings.HasPrefix(target, "#") { + continue + } + + uri := "" + for dir, files := range ddlMigrationFiles { + for _, filename := range files { + if target == filename { + uri = fmt.Sprintf("%s/%s", dir, filename) + break + } + } + } + + if len(uri) == 0 { + return errors.Errorf("Error finding %s migration for filename %q", ddlObj.name, target) + } + + m, err := pop.ParseMigrationFilename(target) + if err != nil { + return errors.Wrapf(err, "error parsing %s migration filename %q", ddlObj.name, uri) + } + + b := &migrate.Builder{Match: m, Path: uri} + migration, errCompile := b.Compile(s3Client, wait, logger) + if errCompile != nil { + return errors.Wrap(errCompile, fmt.Sprintf("Error compiling %s migration", ddlObj.name)) + } + + if err := migration.Run(dbConnection); err != nil { + return errors.Wrap(err, fmt.Sprintf("error executing %s migration", ddlObj.name)) + } + + logger.Info(fmt.Sprintf("Successfully executed %s: %s", ddlObj.name, target)) + } + manifest.Close() + } + + // After DDL migrations, process DML migrations + dmlManifest := expandPath(v.GetString(cli.DMLMigrationManifestFlag)) + logger.Info(fmt.Sprintf("using DML migration manifest %q", dmlManifest)) + + // Create a new migrator for DML migrations + dmlMigrator := pop.NewMigrator(dbConnection) + + manifest, err = os.Open(dmlManifest[len("file://"):]) + if err != nil { + return errors.Wrap(err, "error reading DML manifest") + } + + scanner = bufio.NewScanner(manifest) + for scanner.Scan() { + target := scanner.Text() + if strings.HasPrefix(target, "#") { + continue + } + uri := "" + for dir, filenames := range migrationFiles { + for _, filename := range filenames { + if target == filename { + uri = fmt.Sprintf("%s/%s", dir, filename) + break + } + } + } + if len(uri) == 0 { + return errors.Errorf("Error finding DML migration for filename %q", target) + } + m, err := pop.ParseMigrationFilename(target) + if err != nil { + return errors.Wrapf(err, "error parsing DML migration filename %q", uri) + } + b := &migrate.Builder{Match: m, Path: uri} + migration, errCompile := b.Compile(s3Client, wait, logger) + if errCompile != nil { + return errors.Wrap(errCompile, "Error compiling DML migration") + } + + dmlMigrator.UpMigrations.Migrations = append(dmlMigrator.UpMigrations.Migrations, *migration) + } + + // Run DML migrations and track versions + errUp = dmlMigrator.Up() + if errUp != nil { + return errors.Wrap(errUp, "error running DML migrations") + } + return nil } diff --git a/cmd/prime-api-client/prime/create_mto_service_item.go b/cmd/prime-api-client/prime/create_mto_service_item.go index 4147b173fce..9c96f43e82c 100644 --- a/cmd/prime-api-client/prime/create_mto_service_item.go +++ b/cmd/prime-api-client/prime/create_mto_service_item.go @@ -39,6 +39,10 @@ type shuttleParams struct { Body primemessages.MTOServiceItemShuttle `json:"body"` } +type domesticShuttleParams struct { + Body primemessages.MTOServiceItemDomesticShuttle `json:"body"` +} + // InitCreateMTOServiceItemFlags initializes flags. func InitCreateMTOServiceItemFlags(flag *pflag.FlagSet) { flag.String(utils.FilenameFlag, "", "Name of the file being passed in") @@ -146,12 +150,17 @@ func CreateMTOServiceItem(cmd *cobra.Command, args []string) error { var params shuttleParams err = utils.DecodeJSONFileToPayload(filename, utils.ContainsDash(args), ¶ms) serviceItemParams.SetBody(¶ms.Body) + case primemessages.MTOServiceItemModelTypeMTOServiceItemDomesticShuttle: + var params domesticShuttleParams + err = utils.DecodeJSONFileToPayload(filename, utils.ContainsDash(args), ¶ms) + serviceItemParams.SetBody(¶ms.Body) default: err = fmt.Errorf("allowed modelType(): %v", []primemessages.MTOServiceItemModelType{ primemessages.MTOServiceItemModelTypeMTOServiceItemDestSIT, primemessages.MTOServiceItemModelTypeMTOServiceItemOriginSIT, primemessages.MTOServiceItemModelTypeMTOServiceItemDomesticCrating, primemessages.MTOServiceItemModelTypeMTOServiceItemShuttle, + primemessages.MTOServiceItemModelTypeMTOServiceItemDomesticShuttle, }) } // return any decoding errors diff --git a/config/env/demo.app-client-tls.env b/config/env/demo.app-client-tls.env index fb6bebb747e..9990552dff2 100644 --- a/config/env/demo.app-client-tls.env +++ b/config/env/demo.app-client-tls.env @@ -50,4 +50,4 @@ FEATURE_FLAG_QUEUE_MANAGEMENT=false FEATURE_FLAG_DODID_UNIQUE=false FEATURE_FLAG_ENABLE_ALASKA=false FEATURE_FLAG_ENABLE_HAWAII=false -FEATURE_FLAG_BULK_ASSIGNMENT=false \ No newline at end of file +FEATURE_FLAG_BULK_ASSIGNMENT=false diff --git a/config/env/demo.app.env b/config/env/demo.app.env index 68e7b14daec..fae5faf098c 100644 --- a/config/env/demo.app.env +++ b/config/env/demo.app.env @@ -55,4 +55,4 @@ FEATURE_FLAG_QUEUE_MANAGEMENT=false FEATURE_FLAG_DODID_UNIQUE=false FEATURE_FLAG_ENABLE_ALASKA=false FEATURE_FLAG_ENABLE_HAWAII=false -FEATURE_FLAG_BULK_ASSIGNMENT=false \ No newline at end of file +FEATURE_FLAG_BULK_ASSIGNMENT=false diff --git a/config/env/demo.migrations.env b/config/env/demo.migrations.env index 346e85f8d21..b407f1c498c 100644 --- a/config/env/demo.migrations.env +++ b/config/env/demo.migrations.env @@ -6,3 +6,8 @@ DB_SSL_MODE=verify-full DB_SSL_ROOT_CERT=/bin/rds-ca-rsa4096-g1.pem DB_USER=ecs_user MIGRATION_MANIFEST=/migrate/migrations_manifest.txt +DML_MIGRATION_MANIFEST=/migrate/dml_migrations_manifest.txt +DDL_TYPES_MIGRATION_MANIFEST=/migrate/ddl_types_manifest.txt +DDL_TABLES_MIGRATION_MANIFEST=/migrate/ddl_tables_manifest.txt +DDL_VIEWS_MIGRATION_MANIFEST=/migrate/ddl_views_manifest.txt +DDL_FUNCTIONS_MIGRATION_MANIFEST=/migrate/ddl_functions_manifest.txt \ No newline at end of file diff --git a/config/env/exp.migrations.env b/config/env/exp.migrations.env index 346e85f8d21..d215d9e7797 100644 --- a/config/env/exp.migrations.env +++ b/config/env/exp.migrations.env @@ -6,3 +6,8 @@ DB_SSL_MODE=verify-full DB_SSL_ROOT_CERT=/bin/rds-ca-rsa4096-g1.pem DB_USER=ecs_user MIGRATION_MANIFEST=/migrate/migrations_manifest.txt +DML_MIGRATION_MANIFEST=/migrate/dml_migrations_manifest.txt +DDL_TYPES_MIGRATION_MANIFEST=/migrate/ddl_types_manifest.txt +DDL_TABLES_MIGRATION_MANIFEST=/migrate/ddl_tables_manifest.txt +DDL_VIEWS_MIGRATION_MANIFEST=/migrate/ddl_views_manifest.txt +DDL_FUNCTIONS_MIGRATION_MANIFEST=/migrate/ddl_functions_manifest.txt diff --git a/config/env/loadtest.migrations.env b/config/env/loadtest.migrations.env index 346e85f8d21..b407f1c498c 100644 --- a/config/env/loadtest.migrations.env +++ b/config/env/loadtest.migrations.env @@ -6,3 +6,8 @@ DB_SSL_MODE=verify-full DB_SSL_ROOT_CERT=/bin/rds-ca-rsa4096-g1.pem DB_USER=ecs_user MIGRATION_MANIFEST=/migrate/migrations_manifest.txt +DML_MIGRATION_MANIFEST=/migrate/dml_migrations_manifest.txt +DDL_TYPES_MIGRATION_MANIFEST=/migrate/ddl_types_manifest.txt +DDL_TABLES_MIGRATION_MANIFEST=/migrate/ddl_tables_manifest.txt +DDL_VIEWS_MIGRATION_MANIFEST=/migrate/ddl_views_manifest.txt +DDL_FUNCTIONS_MIGRATION_MANIFEST=/migrate/ddl_functions_manifest.txt \ No newline at end of file diff --git a/config/env/prd.migrations.env b/config/env/prd.migrations.env index 346e85f8d21..b407f1c498c 100644 --- a/config/env/prd.migrations.env +++ b/config/env/prd.migrations.env @@ -6,3 +6,8 @@ DB_SSL_MODE=verify-full DB_SSL_ROOT_CERT=/bin/rds-ca-rsa4096-g1.pem DB_USER=ecs_user MIGRATION_MANIFEST=/migrate/migrations_manifest.txt +DML_MIGRATION_MANIFEST=/migrate/dml_migrations_manifest.txt +DDL_TYPES_MIGRATION_MANIFEST=/migrate/ddl_types_manifest.txt +DDL_TABLES_MIGRATION_MANIFEST=/migrate/ddl_tables_manifest.txt +DDL_VIEWS_MIGRATION_MANIFEST=/migrate/ddl_views_manifest.txt +DDL_FUNCTIONS_MIGRATION_MANIFEST=/migrate/ddl_functions_manifest.txt \ No newline at end of file diff --git a/config/env/stg.migrations.env b/config/env/stg.migrations.env index 346e85f8d21..b407f1c498c 100644 --- a/config/env/stg.migrations.env +++ b/config/env/stg.migrations.env @@ -6,3 +6,8 @@ DB_SSL_MODE=verify-full DB_SSL_ROOT_CERT=/bin/rds-ca-rsa4096-g1.pem DB_USER=ecs_user MIGRATION_MANIFEST=/migrate/migrations_manifest.txt +DML_MIGRATION_MANIFEST=/migrate/dml_migrations_manifest.txt +DDL_TYPES_MIGRATION_MANIFEST=/migrate/ddl_types_manifest.txt +DDL_TABLES_MIGRATION_MANIFEST=/migrate/ddl_tables_manifest.txt +DDL_VIEWS_MIGRATION_MANIFEST=/migrate/ddl_views_manifest.txt +DDL_FUNCTIONS_MIGRATION_MANIFEST=/migrate/ddl_functions_manifest.txt \ No newline at end of file diff --git a/migrations/app/ddl_functions_manifest.txt b/migrations/app/ddl_functions_manifest.txt new file mode 100644 index 00000000000..237796e829e --- /dev/null +++ b/migrations/app/ddl_functions_manifest.txt @@ -0,0 +1,3 @@ +# This is the functions(procedures) migrations manifest. +# If a migration is not recorded here, then it will error. +# Naming convention: fn_some_function.up.sql running will create this file. diff --git a/migrations/app/ddl_migrations/ddl_functions/README.md b/migrations/app/ddl_migrations/ddl_functions/README.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/migrations/app/ddl_migrations/ddl_tables/README.md b/migrations/app/ddl_migrations/ddl_tables/README.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/migrations/app/ddl_migrations/ddl_types/README.md b/migrations/app/ddl_migrations/ddl_types/README.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/migrations/app/ddl_migrations/ddl_views/README.md b/migrations/app/ddl_migrations/ddl_views/README.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/migrations/app/ddl_tables_manifest.txt b/migrations/app/ddl_tables_manifest.txt new file mode 100644 index 00000000000..8fd6841c337 --- /dev/null +++ b/migrations/app/ddl_tables_manifest.txt @@ -0,0 +1,3 @@ +# This is the tables migrations manifest. +# If a migration is not recorded here, then it will error. +# Naming convention: tbl_some_table.up.sql running will create this file. diff --git a/migrations/app/ddl_types_manifest.txt b/migrations/app/ddl_types_manifest.txt new file mode 100644 index 00000000000..9229c96f599 --- /dev/null +++ b/migrations/app/ddl_types_manifest.txt @@ -0,0 +1,3 @@ +# This is the types migrations manifest. +# If a migration is not recorded here, then it will error. +# Naming convention: ty_some_type.up.sql running will create this file. diff --git a/migrations/app/ddl_views_manifest.txt b/migrations/app/ddl_views_manifest.txt new file mode 100644 index 00000000000..939945b6618 --- /dev/null +++ b/migrations/app/ddl_views_manifest.txt @@ -0,0 +1,3 @@ +# This is the views migrations manifest. +# If a migration is not recorded here, then it will error. +# Naming convention: vw_some_view.up.sql running will create this file. diff --git a/migrations/app/dml_migrations_manifest.txt b/migrations/app/dml_migrations_manifest.txt new file mode 100644 index 00000000000..570749e1cfa --- /dev/null +++ b/migrations/app/dml_migrations_manifest.txt @@ -0,0 +1,3 @@ +# This is the migrations manifest. +# If a migration is not recorded here, then it will error. +# Naming convention: 202502201325_B-123456_update_some_table.up.sql running will create this file. diff --git a/migrations/app/migrations_manifest.txt b/migrations/app/migrations_manifest.txt index dd9e5df6411..85d12df5c23 100644 --- a/migrations/app/migrations_manifest.txt +++ b/migrations/app/migrations_manifest.txt @@ -1079,6 +1079,7 @@ 20250109194140_create_audit_history_table_for_payment_service_items.up.sql 20250110001339_update_nts_release_enum_name.up.sql 20250110153428_add_shipment_address_updates_to_move_history.up.sql +20250110201339_add_payment_params_for_intl_crating_uncrating.up.sql 20250110214012_homesafeconnect_cert.up.sql 20250113152050_rename_ubp.up.sql 20250113160816_updating_create_accessorial_service_item_proc.up.sql @@ -1089,4 +1090,8 @@ 20250120214107_add_international_ntsr_service_items.up.sql 20250121153007_update_pricing_proc_to_handle_international_shuttle.up.sql 20250121184450_upd_duty_loc_B-22242.up.sql +20250123173216_add_destination_queue_db_func_and_gbloc_view.up.sql +20250204162411_updating_create_accessorial_service_item_proc_for_crating.up.sql 20250206173204_add_hawaii_data.up.sql +20250210175754_B22451_update_dest_queue_to_consider_sit_extensions.up.sql +# nothing should be added below this line this file is archived and only needed for rebuilding db Locally to be run prior to new migrations process to keep the current state diff --git a/migrations/app/schema/20250106202424_update_duty_locs.up.sql b/migrations/app/schema/20250106202424_update_duty_locs.up.sql index d3bc09560e5..2ecfaf6a399 100644 --- a/migrations/app/schema/20250106202424_update_duty_locs.up.sql +++ b/migrations/app/schema/20250106202424_update_duty_locs.up.sql @@ -24,11 +24,11 @@ BEGIN SELECT '8d613f71-b80e-4ad4-95e7-00781b084c7c'::uuid, 'n/a', NULL, 'NAS NORTH ISLAND', 'CA', '39125', now(), now(), NULL, 'SAN DIEGO', false, '791899e6-cd77-46f2-981b-176ecb8d7098'::uuid, '191165db-d30a-414d-862b-54afdfc7aeb9'::uuid WHERE NOT EXISTS (select * from addresses where id = '8d613f71-b80e-4ad4-95e7-00781b084c7c'); - INSERT INTO duty_locations (id,"name",affiliation,address_id,created_at,updated_at,transportation_office_id,provides_services_counseling) + INSERT INTO duty_locations (id,"name",affiliation,address_id,created_at,updated_at,transportation_office_id,provides_services_counseling) SELECT '56255626-bbbe-4834-8324-1c08f011f2f6'::uuid,'NAS N Island, CA 92135',NULL,'3d617fab-bf6f-4f07-8ab5-f7652b8e7f3e'::uuid,now(),now(),null,true WHERE NOT EXISTS (select * from duty_locations where id = '56255626-bbbe-4834-8324-1c08f011f2f6'); - - INSERT INTO duty_locations (id,"name",affiliation,address_id,created_at,updated_at,transportation_office_id,provides_services_counseling) + + INSERT INTO duty_locations (id,"name",affiliation,address_id,created_at,updated_at,transportation_office_id,provides_services_counseling) SELECT '7156098f-13cf-4455-bcd5-eb829d57c714'::uuid,'NAS North Island, CA 92135',NULL,'8d613f71-b80e-4ad4-95e7-00781b084c7c'::uuid,now(),now(),null,true WHERE NOT EXISTS (select * from duty_locations where id = '7156098f-13cf-4455-bcd5-eb829d57c714'); END $$; @@ -42,7 +42,7 @@ BEGIN SELECT 'fb90a7df-6494-4974-a0ce-4bdbcaff80c0'::uuid, 'n/a', NULL, 'CANNON AFB', 'NM', '88101', now(), now(), NULL, 'CURRY', false, '791899e6-cd77-46f2-981b-176ecb8d7098'::uuid, '68393e10-1aed-4a51-85a0-559a0a5b0e3f'::uuid WHERE NOT EXISTS (select * from addresses where id = 'fb90a7df-6494-4974-a0ce-4bdbcaff80c0'); - INSERT INTO duty_locations (id,"name",affiliation,address_id,created_at,updated_at,transportation_office_id,provides_services_counseling) + INSERT INTO duty_locations (id,"name",affiliation,address_id,created_at,updated_at,transportation_office_id,provides_services_counseling) SELECT '98beab3c-f8ce-4e3c-b78e-8db614721621'::uuid, 'Cannon AFB, NM 88101',null, 'fb90a7df-6494-4974-a0ce-4bdbcaff80c0'::uuid,now(),now(),'80796bc4-e494-4b19-bb16-cdcdba187829',true WHERE NOT EXISTS (select * from duty_locations where id = '98beab3c-f8ce-4e3c-b78e-8db614721621'); END $$; diff --git a/migrations/app/schema/20250110201339_add_payment_params_for_intl_crating_uncrating.up.sql b/migrations/app/schema/20250110201339_add_payment_params_for_intl_crating_uncrating.up.sql new file mode 100644 index 00000000000..dedc7e9637d --- /dev/null +++ b/migrations/app/schema/20250110201339_add_payment_params_for_intl_crating_uncrating.up.sql @@ -0,0 +1,28 @@ +-- adding ExternalCrate param key for intl crating +INSERT INTO service_item_param_keys +(id,key,description,type,origin,created_at,updated_at) +VALUES +('7bb4a8eb-7fff-4e02-8809-f2def00af455','ExternalCrate', 'True if this an external crate', 'BOOLEAN', 'PRIME', now(), now()); + + +-- ICRT +INSERT INTO service_params +(id,service_id,service_item_param_key_id,created_at,updated_at,is_optional) +VALUES +('2ee4d131-041f-498e-b921-cc77970341e9', (SELECT id FROM re_services WHERE code='ICRT'), (SELECT id FROM service_item_param_keys WHERE key='ContractCode'), now(), now(), 'false'), +('bd36234a-090e-4c06-a478-8194c3a78f82', (SELECT id FROM re_services WHERE code='ICRT'), (SELECT id FROM service_item_param_keys WHERE key='ContractYearName'), now(), now(), 'false'), +('bdcda078-6007-48d3-9c1a-16a1ae54dc69', (SELECT id FROM re_services WHERE code='ICRT'), (SELECT id FROM service_item_param_keys WHERE key='EscalationCompounded'), now(), now(), 'false'), +('c6f982f5-d603-43e7-94ed-15ae6e703f86', (SELECT id FROM re_services WHERE code='ICRT'), (SELECT id FROM service_item_param_keys WHERE key='PriceRateOrFactor'), now(), now(), 'false'), +('b323a481-3591-4609-84a5-5a1e8a56a51a', (SELECT id FROM re_services WHERE code='ICRT'), (SELECT id FROM service_item_param_keys WHERE key='StandaloneCrate'), now(), now(), 'true'), +('7fb5a389-bfd7-44d5-a8ff-ef784d37a6a1', (SELECT id FROM re_services WHERE code='ICRT'), (SELECT id FROM service_item_param_keys WHERE key='StandaloneCrateCap'), now(), now(), 'true'), +('3ca76951-c612-491f-ac9a-ad73c1129c99', (SELECT id FROM re_services WHERE code='ICRT'), (SELECT id FROM service_item_param_keys WHERE key='UncappedRequestTotal'), now(), now(), 'true'), +('d486a522-3fa3-45b2-9749-827c40b002b0', (SELECT id FROM re_services WHERE code='ICRT'), (SELECT id FROM service_item_param_keys where key='ExternalCrate'), now(), now(), 'true'); + +-- IUCRT +INSERT INTO service_params +(id,service_id,service_item_param_key_id,created_at,updated_at) +VALUES +('b4619dc8-d1ba-4f85-a198-e985ae80e614', (SELECT id FROM re_services WHERE code='IUCRT'), (SELECT id FROM service_item_param_keys WHERE key='ContractCode'), now(), now()), +('d5d7fc34-2b48-4f99-b053-9d118171f202', (SELECT id FROM re_services WHERE code='IUCRT'), (SELECT id FROM service_item_param_keys WHERE key='ContractYearName'), now(), now()), +('2272b490-ffd0-4b24-8ae1-38019dc5c67d', (SELECT id FROM re_services WHERE code='IUCRT'), (SELECT id FROM service_item_param_keys WHERE key='EscalationCompounded'), now(), now()), +('5fd0739b-695a-4d96-9c5a-f048dfaa8f0c', (SELECT id FROM re_services WHERE code='IUCRT'), (SELECT id FROM service_item_param_keys WHERE key='PriceRateOrFactor'), now(), now()); \ No newline at end of file diff --git a/migrations/app/schema/20250121184450_upd_duty_loc_B-22242.up.sql b/migrations/app/schema/20250121184450_upd_duty_loc_B-22242.up.sql index c21c04f6a81..70928fff219 100644 --- a/migrations/app/schema/20250121184450_upd_duty_loc_B-22242.up.sql +++ b/migrations/app/schema/20250121184450_upd_duty_loc_B-22242.up.sql @@ -1,30 +1,30 @@ DO $$ BEGIN - + --remove duty loc Johnston City, TN 37602 IF EXISTS (SELECT 1 FROM duty_locations WHERE id = 'd3a1be10-dcd7-4720-bcbe-7ba76d243687') THEN - - + + update orders set origin_duty_location_id = 'cd0c7325-15bb-45c7-a690-26c56c903ed7' where origin_duty_location_id = 'd3a1be10-dcd7-4720-bcbe-7ba76d243687'; update orders set new_duty_location_id = 'cd0c7325-15bb-45c7-a690-26c56c903ed7' where new_duty_location_id = 'd3a1be10-dcd7-4720-bcbe-7ba76d243687'; - + delete from duty_locations where id = 'd3a1be10-dcd7-4720-bcbe-7ba76d243687'; - + END IF; END $$; DO $$ BEGIN - + --remove duty loc Oceanside, CA 92052 IF EXISTS (SELECT 1 FROM duty_locations WHERE id = '54ca99b7-3c2a-42b0-aa1a-ad071ac580de') THEN - + update orders set origin_duty_location_id = 'a6993e7b-4600-44b9-b288-04ca011143f0' where origin_duty_location_id = '54ca99b7-3c2a-42b0-aa1a-ad071ac580de'; update orders set new_duty_location_id = 'a6993e7b-4600-44b9-b288-04ca011143f0' where new_duty_location_id = '54ca99b7-3c2a-42b0-aa1a-ad071ac580de'; - + delete from duty_locations where id = '54ca99b7-3c2a-42b0-aa1a-ad071ac580de'; - + END IF; END $$; @@ -34,9 +34,9 @@ BEGIN --remove duty loc Albuquerque, NM 87103 IF EXISTS (SELECT 1 FROM duty_locations WHERE id = '2cc57072-19fa-438b-a44b-e349dff11763') THEN - + update orders set new_duty_location_id = '54acfb0e-222b-49eb-b94b-ccb00c6f529c' where new_duty_location_id = '2cc57072-19fa-438b-a44b-e349dff11763'; - + delete from duty_locations where id = '2cc57072-19fa-438b-a44b-e349dff11763'; END IF; @@ -45,45 +45,45 @@ END $$; DO $$ BEGIN - + --remove duty loc August, GA 30917 IF EXISTS (SELECT 1 FROM duty_locations WHERE id = '109ac405-47fb-4e1e-9efb-58290453ac09') THEN - + update orders set origin_duty_location_id = '595363c2-14ee-48e0-b318-e76ab0016453' where origin_duty_location_id = '109ac405-47fb-4e1e-9efb-58290453ac09'; update orders set new_duty_location_id = '595363c2-14ee-48e0-b318-e76ab0016453' where new_duty_location_id = '109ac405-47fb-4e1e-9efb-58290453ac09'; - + delete from duty_locations where id = '109ac405-47fb-4e1e-9efb-58290453ac09'; - + END IF; END $$; DO $$ BEGIN - + --remove duty loc Frankfort, KY 40602 IF EXISTS (SELECT 1 FROM duty_locations WHERE id = 'c7fadaa2-902f-4302-a7cd-108c525b96d4') THEN - + update orders set origin_duty_location_id = '1a973257-cd15-42a9-86be-a14796c014bc' where origin_duty_location_id = 'c7fadaa2-902f-4302-a7cd-108c525b96d4'; update orders set new_duty_location_id = '1a973257-cd15-42a9-86be-a14796c014bc' where new_duty_location_id = 'c7fadaa2-902f-4302-a7cd-108c525b96d4'; - + delete from duty_locations where id = 'c7fadaa2-902f-4302-a7cd-108c525b96d4'; - + END IF; END $$; DO $$ BEGIN - + --remove duty loc Seattle, WA 98111 IF EXISTS (SELECT 1 FROM duty_locations WHERE id = '2fb3e898-d6de-4be7-8576-7c7b10c2a706') THEN - + update orders set origin_duty_location_id = 'e7fdae4f-6be7-4264-99f8-03ee8541499c' where origin_duty_location_id = '2fb3e898-d6de-4be7-8576-7c7b10c2a706'; update orders set new_duty_location_id = 'e7fdae4f-6be7-4264-99f8-03ee8541499c' where new_duty_location_id = '2fb3e898-d6de-4be7-8576-7c7b10c2a706'; - + delete from duty_locations where id = '2fb3e898-d6de-4be7-8576-7c7b10c2a706'; - + END IF; END $$; diff --git a/migrations/app/schema/20250123173216_add_destination_queue_db_func_and_gbloc_view.up.sql b/migrations/app/schema/20250123173216_add_destination_queue_db_func_and_gbloc_view.up.sql new file mode 100644 index 00000000000..5148f0f57d7 --- /dev/null +++ b/migrations/app/schema/20250123173216_add_destination_queue_db_func_and_gbloc_view.up.sql @@ -0,0 +1,502 @@ +--insert USMC gbloc +insert into jppso_regions values ('85b324ae-1a0d-4f7d-971f-ea509dcc73d7', 'USMC','USMC',now(),now()); + +--insert USMC AORs +INSERT INTO gbloc_aors (id,jppso_regions_id,oconus_rate_area_id,department_indicator,shipment_type,is_active,created_at,updated_at) VALUES + ('a8eb35e2-275e-490b-9945-1971b954b958'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'e418be11-2b6f-4714-b026-e293528c50bd'::uuid,'MARINES',NULL,true,now(),now()), + ('ada1b48d-d2e0-481a-8a2e-a265a824647d'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'b41c5636-96dd-4f0d-a18e-eebb17f97ea5'::uuid,'MARINES',NULL,true,now(),now()), + ('588af482-7cd7-42ea-8e05-49dce645ecbe'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'02f32a6a-0338-4545-8437-059862892d2c'::uuid,'MARINES',NULL,true,now(),now()), + ('1ff3bed7-bbf3-432d-9da3-d76264d72913'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'0f128476-d52a-418c-8ba0-c8bfd1c32629'::uuid,'MARINES',NULL,true,now(),now()), + ('0853a854-98b2-4363-a2b1-db14c44dde2f'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'081f84c3-17ec-4ff6-97ce-d5c44a8e4a28'::uuid,'MARINES',NULL,true,now(),now()), + ('21c1ba40-2533-4196-9eb5-6ffddff3a794'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'ce7cdd91-e323-43a6-a604-daaa6bf8be06'::uuid,'MARINES',NULL,true,now(),now()), + ('19ca4073-8736-453e-bb0d-9b13e3b557b0'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'655050d4-711a-43b7-b06d-828ec990c35e'::uuid,'MARINES',NULL,true,now(),now()), + ('a9c2131e-09c3-480d-ba3e-0c144de18aa5'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'455a34af-30a8-4a98-a62b-6f40fd7f047b'::uuid,'MARINES',NULL,true,now(),now()), + ('649e72f8-cac8-483f-a9ed-c9659e37545b'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'1bf1daee-51bb-4c28-aac8-a126ef596486'::uuid,'MARINES',NULL,true,now(),now()), + ('7173f871-f948-4eed-86ae-5e977b16c426'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'15b1d852-0dde-4e1b-b3c6-e08bbc714db3'::uuid,'MARINES',NULL,true,now(),now()); +INSERT INTO gbloc_aors (id,jppso_regions_id,oconus_rate_area_id,department_indicator,shipment_type,is_active,created_at,updated_at) VALUES + ('4f626e2a-cad0-4b4d-baa8-3101275bac23'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'d63de5bb-379b-4f3b-b4c1-554b9746d311'::uuid,'MARINES',NULL,true,now(),now()), + ('de59c604-9119-48fc-bf3f-883de17b7ee6'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'95d142f8-b50a-4108-b5e2-fbd3b7022d3b'::uuid,'MARINES',NULL,true,now(),now()), + ('1cec884c-9e34-42fe-8887-1e8b8fa1cd2e'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'922bb7da-d0e9-431c-a493-95a861dca929'::uuid,'MARINES',NULL,true,now(),now()), + ('0f24b453-e3bc-47ec-86b5-8d8937f65504'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'79aef0ca-9065-4c0e-a134-2889b250cc38'::uuid,'MARINES',NULL,true,now(),now()), + ('2d55560a-7d0a-474f-a0f9-b31c5be8d80e'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'1f181025-e410-41ac-935b-4b5147353f84'::uuid,'MARINES',NULL,true,now(),now()), + ('abcc37f6-9209-4639-8fa1-c8d5f6e4e77d'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'d9ede620-c3f1-4f8d-b60b-eb93664382f7'::uuid,'MARINES',NULL,true,now(),now()), + ('3ac990d2-5df4-4889-a943-2710f818e75a'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'bd27700b-3094-46cc-807b-f18472cbfaf0'::uuid,'MARINES',NULL,true,now(),now()), + ('f3084090-680f-4656-947a-eb2e773e4076'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'2f4a1689-ee65-45fa-9d0c-debd9344f8b9'::uuid,'MARINES',NULL,true,now(),now()), + ('a35ac50b-09a1-46ef-969e-17569717ee10'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'20c52934-2588-4d8f-b3ed-c046d771f4e9'::uuid,'MARINES',NULL,true,now(),now()), + ('107c1479-e6d9-44cb-8342-ac934055074d'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'15fe364b-add5-4ade-bdd9-38fe442616fb'::uuid,'MARINES',NULL,true,now(),now()); +INSERT INTO gbloc_aors (id,jppso_regions_id,oconus_rate_area_id,department_indicator,shipment_type,is_active,created_at,updated_at) VALUES + ('843db089-67cb-463d-a255-1d198f4f7aaa'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'e5a1248e-3870-4a4c-9c9d-2056234eb64a'::uuid,'MARINES',NULL,true,now(),now()), + ('ac820ac9-d380-4c11-9103-172795658e1f'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'efc92db1-4b06-49ab-a295-a98f3f6c9c04'::uuid,'MARINES',NULL,true,now(),now()), + ('dca7ccd2-e438-4f82-8b76-9b61fbf2a593'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'13474ce5-8839-4af7-b975-c3f70ccdab7b'::uuid,'MARINES',NULL,true,now(),now()), + ('def1095b-2a5c-4f7c-8889-9fde15a7ec06'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'d3b05c5e-6faa-4fa9-b725-0904f8a4f3d7'::uuid,'MARINES',NULL,true,now(),now()), + ('d5fbc738-ee31-4c51-8fd4-bbb8db941dc1'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'e5f849fe-5672-4d0d-881c-078e56eea33d'::uuid,'MARINES',NULL,true,now(),now()), + ('ac8c75fe-f637-429d-80fa-913321a65372'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'fca7c60e-fbd9-4885-afdb-ae41c521b560'::uuid,'MARINES',NULL,true,now(),now()), + ('c33eb1f8-b0fc-4670-af5e-5bd423eca6e7'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'0d52a0f0-f39c-4d34-9387-2df45a5810d4'::uuid,'MARINES',NULL,true,now(),now()), + ('188ea995-b8b7-4ce0-97a9-f553f3b72c2f'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'af008e75-81d5-4211-8204-4964c78e70d9'::uuid,'MARINES',NULL,true,now(),now()), + ('4f13c1a6-059b-4aa2-9250-4316e60da2a7'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'dd689e55-af29-4c76-b7e0-c2429ea4833c'::uuid,'MARINES',NULL,true,now(),now()), + ('67e1ff6f-b79b-45cf-b250-3d4ec89bebae'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'60e08330-6869-4586-8198-35f7a4ae9ea7'::uuid,'MARINES',NULL,true,now(),now()); +INSERT INTO gbloc_aors (id,jppso_regions_id,oconus_rate_area_id,department_indicator,shipment_type,is_active,created_at,updated_at) VALUES + ('3a7c679c-0439-4030-8f7e-f6d8e92720d7'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'f7478e79-dbfe-46c8-b337-1e7c46df79dc'::uuid,'MARINES',NULL,true,now(),now()), + ('db91f133-a301-4c87-af9f-6c10584063e6'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'486d7dd4-f51f-4b13-88fc-830b5b15f0a8'::uuid,'MARINES',NULL,true,now(),now()), + ('6b83cc22-36a9-470e-9f43-e65ade5e8a66'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'f66edf62-ba5e-47f8-8264-b6dc9e7dd9ba'::uuid,'MARINES',NULL,true,now(),now()), + ('3df30221-acd3-4428-890c-3a5ef5296cb1'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'0052c55a-d9d7-46b0-9328-58d3911f61b4'::uuid,'MARINES',NULL,true,now(),now()), + ('bae4c9c6-94d8-4ad0-bfc0-7642f5353199'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'16a51fd1-04ed-432a-a8d7-9f17c1de095d'::uuid,'MARINES',NULL,true,now(),now()), + ('1cd873ff-d170-4f48-8f5b-5c5146052d68'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'64b4756f-437d-4aa5-a95e-c396f0cafcbb'::uuid,'MARINES',NULL,true,now(),now()), + ('eb4d870b-8d66-4135-b294-8992e56ad76f'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'f2de1da5-a737-4493-b3f7-7700944a5b62'::uuid,'MARINES',NULL,true,now(),now()), + ('e8c0709c-1f08-4d3e-b848-72ab5b524677'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'7a9c2adb-0562-42c1-a5f1-81710dd19590'::uuid,'MARINES',NULL,true,now(),now()), + ('3c53df36-ee6b-4bc0-a5ca-cedc1dc3c32e'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'e713eed6-35a6-456d-bfb2-0e0646078ab8'::uuid,'MARINES',NULL,true,now(),now()), + ('7cbc7e6c-4da7-47a1-ac93-171b89dba1e0'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'a4594957-0ec6-4edc-85c2-68871f4f6359'::uuid,'MARINES',NULL,true,now(),now()); +INSERT INTO gbloc_aors (id,jppso_regions_id,oconus_rate_area_id,department_indicator,shipment_type,is_active,created_at,updated_at) VALUES + ('d22cb1d2-79b4-45c9-bb6f-8acef54a67b0'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'acd4d2f9-9a49-4a73-be14-3515f19ba0d1'::uuid,'MARINES',NULL,true,now(),now()), + ('5b5e7a5a-d027-44f2-9b4f-f25c1a91bc00'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'c8300ab6-519c-4bef-9b81-25e7334773ca'::uuid,'MARINES',NULL,true,now(),now()), + ('21307477-912d-40ca-a399-6dfebcc322ea'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'67eca2ce-9d88-44ca-bb17-615f3199415c'::uuid,'MARINES',NULL,true,now(),now()), + ('82282efb-7fef-4a6d-a260-a18d2f21fa8d'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'149c3a94-abb1-4af0-aabf-3af019d5e243'::uuid,'MARINES',NULL,true,now(),now()), + ('3bdad313-d414-4842-8e6e-3675e20d78eb'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'87c09b47-058e-47ea-9684-37a8ac1f7120'::uuid,'MARINES',NULL,true,now(),now()), + ('db80b591-045d-4907-9b77-6694fe34e3ed'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'0ba81e85-f175-435a-a7c2-a22d0c44cc7b'::uuid,'MARINES',NULL,true,now(),now()), + ('05970a8a-28aa-454f-a28e-31327a2415dd'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'946256dc-1572-4201-b5ff-464da876f5ff'::uuid,'MARINES',NULL,true,now(),now()), + ('c1b3e0be-0463-4dfc-ab22-016037b41a05'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'5d381518-6beb-422e-8c57-4682b87ff1fe'::uuid,'MARINES',NULL,true,now(),now()), + ('1a92f4b0-4060-4f1b-9b45-b3fc47c5f08d'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'b07fb701-2c9b-4847-a459-76b1f47aa872'::uuid,'MARINES',NULL,true,now(),now()), + ('73794c53-a915-41af-b93d-5ada2e174409'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'b6c51584-f3e8-4c9e-9bdf-f7cac0433319'::uuid,'MARINES',NULL,true,now(),now()); +INSERT INTO gbloc_aors (id,jppso_regions_id,oconus_rate_area_id,department_indicator,shipment_type,is_active,created_at,updated_at) VALUES + ('c03994c2-9e2a-4888-a5c2-c81ee05eba31'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'6097d01d-78ef-40d5-8699-7f8a8e48f4e7'::uuid,'MARINES',NULL,true,now(),now()), + ('ae6a55fd-2171-425a-9716-56d3fb9452e3'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'e48a3602-7fdd-4527-a8a7-f244fb331228'::uuid,'MARINES',NULL,true,now(),now()), + ('f052acf4-7d4a-4061-a2f4-ba19ff17ec4d'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'df47ef29-e902-4bd3-a32d-88d6187399b3'::uuid,'MARINES',NULL,true,now(),now()), + ('10b47428-bef7-4cfe-8564-2ccb654d514a'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'0f4eddf9-b727-4725-848e-3d9329553823'::uuid,'MARINES',NULL,true,now(),now()), + ('4a547501-6aae-4886-be5c-e5a0fad05441'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'d16ebe80-2195-48cf-ba61-b5efb8212754'::uuid,'MARINES',NULL,true,now(),now()), + ('d99cbe3a-84a0-4a2c-8e05-41ce066570ea'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'093268d0-597b-40bd-882a-8f385480bc68'::uuid,'MARINES',NULL,true,now(),now()), + ('ad09f13c-9dd9-468b-ab92-ad3e2d77c905'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'edb8b022-9534-44e7-87ea-e93f5451f057'::uuid,'MARINES',NULL,true,now(),now()), + ('362482c6-7770-49f4-86c9-89e2990e5345'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'4c62bb35-4d2a-4995-9c4f-8c665f2f6d3e'::uuid,'MARINES',NULL,true,now(),now()), + ('45994e2f-1aa4-4aa1-8631-a347a4463bc2'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'58ccfcc5-ded6-4f91-8cb7-8687bc56c4c6'::uuid,'MARINES',NULL,true,now(),now()), + ('b4fd2bf2-05b2-4429-afcb-ebbae512fd2b'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'dbc839c6-7b56-45b0-ab36-a0e77b0d538c'::uuid,'MARINES',NULL,true,now(),now()); +INSERT INTO gbloc_aors (id,jppso_regions_id,oconus_rate_area_id,department_indicator,shipment_type,is_active,created_at,updated_at) VALUES + ('5aa5f596-a9bb-4f37-af0b-6e0cfbfbc711'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'c77e434a-1bf9-44c6-92aa-d377d72d1d44'::uuid,'MARINES',NULL,true,now(),now()), + ('c7bc79d4-525e-496d-93bd-2ea76b32baf4'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'89533190-e613-4c36-99df-7ee3871bb071'::uuid,'MARINES',NULL,true,now(),now()), + ('5bc99ccb-7010-4d2f-99a0-9277eda982ba'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'c86cb50a-99f6-41ed-8e9d-f096bd5cadca'::uuid,'MARINES',NULL,true,now(),now()), + ('dc97dd55-213b-4212-8c1a-aaee7b92fc55'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'b65bd7c4-6251-4682-aa07-77f761c363af'::uuid,'MARINES',NULL,true,now(),now()), + ('3a4daddc-a377-440f-bb5f-d33024374e3e'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'88df8618-798a-4a12-9b14-a7267a6cfd7f'::uuid,'MARINES',NULL,true,now(),now()), + ('e729c30a-0bec-424a-919a-45cbe31998e9'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'15894c4f-eb1a-4b6b-8b3f-e3abc4eeee8d'::uuid,'MARINES',NULL,true,now(),now()), + ('74b6df4f-08ea-48fb-9628-15c3f47f3a27'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'83fd3eb7-6269-46e2-84e1-f6180f40b9e8'::uuid,'MARINES',NULL,true,now(),now()), + ('643112a7-71a9-41d1-97f6-92aee969478a'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'b0f95e07-e3d1-4403-a402-50be9a542cf9'::uuid,'MARINES',NULL,true,now(),now()), + ('73b35029-077b-4d30-8f5a-34c3785f6e96'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'cef18ff2-8f48-47c0-8062-a40f9dec641c'::uuid,'MARINES',NULL,true,now(),now()), + ('9d8862ec-4358-497a-8154-65a83c676261'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'15eefe71-55ae-40b0-8bb5-f1952fcf45c8'::uuid,'MARINES',NULL,true,now(),now()); +INSERT INTO gbloc_aors (id,jppso_regions_id,oconus_rate_area_id,department_indicator,shipment_type,is_active,created_at,updated_at) VALUES + ('8d5591d4-fcc4-44a9-babd-b575672ad6a9'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'d3c5d0a7-0477-44f6-8279-f6b9fa7b3436'::uuid,'MARINES',NULL,true,now(),now()), + ('c1057fc7-1c8b-44e2-a175-131ff0d7429f'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'8b89e7d7-cb36-40bd-ba14-699f1e4b1806'::uuid,'MARINES',NULL,true,now(),now()), + ('ae7949e2-9504-4874-92be-98460e8126da'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'5b833661-8fda-4f2a-9f2b-e1f4c21749a4'::uuid,'MARINES',NULL,true,now(),now()), + ('acf2cacd-0d4f-4de1-afdc-2eb1e72eeb80'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'38bed277-7990-460d-ad27-a69559638f42'::uuid,'MARINES',NULL,true,now(),now()), + ('c66c3d59-def9-4e08-8bee-5b2bacfc5cfd'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'67081e66-7e84-4bac-9c31-aa3e45bbba23'::uuid,'MARINES',NULL,true,now(),now()), + ('1f8cd63a-8dfe-4357-9ac5-2bef71f9d564'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'99e3f894-595f-403a-83f5-f7e035dd1f20'::uuid,'MARINES',NULL,true,now(),now()), + ('3e8030da-8316-4330-abea-35452e39fa61'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'1c0a5988-492f-4bc2-8409-9a143a494248'::uuid,'MARINES',NULL,true,now(),now()), + ('ce3c740a-b0a2-4a55-9abe-c2426fb8d821'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'63e10b54-5348-4362-932e-6613a8db4d42'::uuid,'MARINES',NULL,true,now(),now()), + ('9e913f22-287f-4e42-9544-46d9c6741db7'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'437e9930-0625-4448-938d-ba93a0a98ab5'::uuid,'MARINES',NULL,true,now(),now()), + ('d0ae21fe-07c0-40ee-9aa3-877d6d6a6bb9'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'87d3cb47-1026-4f42-bff6-899ff0fa7660'::uuid,'MARINES',NULL,true,now(),now()); +INSERT INTO gbloc_aors (id,jppso_regions_id,oconus_rate_area_id,department_indicator,shipment_type,is_active,created_at,updated_at) VALUES + ('6f8d7c90-7682-41b7-b5cc-9d4aeb2e22ef'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'0115586e-be8c-4808-b65a-d417fad19238'::uuid,'MARINES',NULL,true,now(),now()), + ('cf7f39c0-8f6b-4598-85db-f465241e66f4'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'6638a77d-4b91-48f6-8241-c87fbdddacd1'::uuid,'MARINES',NULL,true,now(),now()), + ('296a8951-19b1-4868-9937-aef61bb73106'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'97c16bc3-e174-410b-9a09-a0db31420dbc'::uuid,'MARINES',NULL,true,now(),now()), + ('cdcec10e-5300-443f-9aae-7d8ce07142b0'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'3c6c5e35-1281-45fc-83ee-e6d656e155b6'::uuid,'MARINES',NULL,true,now(),now()), + ('e638701f-f8fc-48c0-b2e0-db134b9ece1f'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'6af248be-e5a8-49e9-a9e2-516748279ab5'::uuid,'MARINES',NULL,true,now(),now()), + ('ed4f4905-b1cf-4e57-beac-bc0a2d167c71'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'3580abe3-84da-4b46-af7b-d4379e6cff46'::uuid,'MARINES',NULL,true,now(),now()), + ('2a8b16c9-99f8-43ca-a759-c4884f8f7b24'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'f3bb397e-04e6-4b37-9bf3-b3ebab79a9b6'::uuid,'MARINES',NULL,true,now(),now()), + ('70530105-a4ab-4af3-ba02-9e4cf81237fa'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'f9bfe297-4ee0-4f76-a4bd-64b3a514af5d'::uuid,'MARINES',NULL,true,now(),now()), + ('61e17c04-ed7b-4da9-816a-1b6343086d94'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'70f11a71-667b-422d-ae37-8f25539f7782'::uuid,'MARINES',NULL,true,now(),now()), + ('19d5158f-96a6-48ef-8dd9-b831c582c9c4'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'baf66a9a-3c8a-49e7-83ea-841b9960e184'::uuid,'MARINES',NULL,true,now(),now()); +INSERT INTO gbloc_aors (id,jppso_regions_id,oconus_rate_area_id,department_indicator,shipment_type,is_active,created_at,updated_at) VALUES + ('91043072-657f-4b2a-b5d1-42d8f6a7ba38'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'e96d46b1-3ab7-4b29-b798-ec3b728dd6a1'::uuid,'MARINES',NULL,true,now(),now()), + ('a3dc835e-3989-4476-b560-006745a884bc'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'e4171b5b-d26c-43b3-b41c-68b43bcbb079'::uuid,'MARINES',NULL,true,now(),now()), + ('0cf1dc14-0c9a-4262-9cab-db73c64f6e36'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'a0651bec-1258-4e36-9c76-155247e42c0a'::uuid,'MARINES',NULL,true,now(),now()), + ('e03b53cb-386f-4e1e-ac93-cd1a9260c6b4'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'737a8e63-af19-4902-a4b5-8f80e2268e4b'::uuid,'MARINES',NULL,true,now(),now()), + ('8b3f1142-f6d4-4c2d-9e75-9ab3568742f7'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'870df26e-2c50-4512-aa2e-61094cfbc3e1'::uuid,'MARINES',NULL,true,now(),now()), + ('5f5383b8-f29e-4b9f-8798-99575440c888'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'09dc6547-d346-40c3-93fb-fb7be1fa1b3e'::uuid,'MARINES',NULL,true,now(),now()), + ('1f7a198c-5f62-4f8c-bf91-19d7cdef9bae'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'43d7d081-bb32-4544-84f1-419fe0cb76e1'::uuid,'MARINES',NULL,true,now(),now()), + ('56c17bab-a124-4710-a047-0d67d30a9610'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'d7e57942-9c83-4138-baa0-70e8b5f08598'::uuid,'MARINES',NULL,true,now(),now()), + ('aad1440d-acaf-4ba8-9564-da97ab9ba651'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'c2f06691-5989-41a3-a848-19c9f0fec5df'::uuid,'MARINES',NULL,true,now(),now()), + ('6cc24224-2298-4023-888f-5e624e585171'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'5263a9ed-ff4d-42cc-91d5-dbdefeef54d1'::uuid,'MARINES',NULL,true,now(),now()); +INSERT INTO gbloc_aors (id,jppso_regions_id,oconus_rate_area_id,department_indicator,shipment_type,is_active,created_at,updated_at) VALUES + ('88cd8184-7e3a-48cf-bb72-a9e1c3666cc4'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'54ab3a49-6d78-4922-bac1-94a722b9859a'::uuid,'MARINES',NULL,true,now(),now()), + ('11fdac1c-7ae9-49ea-bee4-90d4582f7c6d'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'3b6fe2e9-9116-4a70-8eef-96205205b0e3'::uuid,'MARINES',NULL,true,now(),now()), + ('83c88ffb-5182-42b3-93f4-635556d8caaf'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'ba0017c8-5d48-4efe-8802-361d2f2bc16d'::uuid,'MARINES',NULL,true,now(),now()), + ('802f7a5f-8ce3-4c40-975a-83cf0ec502fc'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'af65234a-8577-4f6d-a346-7d486e963287'::uuid,'MARINES',NULL,true,now(),now()), + ('5197eed4-f6ae-4f70-b640-b67714b73f87'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'25f62695-ba9b-40c3-a7e4-0c078731123d'::uuid,'MARINES',NULL,true,now(),now()), + ('50d675ab-4064-4edb-8f1d-5739b0318ed9'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'b0422b13-4afe-443e-901f-fe652cde23a4'::uuid,'MARINES',NULL,true,now(),now()), + ('a3733567-2b57-4f12-9390-967e04bc1453'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'ecbc1d89-9cf6-4f52-b453-3ba473a0ff4e'::uuid,'MARINES',NULL,true,now(),now()), + ('6a321494-cdd8-4372-90a1-6a6c67f4e220'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'4e85cb86-e9dd-4c7c-9677-e0a327ac895c'::uuid,'MARINES',NULL,true,now(),now()), + ('99d37d3c-be31-4d3e-9c5e-c9d816a46014'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'7f9dd10d-100e-4252-9786-706349f456ca'::uuid,'MARINES',NULL,true,now(),now()), + ('e9b4c884-ad7f-4c1a-8815-05353834f5c3'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'5451f8d7-60c5-4e22-bbf6-d9af8e6ace54'::uuid,'MARINES',NULL,true,now(),now()); +INSERT INTO gbloc_aors (id,jppso_regions_id,oconus_rate_area_id,department_indicator,shipment_type,is_active,created_at,updated_at) VALUES + ('cfaa046e-6be5-4178-a451-d368317ecb86'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'9e9cba85-7c39-4836-809d-70b54baf392e'::uuid,'MARINES',NULL,true,now(),now()), + ('9b509efb-12d9-42aa-a85a-ffb026866b56'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'f7470914-cf48-43be-b431-a3ca2fe5b290'::uuid,'MARINES',NULL,true,now(),now()), + ('7e54d995-b7bd-457c-bd97-0fd76891402e'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'5a0d3cc1-b866-4bde-b67f-78d565facf3e'::uuid,'MARINES',NULL,true,now(),now()), + ('1357aadd-b420-42c4-8a39-bab8027fa910'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'e629b95a-ec5b-4fc4-897f-e0d1050e1ec6'::uuid,'MARINES',NULL,true,now(),now()), + ('56fbbf0f-e819-41dd-802d-4e677aecd1c9'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'d68a626f-935a-4eb1-ba9b-6829feeff91c'::uuid,'MARINES',NULL,true,now(),now()), + ('bfc2dc53-896f-4f29-92c7-9a7a392b22f2'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'ed496f6e-fd34-48d1-8586-63a1d305c49c'::uuid,'MARINES',NULL,true,now(),now()), + ('d015e8b1-86ea-489f-979d-458ae35ae8d6'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'153b62b2-b1b8-4b9d-afa5-53df4150aba4'::uuid,'MARINES',NULL,true,now(),now()), + ('1f32e712-8b5e-4ae4-b409-c5c92337aed8'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'0020441b-bc0c-436e-be05-b997ca6a853c'::uuid,'MARINES',NULL,true,now(),now()), + ('a8a85e00-e657-41a2-8f32-84bdd9c92ec8'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'a8ae9bb9-e9ac-49b4-81dc-336b8a0dcb54'::uuid,'MARINES',NULL,true,now(),now()), + ('4ad1a57f-0e9e-4405-9a08-0ffa211fc8ce'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'1aa43046-8d6b-4249-88dc-b259d86c0cb8'::uuid,'MARINES',NULL,true,now(),now()); +INSERT INTO gbloc_aors (id,jppso_regions_id,oconus_rate_area_id,department_indicator,shipment_type,is_active,created_at,updated_at) VALUES + ('dd765820-ffa5-4673-a347-fbe3464cd2d8'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'84b50723-95fc-41d1-8115-a734c7e53f66'::uuid,'MARINES',NULL,true,now(),now()), + ('6eff7586-59bd-4638-809b-5cc346646dc9'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'63dc3f78-235e-4b1c-b1db-459d7f5ae25f'::uuid,'MARINES',NULL,true,now(),now()), + ('428b0d7a-3848-4882-a5cc-80d5ae3500d6'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'4bd4c579-c163-4b1b-925a-d852d5a12642'::uuid,'MARINES',NULL,true,now(),now()), + ('9f8311f3-e191-4383-8fb8-2b58cd545dd4'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'ce7f1fc9-5b94-43cb-b398-b31cb6350d6a'::uuid,'MARINES',NULL,true,now(),now()), + ('81e3e6fe-7db3-49ff-b18c-8b078e3d129e'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'4a6a3260-e1ec-4d78-8c07-d89f1405ca16'::uuid,'MARINES',NULL,true,now(),now()), + ('4cf1c40f-60f0-4a47-a351-471720ba0fd3'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'658274b5-720c-4a70-ba9d-249614d85ebc'::uuid,'MARINES',NULL,true,now(),now()), + ('0754978b-d11e-4f2c-b59c-3252d2735b26'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'41f0736c-6d26-4e93-9668-860e9f0e222a'::uuid,'MARINES',NULL,true,now(),now()), + ('c9b8305d-2e16-46a7-9b7c-b3edeb6f8e93'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'c2a8e8c3-dddc-4c0f-a23a-0b4e2c20af0d'::uuid,'MARINES',NULL,true,now(),now()), + ('a8768e6d-1a6d-449a-9f2e-2e198dcd6e00'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'0f1e1c87-0497-4ee2-970d-21ac2d2155db'::uuid,'MARINES',NULL,true,now(),now()), + ('cf292129-d543-4632-9cb3-b074279e42be'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'e40b411d-55f0-4470-83d0-0bbe11fa77dd'::uuid,'MARINES',NULL,true,now(),now()); +INSERT INTO gbloc_aors (id,jppso_regions_id,oconus_rate_area_id,department_indicator,shipment_type,is_active,created_at,updated_at) VALUES + ('3f07cedf-ad90-465e-95a5-ce44a2f088b8'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'2632b4e5-c6cb-4e64-8924-0b7e4b1115ec'::uuid,'MARINES',NULL,true,now(),now()), + ('42a2d93b-9dea-4c63-b0a7-c39364aacf75'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'1336deb9-5c87-409d-8051-4ab9f211eb29'::uuid,'MARINES',NULL,true,now(),now()), + ('64fe0d14-98f7-4f73-9aa3-a19617b2d8c3'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'7a6d3b5b-81a6-4db5-b2ab-ecbfd6bd7941'::uuid,'MARINES',NULL,true,now(),now()), + ('3ab04740-9f13-47f8-a80e-b63ab5b67590'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'91b69254-5976-4839-a31d-972e9958d9cf'::uuid,'MARINES',NULL,true,now(),now()), + ('3be8e483-6bce-4a7d-a3bd-fc1485e79818'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'166f3629-79b9-451a-90a3-c43680929a2f'::uuid,'MARINES',NULL,true,now(),now()), + ('ddf69dcb-345e-47c1-a585-ce24d0854de5'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'4fe05eb4-1b1c-4d4a-a185-0b039ac64835'::uuid,'MARINES',NULL,true,now(),now()), + ('fe9e365f-98a5-4658-a58d-5f8279ff3e5a'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'ca8aecda-4642-45c7-96ed-309c35c4b78f'::uuid,'MARINES',NULL,true,now(),now()), + ('2051a441-f4d0-4b6e-8614-74761de505e6'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'e4bc9404-5466-4a41-993e-09474266afc3'::uuid,'MARINES',NULL,true,now(),now()), + ('6797daed-f002-431c-829b-dab7c1b16ff2'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'d4a51d90-3945-4ad3-9cba-a18d8d7b34d7'::uuid,'MARINES',NULL,true,now(),now()), + ('2398bf11-1986-4914-8e47-6afac423283a'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'a5a60d63-d9a8-4bde-9081-f011784b2d31'::uuid,'MARINES',NULL,true,now(),now()); +INSERT INTO gbloc_aors (id,jppso_regions_id,oconus_rate_area_id,department_indicator,shipment_type,is_active,created_at,updated_at) VALUES + ('f3a6c247-4c4c-4f45-9162-50307d4711f5'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'93842d74-1f3e-46cd-aca9-9f0dafbd20a1'::uuid,'MARINES',NULL,true,now(),now()), + ('0a82b196-7c24-4214-9155-05f0c5c2d7e9'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'1bc0dbda-f0ce-4b76-a551-78dbaaa9e3ec'::uuid,'MARINES',NULL,true,now(),now()), + ('af0a2db3-2e2e-4a78-804b-9a8b89b96e12'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'f1a7ef90-cfa6-4e0c-92c3-f8d70c07ba4d'::uuid,'MARINES',NULL,true,now(),now()), + ('5205a965-d424-469a-9526-17ef551685e6'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'9c5b4c4d-e05c-42ca-bd77-b61f5d8c7afc'::uuid,'MARINES',NULL,true,now(),now()), + ('f263fdb0-933b-42a7-925e-a9852c5804fa'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'27ec2576-78dd-4605-b1a8-0b9ca207fc26'::uuid,'MARINES',NULL,true,now(),now()), + ('b03c7ae3-4d6d-445c-b94a-73af723e5226'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'12396ebc-59e9-430a-8475-759a38af6b7a'::uuid,'MARINES',NULL,true,now(),now()); + + +drop view move_to_gbloc; +CREATE OR REPLACE VIEW move_to_gbloc AS +SELECT move_id, gbloc FROM ( + SELECT DISTINCT ON (sh.move_id) sh.move_id, s.affiliation, + COALESCE(pctg.gbloc, coalesce(pctg_oconus_bos.gbloc, coalesce(pctg_oconus.gbloc, pctg_ppm.gbloc))) AS gbloc + FROM mto_shipments sh + JOIN moves m ON sh.move_id = m.id + JOIN orders o on m.orders_id = o.id + JOIN service_members s on o.service_member_id = s.id + LEFT JOIN ( SELECT a.id AS address_id, + pctg_1.gbloc, pctg_1.postal_code + FROM addresses a + JOIN postal_code_to_gblocs pctg_1 ON a.postal_code::text = pctg_1.postal_code::text) pctg ON pctg.address_id = sh.pickup_address_id + LEFT JOIN ( SELECT ppm.shipment_id, + pctg_1.gbloc + FROM ppm_shipments ppm + JOIN addresses ppm_address ON ppm.pickup_postal_address_id = ppm_address.id + JOIN postal_code_to_gblocs pctg_1 ON ppm_address.postal_code::text = pctg_1.postal_code::text) pctg_ppm ON pctg_ppm.shipment_id = sh.id + LEFT JOIN ( SELECT a.id AS address_id, + cast(jr.code as varchar) AS gbloc, ga.department_indicator + FROM addresses a + JOIN re_oconus_rate_areas ora ON a.us_post_region_cities_id = ora.us_post_region_cities_id + JOIN gbloc_aors ga ON ora.id = ga.oconus_rate_area_id + JOIN jppso_regions jr ON ga.jppso_regions_id = jr.id + ) pctg_oconus_bos ON pctg_oconus_bos.address_id = sh.pickup_address_id + and case when s.affiliation = 'AIR_FORCE' THEN 'AIR_AND_SPACE_FORCE' + when s.affiliation = 'SPACE_FORCE' THEN 'AIR_AND_SPACE_FORCE' + else s.affiliation + end = pctg_oconus_bos.department_indicator + LEFT JOIN ( SELECT a.id AS address_id, + cast(pctg_1.code as varchar) AS gbloc, ga.department_indicator + FROM addresses a + JOIN re_oconus_rate_areas ora ON a.us_post_region_cities_id = ora.us_post_region_cities_id + JOIN gbloc_aors ga ON ora.id = ga.oconus_rate_area_id + JOIN jppso_regions pctg_1 ON ga.jppso_regions_id = pctg_1.id + ) pctg_oconus ON pctg_oconus.address_id = sh.pickup_address_id and pctg_oconus.department_indicator is null + WHERE sh.deleted_at IS NULL + ORDER BY sh.move_id, sh.created_at) as m; + + +-- used for the destination queue +CREATE OR REPLACE VIEW move_to_dest_gbloc +AS +SELECT distinct move_id, gbloc FROM ( + SELECT sh.move_id, s.affiliation, + COALESCE(case when s.affiliation = 'MARINES' then 'USMC' else pctg.gbloc end, coalesce(pctg_oconus_bos.gbloc, coalesce(pctg_oconus.gbloc, pctg_ppm.gbloc))) AS gbloc + FROM mto_shipments sh + JOIN moves m ON sh.move_id = m.id + JOIN orders o on m.orders_id = o.id + JOIN service_members s on o.service_member_id = s.id + LEFT JOIN ( SELECT a.id AS address_id, + pctg_1.gbloc, pctg_1.postal_code + FROM addresses a + JOIN postal_code_to_gblocs pctg_1 ON a.postal_code::text = pctg_1.postal_code::text) pctg ON pctg.address_id = sh.destination_address_id + LEFT JOIN ( SELECT ppm.shipment_id, + pctg_1.gbloc + FROM ppm_shipments ppm + JOIN addresses ppm_address ON ppm.destination_postal_address_id = ppm_address.id + JOIN postal_code_to_gblocs pctg_1 ON ppm_address.postal_code::text = pctg_1.postal_code::text) pctg_ppm ON pctg_ppm.shipment_id = sh.id + LEFT JOIN ( SELECT a.id AS address_id, + cast(jr.code as varchar) AS gbloc, ga.department_indicator + FROM addresses a + JOIN re_oconus_rate_areas ora ON a.us_post_region_cities_id = ora.us_post_region_cities_id + JOIN gbloc_aors ga ON ora.id = ga.oconus_rate_area_id + JOIN jppso_regions jr ON ga.jppso_regions_id = jr.id + ) pctg_oconus_bos ON pctg_oconus_bos.address_id = sh.destination_address_id + and case when s.affiliation = 'AIR_FORCE' THEN 'AIR_AND_SPACE_FORCE' + when s.affiliation = 'SPACE_FORCE' THEN 'AIR_AND_SPACE_FORCE' + else s.affiliation + end = pctg_oconus_bos.department_indicator + LEFT JOIN ( SELECT a.id AS address_id, + cast(pctg_1.code as varchar) AS gbloc, ga.department_indicator + FROM addresses a + JOIN re_oconus_rate_areas ora ON a.us_post_region_cities_id = ora.us_post_region_cities_id + JOIN gbloc_aors ga ON ora.id = ga.oconus_rate_area_id + JOIN jppso_regions pctg_1 ON ga.jppso_regions_id = pctg_1.id + ) pctg_oconus ON pctg_oconus.address_id = sh.destination_address_id and pctg_oconus.department_indicator is null + WHERE sh.deleted_at IS NULL) as m; + +-- database function that returns a list of moves that have destination requests +-- this includes shipment address update requests, destination SIT, & destination shuttle +CREATE OR REPLACE FUNCTION get_destination_queue( + user_gbloc TEXT DEFAULT NULL, + customer_name TEXT DEFAULT NULL, + edipi TEXT DEFAULT NULL, + emplid TEXT DEFAULT NULL, + m_status TEXT[] DEFAULT NULL, + move_code TEXT DEFAULT NULL, + requested_move_date TIMESTAMP DEFAULT NULL, + date_submitted TIMESTAMP DEFAULT NULL, + branch TEXT DEFAULT NULL, + origin_duty_location TEXT DEFAULT NULL, + counseling_office TEXT DEFAULT NULL, + too_assigned_user TEXT DEFAULT NULL, + page INTEGER DEFAULT 1, + per_page INTEGER DEFAULT 20, + sort TEXT DEFAULT NULL, + sort_direction TEXT DEFAULT NULL +) +RETURNS TABLE ( + id UUID, + show BOOLEAN, + locator TEXT, + submitted_at TIMESTAMP WITH TIME ZONE, + orders_id UUID, + status TEXT, + locked_by UUID, + too_assigned_id UUID, + counseling_transportation_office_id UUID, + orders JSONB, + mto_shipments JSONB, + counseling_transportation_office JSONB, + too_assigned JSONB, + total_count BIGINT +) AS $$ +DECLARE + sql_query TEXT; + offset_value INTEGER; + sort_column TEXT; + sort_order TEXT; +BEGIN + IF page < 1 THEN + page := 1; + END IF; + + IF per_page < 1 THEN + per_page := 20; + END IF; + + -- OFFSET for pagination + offset_value := (page - 1) * per_page; + + sql_query := ' + SELECT + moves.id AS id, + moves.show AS show, + moves.locator::TEXT AS locator, + moves.submitted_at::TIMESTAMP WITH TIME ZONE AS submitted_at, + moves.orders_id AS orders_id, + moves.status::TEXT AS status, + moves.locked_by AS locked_by, + moves.too_assigned_id AS too_assigned_id, + moves.counseling_transportation_office_id AS counseling_transportation_office_id, + json_build_object( + ''id'', orders.id, + ''origin_duty_location_gbloc'', orders.gbloc, + ''service_member'', json_build_object( + ''id'', service_members.id, + ''first_name'', service_members.first_name, + ''last_name'', service_members.last_name, + ''edipi'', service_members.edipi, + ''emplid'', service_members.emplid, + ''affiliation'', service_members.affiliation + ), + ''origin_duty_location'', json_build_object( + ''name'', origin_duty_locations.name + ) + )::JSONB AS orders, + COALESCE( + ( + SELECT json_agg( + json_build_object( + ''id'', ms.id, + ''shipment_type'', ms.shipment_type, + ''status'', ms.status, + ''requested_pickup_date'', TO_CHAR(ms.requested_pickup_date, ''YYYY-MM-DD"T00:00:00Z"''), + ''scheduled_pickup_date'', TO_CHAR(ms.scheduled_pickup_date, ''YYYY-MM-DD"T00:00:00Z"''), + ''approved_date'', TO_CHAR(ms.approved_date, ''YYYY-MM-DD"T00:00:00Z"''), + ''prime_estimated_weight'', ms.prime_estimated_weight + ) + ) + FROM ( + SELECT DISTINCT ON (mto_shipments.id) mto_shipments.* + FROM mto_shipments + WHERE mto_shipments.move_id = moves.id + ) AS ms + ), + ''[]'' + )::JSONB AS mto_shipments, + json_build_object( + ''name'', counseling_offices.name + )::JSONB AS counseling_transportation_office, + json_build_object( + ''first_name'', too_user.first_name, + ''last_name'', too_user.last_name + )::JSONB AS too_assigned, + COUNT(*) OVER() AS total_count + FROM moves + JOIN orders ON moves.orders_id = orders.id + JOIN mto_shipments ON mto_shipments.move_id = moves.id + LEFT JOIN ppm_shipments ON ppm_shipments.shipment_id = mto_shipments.id + JOIN mto_service_items ON mto_shipments.id = mto_service_items.mto_shipment_id + JOIN re_services ON mto_service_items.re_service_id = re_services.id + JOIN service_members ON orders.service_member_id = service_members.id + JOIN duty_locations AS new_duty_locations ON orders.new_duty_location_id = new_duty_locations.id + JOIN duty_locations AS origin_duty_locations ON orders.origin_duty_location_id = origin_duty_locations.id + LEFT JOIN office_users AS too_user ON moves.too_assigned_id = too_user.id + LEFT JOIN office_users AS locked_user ON moves.locked_by = locked_user.id + LEFT JOIN transportation_offices AS counseling_offices + ON moves.counseling_transportation_office_id = counseling_offices.id + LEFT JOIN shipment_address_updates ON shipment_address_updates.shipment_id = mto_shipments.id + JOIN move_to_dest_gbloc ON move_to_dest_gbloc.move_id = moves.id + WHERE moves.show = TRUE + '; + + IF user_gbloc IS NOT NULL THEN + sql_query := sql_query || ' AND move_to_dest_gbloc.gbloc = $1 '; + END IF; + + IF customer_name IS NOT NULL THEN + sql_query := sql_query || ' AND ( + service_members.first_name || '' '' || service_members.last_name ILIKE ''%'' || $2 || ''%'' + OR service_members.last_name || '' '' || service_members.first_name ILIKE ''%'' || $2 || ''%'' + )'; + END IF; + + IF edipi IS NOT NULL THEN + sql_query := sql_query || ' AND service_members.edipi ILIKE ''%'' || $3 || ''%'' '; + END IF; + + IF emplid IS NOT NULL THEN + sql_query := sql_query || ' AND service_members.emplid ILIKE ''%'' || $4 || ''%'' '; + END IF; + + IF m_status IS NOT NULL THEN + sql_query := sql_query || ' AND moves.status = ANY($5) '; + END IF; + + IF move_code IS NOT NULL THEN + sql_query := sql_query || ' AND moves.locator ILIKE ''%'' || $6 || ''%'' '; + END IF; + + IF requested_move_date IS NOT NULL THEN + sql_query := sql_query || ' AND ( + mto_shipments.requested_pickup_date::DATE = $7::DATE + OR ppm_shipments.expected_departure_date::DATE = $7::DATE + OR (mto_shipments.shipment_type = ''HHG_OUTOF_NTS'' AND mto_shipments.requested_delivery_date::DATE = $7::DATE) + )'; + END IF; + + IF date_submitted IS NOT NULL THEN + sql_query := sql_query || ' AND moves.submitted_at::DATE = $8::DATE '; + END IF; + + IF branch IS NOT NULL THEN + sql_query := sql_query || ' AND service_members.affiliation ILIKE ''%'' || $9 || ''%'' '; + END IF; + + IF origin_duty_location IS NOT NULL THEN + sql_query := sql_query || ' AND origin_duty_locations.name ILIKE ''%'' || $10 || ''%'' '; + END IF; + + IF counseling_office IS NOT NULL THEN + sql_query := sql_query || ' AND counseling_offices.name ILIKE ''%'' || $11 || ''%'' '; + END IF; + + IF too_assigned_user IS NOT NULL THEN + sql_query := sql_query || ' AND (too_user.first_name || '' '' || too_user.last_name) ILIKE ''%'' || $12 || ''%'' '; + END IF; + + -- add destination queue-specific filters (pending dest address requests, dest SIT & dest shuttle service items) + sql_query := sql_query || ' + AND ( + shipment_address_updates.status = ''REQUESTED'' + OR ( + mto_service_items.status = ''SUBMITTED'' + AND re_services.code IN (''DDFSIT'', ''DDASIT'', ''DDDSIT'', ''DDSHUT'', ''DDSFSC'', ''IDFSIT'', ''IDASIT'', ''IDDSIT'', ''IDSHUT'') + ) + ) + '; + + -- default sorting values if none are provided (move.id) + sort_column := 'id'; + sort_order := 'ASC'; + + IF sort IS NOT NULL THEN + CASE sort + WHEN 'locator' THEN sort_column := 'moves.locator'; + WHEN 'status' THEN sort_column := 'moves.status'; + WHEN 'customerName' THEN sort_column := 'service_members.last_name'; + WHEN 'edipi' THEN sort_column := 'service_members.edipi'; + WHEN 'emplid' THEN sort_column := 'service_members.emplid'; + WHEN 'requestedMoveDate' THEN sort_column := 'COALESCE(mto_shipments.requested_pickup_date, ppm_shipments.expected_departure_date, mto_shipments.requested_delivery_date)'; + WHEN 'appearedInTooAt' THEN sort_column := 'COALESCE(moves.submitted_at, moves.approvals_requested_at)'; + WHEN 'branch' THEN sort_column := 'service_members.affiliation'; + WHEN 'originDutyLocation' THEN sort_column := 'origin_duty_locations.name'; + WHEN 'counselingOffice' THEN sort_column := 'counseling_offices.name'; + WHEN 'assignedTo' THEN sort_column := 'too_user.last_name'; + ELSE + sort_column := 'moves.id'; + END CASE; + END IF; + + IF sort_direction IS NOT NULL THEN + IF LOWER(sort_direction) = 'desc' THEN + sort_order := 'DESC'; + ELSE + sort_order := 'ASC'; + END IF; + END IF; + + sql_query := sql_query || ' + GROUP BY + moves.id, + moves.show, + moves.locator, + moves.submitted_at, + moves.orders_id, + moves.status, + moves.locked_by, + moves.too_assigned_id, + moves.counseling_transportation_office_id, + mto_shipments.requested_pickup_date, + mto_shipments.requested_delivery_date, + ppm_shipments.expected_departure_date, + orders.id, + service_members.id, + service_members.first_name, + service_members.last_name, + service_members.edipi, + service_members.emplid, + service_members.affiliation, + origin_duty_locations.name, + counseling_offices.name, + too_user.first_name, + too_user.last_name'; + sql_query := sql_query || format(' ORDER BY %s %s ', sort_column, sort_order); + sql_query := sql_query || ' LIMIT $13 OFFSET $14 '; + + RETURN QUERY EXECUTE sql_query + USING user_gbloc, customer_name, edipi, emplid, m_status, move_code, requested_move_date, date_submitted, + branch, origin_duty_location, counseling_office, too_assigned_user, per_page, offset_value; + +END; +$$ LANGUAGE plpgsql; diff --git a/migrations/app/schema/20250204162411_updating_create_accessorial_service_item_proc_for_crating.up.sql b/migrations/app/schema/20250204162411_updating_create_accessorial_service_item_proc_for_crating.up.sql new file mode 100644 index 00000000000..135a027eecd --- /dev/null +++ b/migrations/app/schema/20250204162411_updating_create_accessorial_service_item_proc_for_crating.up.sql @@ -0,0 +1,131 @@ +DO ' +BEGIN + IF EXISTS (SELECT 1 FROM pg_type WHERE typname = ''mto_service_item_type'') THEN + IF NOT EXISTS ( + SELECT 1 FROM pg_attribute + WHERE attrelid = ''mto_service_item_type''::regtype + AND attname = ''external_crate'' + ) THEN + ALTER TYPE mto_service_item_type ADD ATTRIBUTE "external_crate" bool; + END IF; + END IF; +END +'; + +-- added external_crate +CREATE OR REPLACE PROCEDURE create_accessorial_service_items_for_shipment ( + IN shipment_id UUID, + IN service_items mto_service_item_type[], + INOUT created_service_item_ids text[] +) AS ' +DECLARE + s_type mto_shipment_type; + m_code market_code_enum; + move_id UUID; + service_item RECORD; + item mto_service_item_type; + new_service_id text; +BEGIN + -- get the shipment type, market code, and move_id based on shipment_id + SELECT ms.shipment_type, ms.market_code, ms.move_id + INTO s_type, m_code, move_id + FROM mto_shipments ms + WHERE ms.id = shipment_id; + + IF s_type IS NULL OR m_code IS NULL THEN + RAISE EXCEPTION ''Shipment with ID % not found or missing required details.'', shipment_id; + END IF; + + -- loop through each provided service item object + FOREACH item IN ARRAY service_items + LOOP + FOR service_item IN + SELECT rsi.id, + rs.id AS re_service_id, + rs.service_location, + rsi.is_auto_approved, + rs.code AS service_code + FROM re_service_items rsi + JOIN re_services rs ON rsi.service_id = rs.id + WHERE rsi.shipment_type = s_type + AND rsi.market_code = m_code + AND rs.code = (item.re_service_code) + AND rsi.is_auto_approved = false + LOOP + BEGIN + -- International crating/uncrating will not have the SI update functionality. + -- Prime should to be able to create multiple crating SI for now. + IF service_item.service_code IN (''ICRT'', ''IUCRT'') OR (NOT does_service_item_exist(service_item.re_service_id, shipment_id)) THEN + + INSERT INTO mto_service_items ( + mto_shipment_id, + move_id, + re_service_id, + service_location, + status, + created_at, + updated_at, + sit_postal_code, + sit_entry_date, + sit_customer_contacted, + reason, + estimated_weight, + actual_weight, + pickup_postal_code, + description, + sit_destination_original_address_id, + sit_destination_final_address_id, + sit_requested_delivery, + sit_departure_date, + sit_origin_hhg_original_address_id, + sit_origin_hhg_actual_address_id, + customer_expense, + customer_expense_reason, + sit_delivery_miles, + standalone_crate, + external_crate + ) + VALUES ( + shipment_id, + move_id, + service_item.re_service_id, + service_item.service_location, + ''SUBMITTED''::service_item_status, + NOW(), + NOW(), + (item).sit_postal_code, + (item).sit_entry_date, + (item).sit_customer_contacted, + (item).reason, + (item).estimated_weight, + (item).actual_weight, + (item).pickup_postal_code, + (item).description, + (item).sit_destination_original_address_id, + (item).sit_destination_final_address_id, + (item).sit_requested_delivery, + (item).sit_departure_date, + (item).sit_origin_hhg_original_address_id, + (item).sit_origin_hhg_actual_address_id, + (item).customer_expense, + (item).customer_expense_reason, + (item).sit_delivery_miles, + (item).standalone_crate, + (item).external_crate + ) RETURNING id INTO new_service_id; + + created_service_item_ids := array_append(created_service_item_ids, new_service_id); + + END IF; + EXCEPTION + WHEN OTHERS THEN + RAISE EXCEPTION ''Error creating accessorial service item with code % for shipment %: %'', + service_item.service_code, shipment_id, SQLERRM; + END; + END LOOP; + END LOOP; + + UPDATE moves SET status = ''APPROVALS REQUESTED'' WHERE id = move_id; +END; +' +LANGUAGE plpgsql; \ No newline at end of file diff --git a/migrations/app/schema/20250210175754_B22451_update_dest_queue_to_consider_sit_extensions.up.sql b/migrations/app/schema/20250210175754_B22451_update_dest_queue_to_consider_sit_extensions.up.sql new file mode 100644 index 00000000000..1c3d1ceced6 --- /dev/null +++ b/migrations/app/schema/20250210175754_B22451_update_dest_queue_to_consider_sit_extensions.up.sql @@ -0,0 +1,280 @@ +-- updating to consider sit extensions in PENDING status +CREATE OR REPLACE FUNCTION get_destination_queue( + user_gbloc TEXT DEFAULT NULL, + customer_name TEXT DEFAULT NULL, + edipi TEXT DEFAULT NULL, + emplid TEXT DEFAULT NULL, + m_status TEXT[] DEFAULT NULL, + move_code TEXT DEFAULT NULL, + requested_move_date TIMESTAMP DEFAULT NULL, + date_submitted TIMESTAMP DEFAULT NULL, + branch TEXT DEFAULT NULL, + origin_duty_location TEXT DEFAULT NULL, + counseling_office TEXT DEFAULT NULL, + too_assigned_user TEXT DEFAULT NULL, + page INTEGER DEFAULT 1, + per_page INTEGER DEFAULT 20, + sort TEXT DEFAULT NULL, + sort_direction TEXT DEFAULT NULL +) +RETURNS TABLE ( + id UUID, + show BOOLEAN, + locator TEXT, + submitted_at TIMESTAMP WITH TIME ZONE, + orders_id UUID, + status TEXT, + locked_by UUID, + too_assigned_id UUID, + counseling_transportation_office_id UUID, + orders JSONB, + mto_shipments JSONB, + counseling_transportation_office JSONB, + too_assigned JSONB, + total_count BIGINT +) AS $$ +DECLARE + sql_query TEXT; + offset_value INTEGER; + sort_column TEXT; + sort_order TEXT; +BEGIN + IF page < 1 THEN + page := 1; + END IF; + + IF per_page < 1 THEN + per_page := 20; + END IF; + + offset_value := (page - 1) * per_page; + + sql_query := ' + SELECT + moves.id AS id, + moves.show AS show, + moves.locator::TEXT AS locator, + moves.submitted_at::TIMESTAMP WITH TIME ZONE AS submitted_at, + moves.orders_id AS orders_id, + moves.status::TEXT AS status, + moves.locked_by AS locked_by, + moves.too_assigned_id AS too_assigned_id, + moves.counseling_transportation_office_id AS counseling_transportation_office_id, + json_build_object( + ''id'', orders.id, + ''origin_duty_location_gbloc'', orders.gbloc, + ''service_member'', json_build_object( + ''id'', service_members.id, + ''first_name'', service_members.first_name, + ''last_name'', service_members.last_name, + ''edipi'', service_members.edipi, + ''emplid'', service_members.emplid, + ''affiliation'', service_members.affiliation + ), + ''origin_duty_location'', json_build_object( + ''name'', origin_duty_locations.name + ) + )::JSONB AS orders, + COALESCE( + ( + SELECT json_agg( + json_build_object( + ''id'', ms.id, + ''shipment_type'', ms.shipment_type, + ''status'', ms.status, + ''requested_pickup_date'', TO_CHAR(ms.requested_pickup_date, ''YYYY-MM-DD"T00:00:00Z"''), + ''scheduled_pickup_date'', TO_CHAR(ms.scheduled_pickup_date, ''YYYY-MM-DD"T00:00:00Z"''), + ''approved_date'', TO_CHAR(ms.approved_date, ''YYYY-MM-DD"T00:00:00Z"''), + ''prime_estimated_weight'', ms.prime_estimated_weight + ) + ) + FROM ( + SELECT DISTINCT ON (mto_shipments.id) mto_shipments.* + FROM mto_shipments + WHERE mto_shipments.move_id = moves.id + ) AS ms + ), + ''[]'' + )::JSONB AS mto_shipments, + json_build_object( + ''name'', counseling_offices.name + )::JSONB AS counseling_transportation_office, + json_build_object( + ''first_name'', too_user.first_name, + ''last_name'', too_user.last_name + )::JSONB AS too_assigned, + COUNT(*) OVER() AS total_count + FROM moves + JOIN orders ON moves.orders_id = orders.id + JOIN mto_shipments ON mto_shipments.move_id = moves.id + LEFT JOIN ppm_shipments ON ppm_shipments.shipment_id = mto_shipments.id + JOIN mto_service_items ON mto_shipments.id = mto_service_items.mto_shipment_id + JOIN re_services ON mto_service_items.re_service_id = re_services.id + JOIN service_members ON orders.service_member_id = service_members.id + JOIN duty_locations AS new_duty_locations ON orders.new_duty_location_id = new_duty_locations.id + JOIN duty_locations AS origin_duty_locations ON orders.origin_duty_location_id = origin_duty_locations.id + LEFT JOIN office_users AS too_user ON moves.too_assigned_id = too_user.id + LEFT JOIN office_users AS locked_user ON moves.locked_by = locked_user.id + LEFT JOIN transportation_offices AS counseling_offices + ON moves.counseling_transportation_office_id = counseling_offices.id + LEFT JOIN shipment_address_updates ON shipment_address_updates.shipment_id = mto_shipments.id + LEFT JOIN sit_extensions ON sit_extensions.mto_shipment_id = mto_shipments.id + JOIN move_to_dest_gbloc ON move_to_dest_gbloc.move_id = moves.id + WHERE moves.show = TRUE + '; + + IF user_gbloc IS NOT NULL THEN + sql_query := sql_query || ' AND move_to_dest_gbloc.gbloc = $1 '; + END IF; + + IF customer_name IS NOT NULL THEN + sql_query := sql_query || ' AND ( + service_members.first_name || '' '' || service_members.last_name ILIKE ''%'' || $2 || ''%'' + OR service_members.last_name || '' '' || service_members.first_name ILIKE ''%'' || $2 || ''%'' + )'; + END IF; + + IF edipi IS NOT NULL THEN + sql_query := sql_query || ' AND service_members.edipi ILIKE ''%'' || $3 || ''%'' '; + END IF; + + IF emplid IS NOT NULL THEN + sql_query := sql_query || ' AND service_members.emplid ILIKE ''%'' || $4 || ''%'' '; + END IF; + + IF m_status IS NOT NULL THEN + sql_query := sql_query || ' AND moves.status = ANY($5) '; + END IF; + + IF move_code IS NOT NULL THEN + sql_query := sql_query || ' AND moves.locator ILIKE ''%'' || $6 || ''%'' '; + END IF; + + IF requested_move_date IS NOT NULL THEN + sql_query := sql_query || ' AND ( + mto_shipments.requested_pickup_date::DATE = $7::DATE + OR ppm_shipments.expected_departure_date::DATE = $7::DATE + OR (mto_shipments.shipment_type = ''HHG_OUTOF_NTS'' AND mto_shipments.requested_delivery_date::DATE = $7::DATE) + )'; + END IF; + + IF date_submitted IS NOT NULL THEN + sql_query := sql_query || ' AND moves.submitted_at::DATE = $8::DATE '; + END IF; + + IF branch IS NOT NULL THEN + sql_query := sql_query || ' AND service_members.affiliation ILIKE ''%'' || $9 || ''%'' '; + END IF; + + IF origin_duty_location IS NOT NULL THEN + sql_query := sql_query || ' AND origin_duty_locations.name ILIKE ''%'' || $10 || ''%'' '; + END IF; + + IF counseling_office IS NOT NULL THEN + sql_query := sql_query || ' AND counseling_offices.name ILIKE ''%'' || $11 || ''%'' '; + END IF; + + IF too_assigned_user IS NOT NULL THEN + sql_query := sql_query || ' AND (too_user.first_name || '' '' || too_user.last_name) ILIKE ''%'' || $12 || ''%'' '; + END IF; + + -- add destination queue-specific filters (pending dest address requests, pending dest SIT extension requests when there are dest SIT service items, submitted dest SIT & dest shuttle service items) + sql_query := sql_query || ' + AND ( + shipment_address_updates.status = ''REQUESTED'' + OR ( + sit_extensions.status = ''PENDING'' + AND re_services.code IN (''DDFSIT'', ''DDASIT'', ''DDDSIT'', ''DDSFSC'', ''DDSHUT'', ''IDFSIT'', ''IDASIT'', ''IDDSIT'', ''IDSFSC'', ''IDSHUT'') + ) + OR ( + mto_service_items.status = ''SUBMITTED'' + AND re_services.code IN (''DDFSIT'', ''DDASIT'', ''DDDSIT'', ''DDSFSC'', ''DDSHUT'', ''IDFSIT'', ''IDASIT'', ''IDDSIT'', ''IDSFSC'', ''IDSHUT'') + ) + ) + '; + + -- default sorting values if none are provided (move.id) + sort_column := 'id'; + sort_order := 'ASC'; + + IF sort IS NOT NULL THEN + CASE sort + WHEN 'locator' THEN sort_column := 'moves.locator'; + WHEN 'status' THEN sort_column := 'moves.status'; + WHEN 'customerName' THEN sort_column := 'service_members.last_name'; + WHEN 'edipi' THEN sort_column := 'service_members.edipi'; + WHEN 'emplid' THEN sort_column := 'service_members.emplid'; + WHEN 'requestedMoveDate' THEN sort_column := 'COALESCE(mto_shipments.requested_pickup_date, ppm_shipments.expected_departure_date, mto_shipments.requested_delivery_date)'; + WHEN 'appearedInTooAt' THEN sort_column := 'COALESCE(moves.submitted_at, moves.approvals_requested_at)'; + WHEN 'branch' THEN sort_column := 'service_members.affiliation'; + WHEN 'originDutyLocation' THEN sort_column := 'origin_duty_locations.name'; + WHEN 'counselingOffice' THEN sort_column := 'counseling_offices.name'; + WHEN 'assignedTo' THEN sort_column := 'too_user.last_name'; + ELSE + sort_column := 'moves.id'; + END CASE; + END IF; + + IF sort_direction IS NOT NULL THEN + IF LOWER(sort_direction) = 'desc' THEN + sort_order := 'DESC'; + ELSE + sort_order := 'ASC'; + END IF; + END IF; + + sql_query := sql_query || ' + GROUP BY + moves.id, + moves.show, + moves.locator, + moves.submitted_at, + moves.orders_id, + moves.status, + moves.locked_by, + moves.too_assigned_id, + moves.counseling_transportation_office_id, + mto_shipments.requested_pickup_date, + mto_shipments.requested_delivery_date, + ppm_shipments.expected_departure_date, + orders.id, + service_members.id, + service_members.first_name, + service_members.last_name, + service_members.edipi, + service_members.emplid, + service_members.affiliation, + origin_duty_locations.name, + counseling_offices.name, + too_user.first_name, + too_user.last_name'; + sql_query := sql_query || format(' ORDER BY %s %s ', sort_column, sort_order); + sql_query := sql_query || ' LIMIT $13 OFFSET $14 '; + + RETURN QUERY EXECUTE sql_query + USING user_gbloc, customer_name, edipi, emplid, m_status, move_code, requested_move_date, date_submitted, + branch, origin_duty_location, counseling_office, too_assigned_user, per_page, offset_value; + +END; +$$ LANGUAGE plpgsql; + +-- fixing some capitalization discrepencies for consistency +UPDATE re_services +SET name = 'International POE fuel surcharge' +WHERE name = 'International POE Fuel Surcharge'; + +UPDATE re_services +SET name = 'International POD fuel surcharge' +WHERE name = 'International POD Fuel Surcharge'; + +UPDATE re_services +SET name = 'International destination SIT fuel surcharge' +WHERE name = 'International Destination SIT Fuel Surcharge'; + +UPDATE re_services +SET name = 'International origin SIT fuel surcharge' +WHERE name = 'International Origin SIT Fuel Surcharge'; + +UPDATE re_services +SET name = 'International shipping & linehaul' +WHERE name = 'International Shipping & Linehaul'; \ No newline at end of file diff --git a/pkg/cli/migration.go b/pkg/cli/migration.go index b540516beb0..bf54e72ab55 100644 --- a/pkg/cli/migration.go +++ b/pkg/cli/migration.go @@ -10,9 +10,22 @@ import ( const ( // MigrationManifestFlag is the migration manifest flag - MigrationManifestFlag string = "migration-manifest" + MigrationManifestFlag string = "migration-manifest" //deprecated + DMLMigrationManifestFlag string = "dml-migration-manifest" // MigrationWaitFlag is the migration wait flag MigrationWaitFlag string = "migration-wait" + + DDLTablesMigrationPathFlag = "ddl-tables-migration-path" + DDLTablesMigrationManifestFlag = "ddl-tables-migration-manifest" + + DDLTypesMigrationPathFlag = "ddl-types-migration-path" + DDLTypesMigrationManifestFlag = "ddl-types-migration-manifest" + + DDLViewsMigrationPathFlag = "ddl-views-migration-path" + DDLViewsMigrationManifestFlag = "ddl-views-migration-manifest" + + DDLFunctionsMigrationPathFlag = "ddl-functions-migration-path" + DDLFunctionsMigrationManifestFlag = "ddl-functions-migration-manifest" ) var ( @@ -22,7 +35,17 @@ var ( // InitMigrationFlags initializes the Migration command line flags func InitMigrationFlags(flag *pflag.FlagSet) { flag.StringP(MigrationManifestFlag, "m", "migrations/app/migrations_manifest.txt", "Path to the manifest") + flag.StringP(DMLMigrationManifestFlag, "d", "migrations/app/dml_migrations_manifest.txt", "Path to the manifest") flag.DurationP(MigrationWaitFlag, "w", time.Millisecond*10, "duration to wait when polling for new data from migration file") + flag.String(DDLTablesMigrationPathFlag, "", "Path to DDL tables migrations directory") + flag.String(DDLTablesMigrationManifestFlag, "", "Path to DDL tables migrations manifest") + flag.String(DDLTypesMigrationPathFlag, "", "Path to DDL types migrations directory") + flag.String(DDLTypesMigrationManifestFlag, "", "Path to DDL types migrations manifest") + flag.String(DDLViewsMigrationPathFlag, "", "Path to DDL views migrations directory") + flag.String(DDLViewsMigrationManifestFlag, "", "Path to DDL views migrations manifest") + flag.String(DDLFunctionsMigrationPathFlag, "", "Path to DDL functions migrations directory") + flag.String(DDLFunctionsMigrationManifestFlag, "", "Path to DDL functions migrations manifest") + } // CheckMigration validates migration command line flags @@ -34,5 +57,20 @@ func CheckMigration(v *viper.Viper) error { if len(MigrationManifestFlag) == 0 { return errMissingMigrationManifest } + if len(DMLMigrationManifestFlag) == 0 { + return errMissingMigrationManifest + } + if len(DDLTypesMigrationManifestFlag) == 0 { + return errMissingMigrationManifest + } + if len(DDLTablesMigrationManifestFlag) == 0 { + return errMissingMigrationManifest + } + if len(DDLViewsMigrationManifestFlag) == 0 { + return errMissingMigrationManifest + } + if len(DDLFunctionsMigrationManifestFlag) == 0 { + return errMissingMigrationManifest + } return nil } diff --git a/pkg/factory/mto_service_item_factory.go b/pkg/factory/mto_service_item_factory.go index 2bc9c236f4d..623b3a2c583 100644 --- a/pkg/factory/mto_service_item_factory.go +++ b/pkg/factory/mto_service_item_factory.go @@ -747,7 +747,7 @@ func BuildFullOriginMTOServiceItems(db *pop.Connection, customs []Customization, // are required params, and entryDate and departureDate can be specificed // optionally. func BuildOriginSITServiceItems(db *pop.Connection, move models.Move, shipment models.MTOShipment, entryDate *time.Time, departureDate *time.Time) models.MTOServiceItems { - postalCode := "90210" + postalCode := shipment.PickupAddress.PostalCode reason := "peak season all trucks in use" defaultEntryDate := time.Now().AddDate(0, 0, -45) defaultApprovedAtDate := time.Now() @@ -759,87 +759,193 @@ func BuildOriginSITServiceItems(db *pop.Connection, move models.Move, shipment m defaultDepartureDate = departureDate } - dofsit := BuildRealMTOServiceItemWithAllDeps(db, models.ReServiceCodeDOFSIT, move, shipment, []Customization{ - { - Model: models.MTOServiceItem{ - Status: models.MTOServiceItemStatusApproved, - ApprovedAt: &defaultApprovedAtDate, - SITEntryDate: &defaultEntryDate, - SITPostalCode: &postalCode, - Reason: &reason, + var firstDaySit, addlDaySit, pickupSit, fuelSurchargeSit models.MTOServiceItem + + if shipment.MarketCode != models.MarketCodeInternational { + firstDaySit = BuildRealMTOServiceItemWithAllDeps(db, models.ReServiceCodeDOFSIT, move, shipment, []Customization{ + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusApproved, + ApprovedAt: &defaultApprovedAtDate, + SITEntryDate: &defaultEntryDate, + SITPostalCode: &postalCode, + Reason: &reason, + }, }, - }, - { - Model: models.Address{}, - Type: &Addresses.SITOriginHHGActualAddress, - }, - { - Model: models.Address{}, - Type: &Addresses.SITOriginHHGOriginalAddress, - }, - }, nil) + { + Model: models.Address{}, + Type: &Addresses.SITOriginHHGActualAddress, + }, + { + Model: models.Address{}, + Type: &Addresses.SITOriginHHGOriginalAddress, + }, + }, nil) - doasit := BuildRealMTOServiceItemWithAllDeps(db, models.ReServiceCodeDOASIT, move, shipment, []Customization{ - { - Model: models.MTOServiceItem{ - Status: models.MTOServiceItemStatusApproved, - ApprovedAt: &defaultApprovedAtDate, - SITEntryDate: &defaultEntryDate, - SITPostalCode: &postalCode, - Reason: &reason, + addlDaySit = BuildRealMTOServiceItemWithAllDeps(db, models.ReServiceCodeDOASIT, move, shipment, []Customization{ + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusApproved, + ApprovedAt: &defaultApprovedAtDate, + SITEntryDate: &defaultEntryDate, + SITPostalCode: &postalCode, + Reason: &reason, + }, }, - }, - { - Model: models.Address{}, - Type: &Addresses.SITOriginHHGActualAddress, - }, - { - Model: models.Address{}, - Type: &Addresses.SITOriginHHGOriginalAddress, - }, - }, nil) + { + Model: models.Address{}, + Type: &Addresses.SITOriginHHGActualAddress, + }, + { + Model: models.Address{}, + Type: &Addresses.SITOriginHHGOriginalAddress, + }, + }, nil) - dopsit := BuildRealMTOServiceItemWithAllDeps(db, models.ReServiceCodeDOPSIT, move, shipment, []Customization{ - { - Model: models.MTOServiceItem{ - Status: models.MTOServiceItemStatusApproved, - ApprovedAt: &defaultApprovedAtDate, - SITEntryDate: &defaultEntryDate, - SITDepartureDate: defaultDepartureDate, - SITPostalCode: &postalCode, - Reason: &reason, + pickupSit = BuildRealMTOServiceItemWithAllDeps(db, models.ReServiceCodeDOPSIT, move, shipment, []Customization{ + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusApproved, + ApprovedAt: &defaultApprovedAtDate, + SITEntryDate: &defaultEntryDate, + SITDepartureDate: defaultDepartureDate, + SITPostalCode: &postalCode, + Reason: &reason, + }, }, - }, - { - Model: models.Address{}, - Type: &Addresses.SITOriginHHGActualAddress, - }, - { - Model: models.Address{}, - Type: &Addresses.SITOriginHHGOriginalAddress, - }, - }, nil) + { + Model: models.Address{}, + Type: &Addresses.SITOriginHHGActualAddress, + }, + { + Model: models.Address{}, + Type: &Addresses.SITOriginHHGOriginalAddress, + }, + }, nil) - dosfsc := BuildRealMTOServiceItemWithAllDeps(db, models.ReServiceCodeDOSFSC, move, shipment, []Customization{ - { - Model: models.MTOServiceItem{ - Status: models.MTOServiceItemStatusApproved, - ApprovedAt: &defaultApprovedAtDate, - SITEntryDate: &defaultEntryDate, - SITPostalCode: &postalCode, - Reason: &reason, + fuelSurchargeSit = BuildRealMTOServiceItemWithAllDeps(db, models.ReServiceCodeDOSFSC, move, shipment, []Customization{ + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusApproved, + ApprovedAt: &defaultApprovedAtDate, + SITEntryDate: &defaultEntryDate, + SITPostalCode: &postalCode, + Reason: &reason, + }, }, - }, - { - Model: models.Address{}, - Type: &Addresses.SITOriginHHGActualAddress, - }, - { - Model: models.Address{}, - Type: &Addresses.SITOriginHHGOriginalAddress, - }, - }, nil) - return []models.MTOServiceItem{dofsit, doasit, dopsit, dosfsc} + { + Model: models.Address{}, + Type: &Addresses.SITOriginHHGActualAddress, + }, + { + Model: models.Address{}, + Type: &Addresses.SITOriginHHGOriginalAddress, + }, + }, nil) + } else { + firstDaySit = BuildMTOServiceItem(db, []Customization{ + { + Model: models.ReService{ + Code: models.ReServiceCodeIOFSIT, + }, + }, + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusApproved, + ApprovedAt: &defaultApprovedAtDate, + SITEntryDate: &defaultEntryDate, + SITPostalCode: &postalCode, + Reason: &reason, + }, + }, + { + Model: move, + LinkOnly: true, + }, + { + Model: shipment, + LinkOnly: true, + }, + }, nil) + + addlDaySit = BuildMTOServiceItem(db, []Customization{ + { + Model: models.ReService{ + Code: models.ReServiceCodeIOASIT, + }, + }, + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusApproved, + ApprovedAt: &defaultApprovedAtDate, + SITEntryDate: &defaultEntryDate, + SITPostalCode: &postalCode, + Reason: &reason, + }, + }, + { + Model: move, + LinkOnly: true, + }, + { + Model: shipment, + LinkOnly: true, + }, + }, nil) + + pickupSit = BuildMTOServiceItem(db, []Customization{ + { + Model: models.ReService{ + Code: models.ReServiceCodeIOPSIT, + }, + }, + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusApproved, + ApprovedAt: &defaultApprovedAtDate, + SITEntryDate: &defaultEntryDate, + SITDepartureDate: defaultDepartureDate, + SITPostalCode: &postalCode, + Reason: &reason, + }, + }, + { + Model: move, + LinkOnly: true, + }, + { + Model: shipment, + LinkOnly: true, + }, + }, nil) + + fuelSurchargeSit = BuildMTOServiceItem(db, []Customization{ + { + Model: models.ReService{ + Code: models.ReServiceCodeIOSFSC, + }, + }, + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusApproved, + ApprovedAt: &defaultApprovedAtDate, + SITEntryDate: &defaultEntryDate, + SITPostalCode: &postalCode, + Reason: &reason, + }, + }, + { + Model: move, + LinkOnly: true, + }, + { + Model: shipment, + LinkOnly: true, + }, + }, nil) + } + + return []models.MTOServiceItem{firstDaySit, addlDaySit, pickupSit, fuelSurchargeSit} } // BuildDestSITServiceItems makes all of the service items that are @@ -847,7 +953,7 @@ func BuildOriginSITServiceItems(db *pop.Connection, move models.Move, shipment m // are required params, and entryDate and departureDate can be specificed // optionally. func BuildDestSITServiceItems(db *pop.Connection, move models.Move, shipment models.MTOShipment, entryDate *time.Time, departureDate *time.Time) models.MTOServiceItems { - postalCode := "90210" + postalCode := shipment.DestinationAddress.PostalCode reason := "peak season all trucks in use" defaultEntryDate := time.Now().AddDate(0, 0, -45) defaultApprovedAtDate := time.Now() @@ -859,71 +965,181 @@ func BuildDestSITServiceItems(db *pop.Connection, move models.Move, shipment mod defaultDepartureDate = departureDate } - ddfsit := BuildRealMTOServiceItemWithAllDeps(db, models.ReServiceCodeDDFSIT, move, shipment, []Customization{ - { - Model: models.MTOServiceItem{ - Status: models.MTOServiceItemStatusApproved, - ApprovedAt: &defaultApprovedAtDate, - SITEntryDate: &defaultEntryDate, - SITPostalCode: &postalCode, - Reason: &reason, + var firstDaySit models.MTOServiceItem + var addlDaySit models.MTOServiceItem + var deliverySit models.MTOServiceItem + var fuelSurchargeSit models.MTOServiceItem + + // handling domestic SIT service item creation vs international + if shipment.MarketCode != models.MarketCodeInternational { + firstDaySit = BuildRealMTOServiceItemWithAllDeps(db, models.ReServiceCodeDDFSIT, move, shipment, []Customization{ + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusApproved, + ApprovedAt: &defaultApprovedAtDate, + SITEntryDate: &defaultEntryDate, + SITPostalCode: &postalCode, + Reason: &reason, + }, }, - }, - }, nil) + }, nil) - ddasit := BuildRealMTOServiceItemWithAllDeps(db, models.ReServiceCodeDDASIT, move, shipment, []Customization{ - { - Model: models.MTOServiceItem{ - Status: models.MTOServiceItemStatusApproved, - ApprovedAt: &defaultApprovedAtDate, - SITEntryDate: &defaultEntryDate, - SITPostalCode: &postalCode, - Reason: &reason, + addlDaySit = BuildRealMTOServiceItemWithAllDeps(db, models.ReServiceCodeDDASIT, move, shipment, []Customization{ + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusApproved, + ApprovedAt: &defaultApprovedAtDate, + SITEntryDate: &defaultEntryDate, + SITPostalCode: &postalCode, + Reason: &reason, + }, }, - }, - }, nil) + }, nil) - dddsit := BuildRealMTOServiceItemWithAllDeps(db, models.ReServiceCodeDDDSIT, move, shipment, []Customization{ - { - Model: models.MTOServiceItem{ - Status: models.MTOServiceItemStatusApproved, - ApprovedAt: &defaultApprovedAtDate, - SITEntryDate: &defaultEntryDate, - SITDepartureDate: defaultDepartureDate, - SITPostalCode: &postalCode, - Reason: &reason, + deliverySit = BuildRealMTOServiceItemWithAllDeps(db, models.ReServiceCodeDDDSIT, move, shipment, []Customization{ + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusApproved, + ApprovedAt: &defaultApprovedAtDate, + SITEntryDate: &defaultEntryDate, + SITDepartureDate: defaultDepartureDate, + SITPostalCode: &postalCode, + Reason: &reason, + }, }, - }, - { - Model: models.Address{}, - Type: &Addresses.SITDestinationFinalAddress, - }, - { - Model: models.Address{}, - Type: &Addresses.SITDestinationOriginalAddress, - }, - }, nil) + { + Model: models.Address{}, + Type: &Addresses.SITDestinationFinalAddress, + }, + { + Model: models.Address{}, + Type: &Addresses.SITDestinationOriginalAddress, + }, + }, nil) - ddsfsc := BuildRealMTOServiceItemWithAllDeps(db, models.ReServiceCodeDDSFSC, move, shipment, []Customization{ - { - Model: models.MTOServiceItem{ - Status: models.MTOServiceItemStatusApproved, - ApprovedAt: &defaultApprovedAtDate, - SITEntryDate: &defaultEntryDate, - SITPostalCode: &postalCode, - Reason: &reason, + fuelSurchargeSit = BuildRealMTOServiceItemWithAllDeps(db, models.ReServiceCodeDDSFSC, move, shipment, []Customization{ + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusApproved, + ApprovedAt: &defaultApprovedAtDate, + SITEntryDate: &defaultEntryDate, + SITPostalCode: &postalCode, + Reason: &reason, + }, }, - }, - { - Model: models.Address{}, - Type: &Addresses.SITDestinationFinalAddress, - }, - { - Model: models.Address{}, - Type: &Addresses.SITDestinationOriginalAddress, - }, - }, nil) - return []models.MTOServiceItem{ddfsit, ddasit, dddsit, ddsfsc} + { + Model: models.Address{}, + Type: &Addresses.SITDestinationFinalAddress, + }, + { + Model: models.Address{}, + Type: &Addresses.SITDestinationOriginalAddress, + }, + }, nil) + } else { + firstDaySit = BuildMTOServiceItem(db, []Customization{ + { + Model: models.ReService{ + Code: models.ReServiceCodeIDFSIT, + }, + }, + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusApproved, + ApprovedAt: &defaultApprovedAtDate, + SITEntryDate: &defaultEntryDate, + SITPostalCode: &postalCode, + Reason: &reason, + }, + }, + { + Model: move, + LinkOnly: true, + }, + { + Model: shipment, + LinkOnly: true, + }, + }, nil) + + addlDaySit = BuildMTOServiceItem(db, []Customization{ + { + Model: models.ReService{ + Code: models.ReServiceCodeIDASIT, + }, + }, + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusApproved, + ApprovedAt: &defaultApprovedAtDate, + SITEntryDate: &defaultEntryDate, + SITPostalCode: &postalCode, + Reason: &reason, + }, + }, + { + Model: move, + LinkOnly: true, + }, + { + Model: shipment, + LinkOnly: true, + }, + }, nil) + + deliverySit = BuildMTOServiceItem(db, []Customization{ + { + Model: models.ReService{ + Code: models.ReServiceCodeIDDSIT, + }, + }, + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusApproved, + ApprovedAt: &defaultApprovedAtDate, + SITEntryDate: &defaultEntryDate, + SITDepartureDate: defaultDepartureDate, + SITPostalCode: &postalCode, + Reason: &reason, + }, + }, + { + Model: move, + LinkOnly: true, + }, + { + Model: shipment, + LinkOnly: true, + }, + }, nil) + + fuelSurchargeSit = BuildMTOServiceItem(db, []Customization{ + { + Model: models.ReService{ + Code: models.ReServiceCodeIDSFSC, + }, + }, + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusApproved, + ApprovedAt: &defaultApprovedAtDate, + SITEntryDate: &defaultEntryDate, + SITPostalCode: &postalCode, + Reason: &reason, + }, + }, + { + Model: move, + LinkOnly: true, + }, + { + Model: shipment, + LinkOnly: true, + }, + }, nil) + } + + return []models.MTOServiceItem{firstDaySit, addlDaySit, deliverySit, fuelSurchargeSit} } func BuildDestSITServiceItemsNoSITDepartureDate(db *pop.Connection, move models.Move, shipment models.MTOShipment, entryDate *time.Time) models.MTOServiceItems { diff --git a/pkg/factory/mto_service_item_factory_test.go b/pkg/factory/mto_service_item_factory_test.go index 6287381e981..fe98c4ddba0 100644 --- a/pkg/factory/mto_service_item_factory_test.go +++ b/pkg/factory/mto_service_item_factory_test.go @@ -346,6 +346,68 @@ func (suite *FactorySuite) TestBuildMTOServiceItem() { suite.Equal(expectedCodes, reServiceCodes) }) + suite.Run("Build SIT service items for international shipment - origin SIT", func() { + + customMove := BuildMove(suite.DB(), nil, nil) + customMTOShipment := BuildMTOShipment(suite.DB(), []Customization{ + { + Model: customMove, + LinkOnly: true, + }, + { + Model: models.MTOShipment{ + MarketCode: models.MarketCodeInternational, + }, + }, + }, nil) + + oneMonthLater := time.Now().AddDate(0, 1, 0) + sitServiceItems := BuildOriginSITServiceItems(suite.DB(), customMove, customMTOShipment, &oneMonthLater, nil) + reServiceCodes := []models.ReServiceCode{} + + for i := range sitServiceItems { + reServiceCodes = append(reServiceCodes, sitServiceItems[i].ReService.Code) + } + expectedCodes := []models.ReServiceCode{ + models.ReServiceCodeIOFSIT, + models.ReServiceCodeIOASIT, + models.ReServiceCodeIOPSIT, + models.ReServiceCodeIOSFSC, + } + suite.Equal(expectedCodes, reServiceCodes) + }) + + suite.Run("Build SIT service items for international shipment - destination SIT", func() { + + customMove := BuildMove(suite.DB(), nil, nil) + customMTOShipment := BuildMTOShipment(suite.DB(), []Customization{ + { + Model: customMove, + LinkOnly: true, + }, + { + Model: models.MTOShipment{ + MarketCode: models.MarketCodeInternational, + }, + }, + }, nil) + + oneMonthLater := time.Now().AddDate(0, 1, 0) + sitServiceItems := BuildDestSITServiceItems(suite.DB(), customMove, customMTOShipment, &oneMonthLater, nil) + reServiceCodes := []models.ReServiceCode{} + + for i := range sitServiceItems { + reServiceCodes = append(reServiceCodes, sitServiceItems[i].ReService.Code) + } + expectedCodes := []models.ReServiceCode{ + models.ReServiceCodeIDFSIT, + models.ReServiceCodeIDASIT, + models.ReServiceCodeIDDSIT, + models.ReServiceCodeIDSFSC, + } + suite.Equal(expectedCodes, reServiceCodes) + }) + suite.Run("Port Locations not populated by default", func() { serviceItem := BuildMTOServiceItem(suite.DB(), nil, nil) diff --git a/pkg/gen/ghcapi/configure_mymove.go b/pkg/gen/ghcapi/configure_mymove.go index 9a7272866f4..432343d4dab 100644 --- a/pkg/gen/ghcapi/configure_mymove.go +++ b/pkg/gen/ghcapi/configure_mymove.go @@ -251,6 +251,11 @@ func configureAPI(api *ghcoperations.MymoveAPI) http.Handler { return middleware.NotImplemented("operation customer_support_remarks.GetCustomerSupportRemarksForMove has not yet been implemented") }) } + if api.QueuesGetDestinationRequestsQueueHandler == nil { + api.QueuesGetDestinationRequestsQueueHandler = queues.GetDestinationRequestsQueueHandlerFunc(func(params queues.GetDestinationRequestsQueueParams) middleware.Responder { + return middleware.NotImplemented("operation queues.GetDestinationRequestsQueue has not yet been implemented") + }) + } if api.GhcDocumentsGetDocumentHandler == nil { api.GhcDocumentsGetDocumentHandler = ghc_documents.GetDocumentHandlerFunc(func(params ghc_documents.GetDocumentParams) middleware.Responder { return middleware.NotImplemented("operation ghc_documents.GetDocument has not yet been implemented") diff --git a/pkg/gen/ghcapi/embedded_spec.go b/pkg/gen/ghcapi/embedded_spec.go index e1cd5992c4c..3d41db0769c 100644 --- a/pkg/gen/ghcapi/embedded_spec.go +++ b/pkg/gen/ghcapi/embedded_spec.go @@ -4682,6 +4682,156 @@ func init() { } } }, + "/queues/destination-requests": { + "get": { + "description": "A TOO will view this queue when they have destination requests tied to their GBLOC. This includes unapproved destination SIT service items, destination shuttle service items and destination address requests that are not yet approved by the TOO.\n", + "produces": [ + "application/json" + ], + "tags": [ + "queues" + ], + "summary": "Gets queued list of all customer moves by GBLOC that have both CONUS \u0026 OCONUS destination requests (destination SIT, destination shuttle, address requests)", + "operationId": "getDestinationRequestsQueue", + "parameters": [ + { + "type": "integer", + "description": "requested page of results", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "results per page", + "name": "perPage", + "in": "query" + }, + { + "enum": [ + "customerName", + "edipi", + "emplid", + "branch", + "locator", + "status", + "originDutyLocation", + "destinationDutyLocation", + "requestedMoveDate", + "appearedInTooAt", + "assignedTo", + "counselingOffice" + ], + "type": "string", + "description": "field that results should be sorted by", + "name": "sort", + "in": "query" + }, + { + "enum": [ + "asc", + "desc" + ], + "type": "string", + "description": "direction of sort order if applied", + "name": "order", + "in": "query" + }, + { + "type": "string", + "name": "branch", + "in": "query" + }, + { + "type": "string", + "name": "locator", + "in": "query" + }, + { + "type": "string", + "name": "customerName", + "in": "query" + }, + { + "type": "string", + "name": "edipi", + "in": "query" + }, + { + "type": "string", + "name": "emplid", + "in": "query" + }, + { + "uniqueItems": true, + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "multi", + "name": "originDutyLocation", + "in": "query" + }, + { + "type": "string", + "name": "destinationDutyLocation", + "in": "query" + }, + { + "type": "string", + "format": "date-time", + "name": "appearedInTooAt", + "in": "query" + }, + { + "type": "string", + "description": "filters the requested pickup date of a shipment on the move", + "name": "requestedMoveDate", + "in": "query" + }, + { + "uniqueItems": true, + "type": "array", + "items": { + "enum": [ + "SUBMITTED", + "SERVICE COUNSELING COMPLETED", + "APPROVALS REQUESTED" + ], + "type": "string" + }, + "description": "Filtering for the status.", + "name": "status", + "in": "query" + }, + { + "type": "string", + "description": "Used to illustrate which user is assigned to this move.\n", + "name": "assignedTo", + "in": "query" + }, + { + "type": "string", + "description": "filters using a counselingOffice name of the move", + "name": "counselingOffice", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Successfully returned all moves matching the criteria", + "schema": { + "$ref": "#/definitions/QueueMovesResult" + } + }, + "403": { + "$ref": "#/responses/PermissionDenied" + }, + "500": { + "$ref": "#/responses/ServerError" + } + } + } + }, "/queues/moves": { "get": { "description": "An office TOO user will be assigned a transportation office that will determine which moves are displayed in their queue based on the origin duty location. GHC moves will show up here onced they have reached the submitted status sent by the customer and have move task orders, shipments, and service items to approve.\n", @@ -21632,6 +21782,162 @@ func init() { } } }, + "/queues/destination-requests": { + "get": { + "description": "A TOO will view this queue when they have destination requests tied to their GBLOC. This includes unapproved destination SIT service items, destination shuttle service items and destination address requests that are not yet approved by the TOO.\n", + "produces": [ + "application/json" + ], + "tags": [ + "queues" + ], + "summary": "Gets queued list of all customer moves by GBLOC that have both CONUS \u0026 OCONUS destination requests (destination SIT, destination shuttle, address requests)", + "operationId": "getDestinationRequestsQueue", + "parameters": [ + { + "type": "integer", + "description": "requested page of results", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "results per page", + "name": "perPage", + "in": "query" + }, + { + "enum": [ + "customerName", + "edipi", + "emplid", + "branch", + "locator", + "status", + "originDutyLocation", + "destinationDutyLocation", + "requestedMoveDate", + "appearedInTooAt", + "assignedTo", + "counselingOffice" + ], + "type": "string", + "description": "field that results should be sorted by", + "name": "sort", + "in": "query" + }, + { + "enum": [ + "asc", + "desc" + ], + "type": "string", + "description": "direction of sort order if applied", + "name": "order", + "in": "query" + }, + { + "type": "string", + "name": "branch", + "in": "query" + }, + { + "type": "string", + "name": "locator", + "in": "query" + }, + { + "type": "string", + "name": "customerName", + "in": "query" + }, + { + "type": "string", + "name": "edipi", + "in": "query" + }, + { + "type": "string", + "name": "emplid", + "in": "query" + }, + { + "uniqueItems": true, + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "multi", + "name": "originDutyLocation", + "in": "query" + }, + { + "type": "string", + "name": "destinationDutyLocation", + "in": "query" + }, + { + "type": "string", + "format": "date-time", + "name": "appearedInTooAt", + "in": "query" + }, + { + "type": "string", + "description": "filters the requested pickup date of a shipment on the move", + "name": "requestedMoveDate", + "in": "query" + }, + { + "uniqueItems": true, + "type": "array", + "items": { + "enum": [ + "SUBMITTED", + "SERVICE COUNSELING COMPLETED", + "APPROVALS REQUESTED" + ], + "type": "string" + }, + "description": "Filtering for the status.", + "name": "status", + "in": "query" + }, + { + "type": "string", + "description": "Used to illustrate which user is assigned to this move.\n", + "name": "assignedTo", + "in": "query" + }, + { + "type": "string", + "description": "filters using a counselingOffice name of the move", + "name": "counselingOffice", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Successfully returned all moves matching the criteria", + "schema": { + "$ref": "#/definitions/QueueMovesResult" + } + }, + "403": { + "description": "The request was denied", + "schema": { + "$ref": "#/definitions/Error" + } + }, + "500": { + "description": "A server error occurred", + "schema": { + "$ref": "#/definitions/Error" + } + } + } + } + }, "/queues/moves": { "get": { "description": "An office TOO user will be assigned a transportation office that will determine which moves are displayed in their queue based on the origin duty location. GHC moves will show up here onced they have reached the submitted status sent by the customer and have move task orders, shipments, and service items to approve.\n", diff --git a/pkg/gen/ghcapi/ghcoperations/mymove_api.go b/pkg/gen/ghcapi/ghcoperations/mymove_api.go index a2d58cc6f99..ee86793406f 100644 --- a/pkg/gen/ghcapi/ghcoperations/mymove_api.go +++ b/pkg/gen/ghcapi/ghcoperations/mymove_api.go @@ -180,6 +180,9 @@ func NewMymoveAPI(spec *loads.Document) *MymoveAPI { CustomerSupportRemarksGetCustomerSupportRemarksForMoveHandler: customer_support_remarks.GetCustomerSupportRemarksForMoveHandlerFunc(func(params customer_support_remarks.GetCustomerSupportRemarksForMoveParams) middleware.Responder { return middleware.NotImplemented("operation customer_support_remarks.GetCustomerSupportRemarksForMove has not yet been implemented") }), + QueuesGetDestinationRequestsQueueHandler: queues.GetDestinationRequestsQueueHandlerFunc(func(params queues.GetDestinationRequestsQueueParams) middleware.Responder { + return middleware.NotImplemented("operation queues.GetDestinationRequestsQueue has not yet been implemented") + }), GhcDocumentsGetDocumentHandler: ghc_documents.GetDocumentHandlerFunc(func(params ghc_documents.GetDocumentParams) middleware.Responder { return middleware.NotImplemented("operation ghc_documents.GetDocument has not yet been implemented") }), @@ -527,6 +530,8 @@ type MymoveAPI struct { CustomerGetCustomerHandler customer.GetCustomerHandler // CustomerSupportRemarksGetCustomerSupportRemarksForMoveHandler sets the operation handler for the get customer support remarks for move operation CustomerSupportRemarksGetCustomerSupportRemarksForMoveHandler customer_support_remarks.GetCustomerSupportRemarksForMoveHandler + // QueuesGetDestinationRequestsQueueHandler sets the operation handler for the get destination requests queue operation + QueuesGetDestinationRequestsQueueHandler queues.GetDestinationRequestsQueueHandler // GhcDocumentsGetDocumentHandler sets the operation handler for the get document operation GhcDocumentsGetDocumentHandler ghc_documents.GetDocumentHandler // MoveTaskOrderGetEntitlementsHandler sets the operation handler for the get entitlements operation @@ -870,6 +875,9 @@ func (o *MymoveAPI) Validate() error { if o.CustomerSupportRemarksGetCustomerSupportRemarksForMoveHandler == nil { unregistered = append(unregistered, "customer_support_remarks.GetCustomerSupportRemarksForMoveHandler") } + if o.QueuesGetDestinationRequestsQueueHandler == nil { + unregistered = append(unregistered, "queues.GetDestinationRequestsQueueHandler") + } if o.GhcDocumentsGetDocumentHandler == nil { unregistered = append(unregistered, "ghc_documents.GetDocumentHandler") } @@ -1335,6 +1343,10 @@ func (o *MymoveAPI) initHandlerCache() { if o.handlers["GET"] == nil { o.handlers["GET"] = make(map[string]http.Handler) } + o.handlers["GET"]["/queues/destination-requests"] = queues.NewGetDestinationRequestsQueue(o.context, o.QueuesGetDestinationRequestsQueueHandler) + if o.handlers["GET"] == nil { + o.handlers["GET"] = make(map[string]http.Handler) + } o.handlers["GET"]["/documents/{documentId}"] = ghc_documents.NewGetDocument(o.context, o.GhcDocumentsGetDocumentHandler) if o.handlers["GET"] == nil { o.handlers["GET"] = make(map[string]http.Handler) diff --git a/pkg/gen/ghcapi/ghcoperations/queues/get_destination_requests_queue.go b/pkg/gen/ghcapi/ghcoperations/queues/get_destination_requests_queue.go new file mode 100644 index 00000000000..0bc440cf200 --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/queues/get_destination_requests_queue.go @@ -0,0 +1,58 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package queues + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" +) + +// GetDestinationRequestsQueueHandlerFunc turns a function with the right signature into a get destination requests queue handler +type GetDestinationRequestsQueueHandlerFunc func(GetDestinationRequestsQueueParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn GetDestinationRequestsQueueHandlerFunc) Handle(params GetDestinationRequestsQueueParams) middleware.Responder { + return fn(params) +} + +// GetDestinationRequestsQueueHandler interface for that can handle valid get destination requests queue params +type GetDestinationRequestsQueueHandler interface { + Handle(GetDestinationRequestsQueueParams) middleware.Responder +} + +// NewGetDestinationRequestsQueue creates a new http.Handler for the get destination requests queue operation +func NewGetDestinationRequestsQueue(ctx *middleware.Context, handler GetDestinationRequestsQueueHandler) *GetDestinationRequestsQueue { + return &GetDestinationRequestsQueue{Context: ctx, Handler: handler} +} + +/* + GetDestinationRequestsQueue swagger:route GET /queues/destination-requests queues getDestinationRequestsQueue + +Gets queued list of all customer moves by GBLOC that have both CONUS & OCONUS destination requests (destination SIT, destination shuttle, address requests) + +A TOO will view this queue when they have destination requests tied to their GBLOC. This includes unapproved destination SIT service items, destination shuttle service items and destination address requests that are not yet approved by the TOO. +*/ +type GetDestinationRequestsQueue struct { + Context *middleware.Context + Handler GetDestinationRequestsQueueHandler +} + +func (o *GetDestinationRequestsQueue) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewGetDestinationRequestsQueueParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/pkg/gen/ghcapi/ghcoperations/queues/get_destination_requests_queue_parameters.go b/pkg/gen/ghcapi/ghcoperations/queues/get_destination_requests_queue_parameters.go new file mode 100644 index 00000000000..dac60111a5d --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/queues/get_destination_requests_queue_parameters.go @@ -0,0 +1,589 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package queues + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "fmt" + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// NewGetDestinationRequestsQueueParams creates a new GetDestinationRequestsQueueParams object +// +// There are no default values defined in the spec. +func NewGetDestinationRequestsQueueParams() GetDestinationRequestsQueueParams { + + return GetDestinationRequestsQueueParams{} +} + +// GetDestinationRequestsQueueParams contains all the bound params for the get destination requests queue operation +// typically these are obtained from a http.Request +// +// swagger:parameters getDestinationRequestsQueue +type GetDestinationRequestsQueueParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /* + In: query + */ + AppearedInTooAt *strfmt.DateTime + /*Used to illustrate which user is assigned to this move. + + In: query + */ + AssignedTo *string + /* + In: query + */ + Branch *string + /*filters using a counselingOffice name of the move + In: query + */ + CounselingOffice *string + /* + In: query + */ + CustomerName *string + /* + In: query + */ + DestinationDutyLocation *string + /* + In: query + */ + Edipi *string + /* + In: query + */ + Emplid *string + /* + In: query + */ + Locator *string + /*direction of sort order if applied + In: query + */ + Order *string + /* + Unique: true + In: query + Collection Format: multi + */ + OriginDutyLocation []string + /*requested page of results + In: query + */ + Page *int64 + /*results per page + In: query + */ + PerPage *int64 + /*filters the requested pickup date of a shipment on the move + In: query + */ + RequestedMoveDate *string + /*field that results should be sorted by + In: query + */ + Sort *string + /*Filtering for the status. + Unique: true + In: query + */ + Status []string +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewGetDestinationRequestsQueueParams() beforehand. +func (o *GetDestinationRequestsQueueParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + qs := runtime.Values(r.URL.Query()) + + qAppearedInTooAt, qhkAppearedInTooAt, _ := qs.GetOK("appearedInTooAt") + if err := o.bindAppearedInTooAt(qAppearedInTooAt, qhkAppearedInTooAt, route.Formats); err != nil { + res = append(res, err) + } + + qAssignedTo, qhkAssignedTo, _ := qs.GetOK("assignedTo") + if err := o.bindAssignedTo(qAssignedTo, qhkAssignedTo, route.Formats); err != nil { + res = append(res, err) + } + + qBranch, qhkBranch, _ := qs.GetOK("branch") + if err := o.bindBranch(qBranch, qhkBranch, route.Formats); err != nil { + res = append(res, err) + } + + qCounselingOffice, qhkCounselingOffice, _ := qs.GetOK("counselingOffice") + if err := o.bindCounselingOffice(qCounselingOffice, qhkCounselingOffice, route.Formats); err != nil { + res = append(res, err) + } + + qCustomerName, qhkCustomerName, _ := qs.GetOK("customerName") + if err := o.bindCustomerName(qCustomerName, qhkCustomerName, route.Formats); err != nil { + res = append(res, err) + } + + qDestinationDutyLocation, qhkDestinationDutyLocation, _ := qs.GetOK("destinationDutyLocation") + if err := o.bindDestinationDutyLocation(qDestinationDutyLocation, qhkDestinationDutyLocation, route.Formats); err != nil { + res = append(res, err) + } + + qEdipi, qhkEdipi, _ := qs.GetOK("edipi") + if err := o.bindEdipi(qEdipi, qhkEdipi, route.Formats); err != nil { + res = append(res, err) + } + + qEmplid, qhkEmplid, _ := qs.GetOK("emplid") + if err := o.bindEmplid(qEmplid, qhkEmplid, route.Formats); err != nil { + res = append(res, err) + } + + qLocator, qhkLocator, _ := qs.GetOK("locator") + if err := o.bindLocator(qLocator, qhkLocator, route.Formats); err != nil { + res = append(res, err) + } + + qOrder, qhkOrder, _ := qs.GetOK("order") + if err := o.bindOrder(qOrder, qhkOrder, route.Formats); err != nil { + res = append(res, err) + } + + qOriginDutyLocation, qhkOriginDutyLocation, _ := qs.GetOK("originDutyLocation") + if err := o.bindOriginDutyLocation(qOriginDutyLocation, qhkOriginDutyLocation, route.Formats); err != nil { + res = append(res, err) + } + + qPage, qhkPage, _ := qs.GetOK("page") + if err := o.bindPage(qPage, qhkPage, route.Formats); err != nil { + res = append(res, err) + } + + qPerPage, qhkPerPage, _ := qs.GetOK("perPage") + if err := o.bindPerPage(qPerPage, qhkPerPage, route.Formats); err != nil { + res = append(res, err) + } + + qRequestedMoveDate, qhkRequestedMoveDate, _ := qs.GetOK("requestedMoveDate") + if err := o.bindRequestedMoveDate(qRequestedMoveDate, qhkRequestedMoveDate, route.Formats); err != nil { + res = append(res, err) + } + + qSort, qhkSort, _ := qs.GetOK("sort") + if err := o.bindSort(qSort, qhkSort, route.Formats); err != nil { + res = append(res, err) + } + + qStatus, qhkStatus, _ := qs.GetOK("status") + if err := o.bindStatus(qStatus, qhkStatus, route.Formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindAppearedInTooAt binds and validates parameter AppearedInTooAt from query. +func (o *GetDestinationRequestsQueueParams) bindAppearedInTooAt(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + + // Format: date-time + value, err := formats.Parse("date-time", raw) + if err != nil { + return errors.InvalidType("appearedInTooAt", "query", "strfmt.DateTime", raw) + } + o.AppearedInTooAt = (value.(*strfmt.DateTime)) + + if err := o.validateAppearedInTooAt(formats); err != nil { + return err + } + + return nil +} + +// validateAppearedInTooAt carries on validations for parameter AppearedInTooAt +func (o *GetDestinationRequestsQueueParams) validateAppearedInTooAt(formats strfmt.Registry) error { + + if err := validate.FormatOf("appearedInTooAt", "query", "date-time", o.AppearedInTooAt.String(), formats); err != nil { + return err + } + return nil +} + +// bindAssignedTo binds and validates parameter AssignedTo from query. +func (o *GetDestinationRequestsQueueParams) bindAssignedTo(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + o.AssignedTo = &raw + + return nil +} + +// bindBranch binds and validates parameter Branch from query. +func (o *GetDestinationRequestsQueueParams) bindBranch(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + o.Branch = &raw + + return nil +} + +// bindCounselingOffice binds and validates parameter CounselingOffice from query. +func (o *GetDestinationRequestsQueueParams) bindCounselingOffice(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + o.CounselingOffice = &raw + + return nil +} + +// bindCustomerName binds and validates parameter CustomerName from query. +func (o *GetDestinationRequestsQueueParams) bindCustomerName(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + o.CustomerName = &raw + + return nil +} + +// bindDestinationDutyLocation binds and validates parameter DestinationDutyLocation from query. +func (o *GetDestinationRequestsQueueParams) bindDestinationDutyLocation(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + o.DestinationDutyLocation = &raw + + return nil +} + +// bindEdipi binds and validates parameter Edipi from query. +func (o *GetDestinationRequestsQueueParams) bindEdipi(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + o.Edipi = &raw + + return nil +} + +// bindEmplid binds and validates parameter Emplid from query. +func (o *GetDestinationRequestsQueueParams) bindEmplid(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + o.Emplid = &raw + + return nil +} + +// bindLocator binds and validates parameter Locator from query. +func (o *GetDestinationRequestsQueueParams) bindLocator(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + o.Locator = &raw + + return nil +} + +// bindOrder binds and validates parameter Order from query. +func (o *GetDestinationRequestsQueueParams) bindOrder(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + o.Order = &raw + + if err := o.validateOrder(formats); err != nil { + return err + } + + return nil +} + +// validateOrder carries on validations for parameter Order +func (o *GetDestinationRequestsQueueParams) validateOrder(formats strfmt.Registry) error { + + if err := validate.EnumCase("order", "query", *o.Order, []interface{}{"asc", "desc"}, true); err != nil { + return err + } + + return nil +} + +// bindOriginDutyLocation binds and validates array parameter OriginDutyLocation from query. +// +// Arrays are parsed according to CollectionFormat: "multi" (defaults to "csv" when empty). +func (o *GetDestinationRequestsQueueParams) bindOriginDutyLocation(rawData []string, hasKey bool, formats strfmt.Registry) error { + // CollectionFormat: multi + originDutyLocationIC := rawData + if len(originDutyLocationIC) == 0 { + return nil + } + + var originDutyLocationIR []string + for _, originDutyLocationIV := range originDutyLocationIC { + originDutyLocationI := originDutyLocationIV + + originDutyLocationIR = append(originDutyLocationIR, originDutyLocationI) + } + + o.OriginDutyLocation = originDutyLocationIR + if err := o.validateOriginDutyLocation(formats); err != nil { + return err + } + + return nil +} + +// validateOriginDutyLocation carries on validations for parameter OriginDutyLocation +func (o *GetDestinationRequestsQueueParams) validateOriginDutyLocation(formats strfmt.Registry) error { + + // uniqueItems: true + if err := validate.UniqueItems("originDutyLocation", "query", o.OriginDutyLocation); err != nil { + return err + } + return nil +} + +// bindPage binds and validates parameter Page from query. +func (o *GetDestinationRequestsQueueParams) bindPage(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + + value, err := swag.ConvertInt64(raw) + if err != nil { + return errors.InvalidType("page", "query", "int64", raw) + } + o.Page = &value + + return nil +} + +// bindPerPage binds and validates parameter PerPage from query. +func (o *GetDestinationRequestsQueueParams) bindPerPage(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + + value, err := swag.ConvertInt64(raw) + if err != nil { + return errors.InvalidType("perPage", "query", "int64", raw) + } + o.PerPage = &value + + return nil +} + +// bindRequestedMoveDate binds and validates parameter RequestedMoveDate from query. +func (o *GetDestinationRequestsQueueParams) bindRequestedMoveDate(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + o.RequestedMoveDate = &raw + + return nil +} + +// bindSort binds and validates parameter Sort from query. +func (o *GetDestinationRequestsQueueParams) bindSort(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + o.Sort = &raw + + if err := o.validateSort(formats); err != nil { + return err + } + + return nil +} + +// validateSort carries on validations for parameter Sort +func (o *GetDestinationRequestsQueueParams) validateSort(formats strfmt.Registry) error { + + if err := validate.EnumCase("sort", "query", *o.Sort, []interface{}{"customerName", "edipi", "emplid", "branch", "locator", "status", "originDutyLocation", "destinationDutyLocation", "requestedMoveDate", "appearedInTooAt", "assignedTo", "counselingOffice"}, true); err != nil { + return err + } + + return nil +} + +// bindStatus binds and validates array parameter Status from query. +// +// Arrays are parsed according to CollectionFormat: "" (defaults to "csv" when empty). +func (o *GetDestinationRequestsQueueParams) bindStatus(rawData []string, hasKey bool, formats strfmt.Registry) error { + var qvStatus string + if len(rawData) > 0 { + qvStatus = rawData[len(rawData)-1] + } + + // CollectionFormat: + statusIC := swag.SplitByFormat(qvStatus, "") + if len(statusIC) == 0 { + return nil + } + + var statusIR []string + for i, statusIV := range statusIC { + statusI := statusIV + + if err := validate.EnumCase(fmt.Sprintf("%s.%v", "status", i), "query", statusI, []interface{}{"SUBMITTED", "SERVICE COUNSELING COMPLETED", "APPROVALS REQUESTED"}, true); err != nil { + return err + } + + statusIR = append(statusIR, statusI) + } + + o.Status = statusIR + if err := o.validateStatus(formats); err != nil { + return err + } + + return nil +} + +// validateStatus carries on validations for parameter Status +func (o *GetDestinationRequestsQueueParams) validateStatus(formats strfmt.Registry) error { + + // uniqueItems: true + if err := validate.UniqueItems("status", "query", o.Status); err != nil { + return err + } + return nil +} diff --git a/pkg/gen/ghcapi/ghcoperations/queues/get_destination_requests_queue_responses.go b/pkg/gen/ghcapi/ghcoperations/queues/get_destination_requests_queue_responses.go new file mode 100644 index 00000000000..ea27ea91263 --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/queues/get_destination_requests_queue_responses.go @@ -0,0 +1,149 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package queues + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "github.com/transcom/mymove/pkg/gen/ghcmessages" +) + +// GetDestinationRequestsQueueOKCode is the HTTP code returned for type GetDestinationRequestsQueueOK +const GetDestinationRequestsQueueOKCode int = 200 + +/* +GetDestinationRequestsQueueOK Successfully returned all moves matching the criteria + +swagger:response getDestinationRequestsQueueOK +*/ +type GetDestinationRequestsQueueOK struct { + + /* + In: Body + */ + Payload *ghcmessages.QueueMovesResult `json:"body,omitempty"` +} + +// NewGetDestinationRequestsQueueOK creates GetDestinationRequestsQueueOK with default headers values +func NewGetDestinationRequestsQueueOK() *GetDestinationRequestsQueueOK { + + return &GetDestinationRequestsQueueOK{} +} + +// WithPayload adds the payload to the get destination requests queue o k response +func (o *GetDestinationRequestsQueueOK) WithPayload(payload *ghcmessages.QueueMovesResult) *GetDestinationRequestsQueueOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get destination requests queue o k response +func (o *GetDestinationRequestsQueueOK) SetPayload(payload *ghcmessages.QueueMovesResult) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetDestinationRequestsQueueOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// GetDestinationRequestsQueueForbiddenCode is the HTTP code returned for type GetDestinationRequestsQueueForbidden +const GetDestinationRequestsQueueForbiddenCode int = 403 + +/* +GetDestinationRequestsQueueForbidden The request was denied + +swagger:response getDestinationRequestsQueueForbidden +*/ +type GetDestinationRequestsQueueForbidden struct { + + /* + In: Body + */ + Payload *ghcmessages.Error `json:"body,omitempty"` +} + +// NewGetDestinationRequestsQueueForbidden creates GetDestinationRequestsQueueForbidden with default headers values +func NewGetDestinationRequestsQueueForbidden() *GetDestinationRequestsQueueForbidden { + + return &GetDestinationRequestsQueueForbidden{} +} + +// WithPayload adds the payload to the get destination requests queue forbidden response +func (o *GetDestinationRequestsQueueForbidden) WithPayload(payload *ghcmessages.Error) *GetDestinationRequestsQueueForbidden { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get destination requests queue forbidden response +func (o *GetDestinationRequestsQueueForbidden) SetPayload(payload *ghcmessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetDestinationRequestsQueueForbidden) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(403) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// GetDestinationRequestsQueueInternalServerErrorCode is the HTTP code returned for type GetDestinationRequestsQueueInternalServerError +const GetDestinationRequestsQueueInternalServerErrorCode int = 500 + +/* +GetDestinationRequestsQueueInternalServerError A server error occurred + +swagger:response getDestinationRequestsQueueInternalServerError +*/ +type GetDestinationRequestsQueueInternalServerError struct { + + /* + In: Body + */ + Payload *ghcmessages.Error `json:"body,omitempty"` +} + +// NewGetDestinationRequestsQueueInternalServerError creates GetDestinationRequestsQueueInternalServerError with default headers values +func NewGetDestinationRequestsQueueInternalServerError() *GetDestinationRequestsQueueInternalServerError { + + return &GetDestinationRequestsQueueInternalServerError{} +} + +// WithPayload adds the payload to the get destination requests queue internal server error response +func (o *GetDestinationRequestsQueueInternalServerError) WithPayload(payload *ghcmessages.Error) *GetDestinationRequestsQueueInternalServerError { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get destination requests queue internal server error response +func (o *GetDestinationRequestsQueueInternalServerError) SetPayload(payload *ghcmessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetDestinationRequestsQueueInternalServerError) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(500) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/pkg/gen/ghcapi/ghcoperations/queues/get_destination_requests_queue_urlbuilder.go b/pkg/gen/ghcapi/ghcoperations/queues/get_destination_requests_queue_urlbuilder.go new file mode 100644 index 00000000000..c8b86e869dc --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/queues/get_destination_requests_queue_urlbuilder.go @@ -0,0 +1,256 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package queues + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// GetDestinationRequestsQueueURL generates an URL for the get destination requests queue operation +type GetDestinationRequestsQueueURL struct { + AppearedInTooAt *strfmt.DateTime + AssignedTo *string + Branch *string + CounselingOffice *string + CustomerName *string + DestinationDutyLocation *string + Edipi *string + Emplid *string + Locator *string + Order *string + OriginDutyLocation []string + Page *int64 + PerPage *int64 + RequestedMoveDate *string + Sort *string + Status []string + + _basePath string + // avoid unkeyed usage + _ struct{} +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *GetDestinationRequestsQueueURL) WithBasePath(bp string) *GetDestinationRequestsQueueURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *GetDestinationRequestsQueueURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *GetDestinationRequestsQueueURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/queues/destination-requests" + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/ghc/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + qs := make(url.Values) + + var appearedInTooAtQ string + if o.AppearedInTooAt != nil { + appearedInTooAtQ = o.AppearedInTooAt.String() + } + if appearedInTooAtQ != "" { + qs.Set("appearedInTooAt", appearedInTooAtQ) + } + + var assignedToQ string + if o.AssignedTo != nil { + assignedToQ = *o.AssignedTo + } + if assignedToQ != "" { + qs.Set("assignedTo", assignedToQ) + } + + var branchQ string + if o.Branch != nil { + branchQ = *o.Branch + } + if branchQ != "" { + qs.Set("branch", branchQ) + } + + var counselingOfficeQ string + if o.CounselingOffice != nil { + counselingOfficeQ = *o.CounselingOffice + } + if counselingOfficeQ != "" { + qs.Set("counselingOffice", counselingOfficeQ) + } + + var customerNameQ string + if o.CustomerName != nil { + customerNameQ = *o.CustomerName + } + if customerNameQ != "" { + qs.Set("customerName", customerNameQ) + } + + var destinationDutyLocationQ string + if o.DestinationDutyLocation != nil { + destinationDutyLocationQ = *o.DestinationDutyLocation + } + if destinationDutyLocationQ != "" { + qs.Set("destinationDutyLocation", destinationDutyLocationQ) + } + + var edipiQ string + if o.Edipi != nil { + edipiQ = *o.Edipi + } + if edipiQ != "" { + qs.Set("edipi", edipiQ) + } + + var emplidQ string + if o.Emplid != nil { + emplidQ = *o.Emplid + } + if emplidQ != "" { + qs.Set("emplid", emplidQ) + } + + var locatorQ string + if o.Locator != nil { + locatorQ = *o.Locator + } + if locatorQ != "" { + qs.Set("locator", locatorQ) + } + + var orderQ string + if o.Order != nil { + orderQ = *o.Order + } + if orderQ != "" { + qs.Set("order", orderQ) + } + + var originDutyLocationIR []string + for _, originDutyLocationI := range o.OriginDutyLocation { + originDutyLocationIS := originDutyLocationI + if originDutyLocationIS != "" { + originDutyLocationIR = append(originDutyLocationIR, originDutyLocationIS) + } + } + + originDutyLocation := swag.JoinByFormat(originDutyLocationIR, "multi") + + for _, qsv := range originDutyLocation { + qs.Add("originDutyLocation", qsv) + } + + var pageQ string + if o.Page != nil { + pageQ = swag.FormatInt64(*o.Page) + } + if pageQ != "" { + qs.Set("page", pageQ) + } + + var perPageQ string + if o.PerPage != nil { + perPageQ = swag.FormatInt64(*o.PerPage) + } + if perPageQ != "" { + qs.Set("perPage", perPageQ) + } + + var requestedMoveDateQ string + if o.RequestedMoveDate != nil { + requestedMoveDateQ = *o.RequestedMoveDate + } + if requestedMoveDateQ != "" { + qs.Set("requestedMoveDate", requestedMoveDateQ) + } + + var sortQ string + if o.Sort != nil { + sortQ = *o.Sort + } + if sortQ != "" { + qs.Set("sort", sortQ) + } + + var statusIR []string + for _, statusI := range o.Status { + statusIS := statusI + if statusIS != "" { + statusIR = append(statusIR, statusIS) + } + } + + status := swag.JoinByFormat(statusIR, "") + + if len(status) > 0 { + qsv := status[0] + if qsv != "" { + qs.Set("status", qsv) + } + } + + _result.RawQuery = qs.Encode() + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *GetDestinationRequestsQueueURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *GetDestinationRequestsQueueURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *GetDestinationRequestsQueueURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on GetDestinationRequestsQueueURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on GetDestinationRequestsQueueURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *GetDestinationRequestsQueueURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/pkg/gen/primeapi/embedded_spec.go b/pkg/gen/primeapi/embedded_spec.go index c4dfc4fb5b2..e5f49170cbf 100644 --- a/pkg/gen/primeapi/embedded_spec.go +++ b/pkg/gen/primeapi/embedded_spec.go @@ -355,7 +355,7 @@ func init() { }, "/mto-service-items/{mtoServiceItemID}": { "patch": { - "description": "Updates MTOServiceItems after creation. Not all service items or fields may be updated, please see details below.\n\nThis endpoint supports different body definitions. In the modelType field below, select the modelType corresponding\n to the service item you wish to update and the documentation will update with the new definition.\n\n* Addresses: To update a destination service item's SIT destination final address, update the shipment delivery address.\nFor approved shipments, please use [updateShipmentDestinationAddress](#mtoShipment/updateShipmentDestinationAddress).\nFor shipments not yet approved, please use [updateMTOShipmentAddress](#mtoShipment/updateMTOShipmentAddress).\n\n* SIT Service Items: Take note that when updating ` + "`" + `sitCustomerContacted` + "`" + `, ` + "`" + `sitDepartureDate` + "`" + `, or ` + "`" + `sitRequestedDelivery` + "`" + `, we want\nthose to be updated on ` + "`" + `DOASIT` + "`" + ` (for origin SIT) and ` + "`" + `DDASIT` + "`" + ` (for destination SIT). If updating those values in other service\nitems, the office users will not have as much attention to those values.\n\nTo create a service item, please use [createMTOServiceItem](#mtoServiceItem/createMTOServiceItem)) endpoint.\n\n* Resubmitting rejected SIT/Accessorial service items: This endpoint will handle the logic of changing the status of rejected SIT/Accessorial service items from\nREJECTED to SUBMITTED. Please provide the ` + "`" + `requestedApprovalsRequestedStatus: true` + "`" + ` when resubmitting as this will give attention to the TOO to\nreview the resubmitted SIT/Accessorial service item. Another note, ` + "`" + `updateReason` + "`" + ` must have a different value than the current ` + "`" + `reason` + "`" + ` value on the service item.\nIf this value is not updated, then an error will be sent back.\n\nThe following SIT service items can be resubmitted following a rejection:\n- DDASIT\n- DDDSIT\n- DDFSIT\n- DOASIT\n- DOPSIT\n- DOFSIT\n- DDSFSC\n- DOSFSC\n\nThe following Accessorial service items can be resubmitted following a rejection:\n- IOSHUT\n- IDSHUT\n\nAt a MINIMUM, the payload for resubmitting a rejected SIT/Accessorial service item must look like this:\n` + "`" + `` + "`" + `` + "`" + `json\n{\n \"reServiceCode\": \"DDFSIT\",\n \"updateReason\": \"A reason that differs from the previous reason\",\n \"modelType\": \"UpdateMTOServiceItemSIT\",\n \"requestApprovalsRequestedStatus\": true\n}\n` + "`" + `` + "`" + `` + "`" + `\n\nThe following service items allow you to update the Port that the shipment will use:\n- PODFSC (Port of Debarkation can be updated)\n- POEFSC (Port of Embarkation can be updated)\n\nAt a MINIMUM, the payload for updating the port should contain the reServiceCode (PODFSC or POEFSC), modelType (UpdateMTOServiceItemInternationalPortFSC), portCode, and id for the service item.\nPlease see the example payload below:\n` + "`" + `` + "`" + `` + "`" + `json\n{\n \"id\": \"1ed224b6-c65e-4616-b88e-8304d26c9562\",\n \"modelType\": \"UpdateMTOServiceItemInternationalPortFSC\",\n \"portCode\": \"SEA\",\n \"reServiceCode\": \"POEFSC\"\n}\n` + "`" + `` + "`" + `` + "`" + `\n", + "description": "Updates MTOServiceItems after creation. Not all service items or fields may be updated, please see details below.\n\nThis endpoint supports different body definitions. In the modelType field below, select the modelType corresponding\n to the service item you wish to update and the documentation will update with the new definition.\n\n* Addresses: To update a destination service item's SIT destination final address, update the shipment delivery address.\nFor approved shipments, please use [updateShipmentDestinationAddress](#mtoShipment/updateShipmentDestinationAddress).\nFor shipments not yet approved, please use [updateMTOShipmentAddress](#mtoShipment/updateMTOShipmentAddress).\n\n* SIT Service Items: Take note that when updating ` + "`" + `sitCustomerContacted` + "`" + `, ` + "`" + `sitDepartureDate` + "`" + `, or ` + "`" + `sitRequestedDelivery` + "`" + `, we want\nthose to be updated on ` + "`" + `DOASIT` + "`" + ` (for origin SIT) and ` + "`" + `DDASIT` + "`" + ` (for destination SIT). If updating those values in other service\nitems, the office users will not have as much attention to those values.\n\nTo create a service item, please use [createMTOServiceItem](#mtoServiceItem/createMTOServiceItem)) endpoint.\n\n* Resubmitting rejected SIT/Accessorial service items: This endpoint will handle the logic of changing the status of rejected SIT/Accessorial service items from\nREJECTED to SUBMITTED. Please provide the ` + "`" + `requestedApprovalsRequestedStatus: true` + "`" + ` when resubmitting as this will give attention to the TOO to\nreview the resubmitted SIT/Accessorial service item. Another note, ` + "`" + `updateReason` + "`" + ` must have a different value than the current ` + "`" + `reason` + "`" + ` value on the service item.\nIf this value is not updated, then an error will be sent back.\n\nThe following SIT service items can be resubmitted following a rejection:\n- DDASIT\n- DDDSIT\n- DDFSIT\n- DOASIT\n- DOPSIT\n- DOFSIT\n- DDSFSC\n- DOSFSC\n\nThe following Accessorial service items can be resubmitted following a rejection:\n- IOSHUT\n- IDSHUT\n\nAt a MINIMUM, the payload for resubmitting a rejected SIT/Accessorial service item must look like this:\n` + "`" + `` + "`" + `` + "`" + `json\n{\n \"reServiceCode\": \"DDFSIT\",\n \"updateReason\": \"A reason that differs from the previous reason\",\n \"modelType\": \"UpdateMTOServiceItemSIT\",\n \"requestApprovalsRequestedStatus\": true\n}\n` + "`" + `` + "`" + `` + "`" + `\n\nThe following service items allow you to update the Port that the shipment will use:\n- PODFSC (Port of Debarkation can be updated)\n- POEFSC (Port of Embarkation can be updated)\n\nAt a MINIMUM, the payload for updating the port should contain the reServiceCode (PODFSC or POEFSC), modelType (UpdateMTOServiceItemInternationalPortFSC), portCode, and id for the service item.\nPlease see the example payload below:\n` + "`" + `` + "`" + `` + "`" + `json\n{\n \"id\": \"1ed224b6-c65e-4616-b88e-8304d26c9562\",\n \"modelType\": \"UpdateMTOServiceItemInternationalPortFSC\",\n \"portCode\": \"SEA\",\n \"reServiceCode\": \"POEFSC\"\n}\n` + "`" + `` + "`" + `` + "`" + `\n\nThe following crating/uncrating service items can be resubmitted following a rejection:\n- ICRT\n- IUCRT\n\nAt a MINIMUM, the payload for resubmitting a rejected crating/uncrating service item must look like this:\n` + "`" + `` + "`" + `` + "`" + `json\n{\n \"item\": {\n \"length\": 10000,\n \"width\": 10000,\n \"height\": 10000\n },\n \"crate\": {\n \"length\": 20000,\n \"width\": 20000,\n \"height\": 20000\n },\n \"updateReason\": \"A reason that differs from the previous reason\",\n \"modelType\": \"UpdateMTOServiceItemCrating\",\n \"requestApprovalsRequestedStatus\": true\n}\n` + "`" + `` + "`" + `` + "`" + `\n", "consumes": [ "application/json" ], @@ -2330,6 +2330,50 @@ func init() { } ] }, + "MTOServiceItemDomesticShuttle": { + "description": "Describes a domestic shuttle service item.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItem" + }, + { + "type": "object", + "required": [ + "reason", + "reServiceCode" + ], + "properties": { + "actualWeight": { + "description": "A record of the actual weight that was shuttled. Provided by the movers, based on weight tickets.", + "type": "integer", + "x-nullable": true, + "x-omitempty": false, + "example": 4000 + }, + "estimatedWeight": { + "description": "An estimate of how much weight from a shipment will be included in the shuttling service.", + "type": "integer", + "x-nullable": true, + "x-omitempty": false, + "example": 4200 + }, + "reServiceCode": { + "description": "A unique code for the service item. Indicates if shuttling is requested for the shipment origin (` + "`" + `DOSHUT` + "`" + `) or destination (` + "`" + `DDSHUT` + "`" + `).\n", + "type": "string", + "enum": [ + "DOSHUT", + "DDSHUT" + ] + }, + "reason": { + "description": "The contractor's explanation for why a shuttle service is requested. Used by the TOO while deciding to approve or reject the service item.\n", + "type": "string", + "example": "Storage items need to be picked up." + } + } + } + ] + }, "MTOServiceItemInternationalCrating": { "description": "Describes a international crating/uncrating service item subtype of a MTOServiceItem.", "allOf": [ @@ -2486,13 +2530,14 @@ func init() { ] }, "MTOServiceItemModelType": { - "description": "Describes all model sub-types for a MTOServiceItem model.\n\nUsing this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DOFSIT, DOASIT - MTOServiceItemOriginSIT\n * DDFSIT, DDASIT - MTOServiceItemDestSIT\n * DOSHUT, DDSHUT - MTOServiceItemShuttle\n * IOSHUT, IDSHUT - MTOServiceItemInternationalShuttle\n * DCRT, DUCRT - MTOServiceItemDomesticCrating\n * ICRT, IUCRT - MTOServiceItemInternationalCrating\n * PODFSC, POEFSC - MTOSerivceItemInternationalFuelSurcharge\n\nThe documentation will then update with the supported fields.\n", + "description": "Describes all model sub-types for a MTOServiceItem model.\n\nUsing this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DOFSIT, DOASIT - MTOServiceItemOriginSIT\n * DDFSIT, DDASIT - MTOServiceItemDestSIT\n * DOSHUT, DDSHUT - MTOServiceItemShuttle\n * DOSHUT, DDSHUT - MTOServiceItemDomesticShuttle\n * IOSHUT, IDSHUT - MTOServiceItemInternationalShuttle\n * DCRT, DUCRT - MTOServiceItemDomesticCrating\n * ICRT, IUCRT - MTOServiceItemInternationalCrating\n * PODFSC, POEFSC - MTOSerivceItemInternationalFuelSurcharge\n\nThe documentation will then update with the supported fields.\n", "type": "string", "enum": [ "MTOServiceItemBasic", "MTOServiceItemOriginSIT", "MTOServiceItemDestSIT", "MTOServiceItemShuttle", + "MTOServiceItemDomesticShuttle", "MTOServiceItemInternationalShuttle", "MTOServiceItemDomesticCrating", "MTOServiceItemInternationalCrating", @@ -4069,6 +4114,67 @@ func init() { }, "discriminator": "modelType" }, + "UpdateMTOServiceItemCrating": { + "description": "Subtype used to provide the size and types for crating. This is not creating a new service item but rather updating an existing service item.\n", + "allOf": [ + { + "$ref": "#/definitions/UpdateMTOServiceItem" + }, + { + "type": "object", + "properties": { + "crate": { + "description": "The dimensions for the crate the item will be shipped in.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItemDimension" + } + ] + }, + "description": { + "description": "A description of the item being crated.", + "type": "string", + "x-nullable": true, + "example": "Decorated horse head to be crated." + }, + "externalCrate": { + "type": "boolean", + "x-nullable": true + }, + "item": { + "description": "The dimensions of the item being crated.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItemDimension" + } + ] + }, + "reServiceCode": { + "description": "Service code allowed for this model type.", + "type": "string", + "enum": [ + "ICRT", + "IUCRT" + ] + }, + "requestApprovalsRequestedStatus": { + "description": "Indicates if \"Approvals Requested\" status is being requested.", + "type": "boolean", + "x-nullable": true + }, + "standaloneCrate": { + "type": "boolean", + "x-nullable": true + }, + "updateReason": { + "description": "Reason for updating service item.", + "type": "string", + "x-nullable": true + } + } + } + ] + }, "UpdateMTOServiceItemInternationalPortFSC": { "description": "Subtype used to provide the port for fuel surcharge. This is not creating a new service item but rather updating an existing service item.\n", "allOf": [ @@ -4138,13 +4244,14 @@ func init() { ] }, "UpdateMTOServiceItemModelType": { - "description": "Using this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DDDSIT - UpdateMTOServiceItemSIT\n * DDFSIT - UpdateMTOServiceItemSIT\n * DDASIT - UpdateMTOServiceItemSIT\n * DOPSIT - UpdateMTOServiceItemSIT\n * DOASIT - UpdateMTOServiceItemSIT\n * DOFSIT - UpdateMTOServiceItemSIT\n * DOSFSC - UpdateMTOServiceItemSIT\n * DDSFSC - UpdateMTOServiceItemSIT\n * DDSHUT - UpdateMTOServiceItemShuttle\n * DOSHUT - UpdateMTOServiceItemShuttle\n * PODFSC - UpdateMTOServiceItemInternationalPortFSC\n * POEFSC - UpdateMTOServiceItemInternationalPortFSC\n * IDSHUT - UpdateMTOServiceItemInternationalShuttle\n * IOSHUT - UpdateMTOServiceItemInternationalShuttle\n\nThe documentation will then update with the supported fields.\n", + "description": "Using this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DDDSIT - UpdateMTOServiceItemSIT\n * DDFSIT - UpdateMTOServiceItemSIT\n * DDASIT - UpdateMTOServiceItemSIT\n * DOPSIT - UpdateMTOServiceItemSIT\n * DOASIT - UpdateMTOServiceItemSIT\n * DOFSIT - UpdateMTOServiceItemSIT\n * DOSFSC - UpdateMTOServiceItemSIT\n * DDSFSC - UpdateMTOServiceItemSIT\n * DDSHUT - UpdateMTOServiceItemShuttle\n * DOSHUT - UpdateMTOServiceItemShuttle\n * PODFSC - UpdateMTOServiceItemInternationalPortFSC\n * POEFSC - UpdateMTOServiceItemInternationalPortFSC\n * IDSHUT - UpdateMTOServiceItemInternationalShuttle\n * IOSHUT - UpdateMTOServiceItemInternationalShuttle\n * ICRT - UpdateMTOServiceItemCrating\n * IUCRT - UpdateMTOServiceItemCrating\n\nThe documentation will then update with the supported fields.\n", "type": "string", "enum": [ "UpdateMTOServiceItemSIT", "UpdateMTOServiceItemShuttle", "UpdateMTOServiceItemInternationalPortFSC", - "UpdateMTOServiceItemInternationalShuttle" + "UpdateMTOServiceItemInternationalShuttle", + "UpdateMTOServiceItemCrating" ] }, "UpdateMTOServiceItemSIT": { @@ -5157,7 +5264,7 @@ func init() { }, "/mto-service-items/{mtoServiceItemID}": { "patch": { - "description": "Updates MTOServiceItems after creation. Not all service items or fields may be updated, please see details below.\n\nThis endpoint supports different body definitions. In the modelType field below, select the modelType corresponding\n to the service item you wish to update and the documentation will update with the new definition.\n\n* Addresses: To update a destination service item's SIT destination final address, update the shipment delivery address.\nFor approved shipments, please use [updateShipmentDestinationAddress](#mtoShipment/updateShipmentDestinationAddress).\nFor shipments not yet approved, please use [updateMTOShipmentAddress](#mtoShipment/updateMTOShipmentAddress).\n\n* SIT Service Items: Take note that when updating ` + "`" + `sitCustomerContacted` + "`" + `, ` + "`" + `sitDepartureDate` + "`" + `, or ` + "`" + `sitRequestedDelivery` + "`" + `, we want\nthose to be updated on ` + "`" + `DOASIT` + "`" + ` (for origin SIT) and ` + "`" + `DDASIT` + "`" + ` (for destination SIT). If updating those values in other service\nitems, the office users will not have as much attention to those values.\n\nTo create a service item, please use [createMTOServiceItem](#mtoServiceItem/createMTOServiceItem)) endpoint.\n\n* Resubmitting rejected SIT/Accessorial service items: This endpoint will handle the logic of changing the status of rejected SIT/Accessorial service items from\nREJECTED to SUBMITTED. Please provide the ` + "`" + `requestedApprovalsRequestedStatus: true` + "`" + ` when resubmitting as this will give attention to the TOO to\nreview the resubmitted SIT/Accessorial service item. Another note, ` + "`" + `updateReason` + "`" + ` must have a different value than the current ` + "`" + `reason` + "`" + ` value on the service item.\nIf this value is not updated, then an error will be sent back.\n\nThe following SIT service items can be resubmitted following a rejection:\n- DDASIT\n- DDDSIT\n- DDFSIT\n- DOASIT\n- DOPSIT\n- DOFSIT\n- DDSFSC\n- DOSFSC\n\nThe following Accessorial service items can be resubmitted following a rejection:\n- IOSHUT\n- IDSHUT\n\nAt a MINIMUM, the payload for resubmitting a rejected SIT/Accessorial service item must look like this:\n` + "`" + `` + "`" + `` + "`" + `json\n{\n \"reServiceCode\": \"DDFSIT\",\n \"updateReason\": \"A reason that differs from the previous reason\",\n \"modelType\": \"UpdateMTOServiceItemSIT\",\n \"requestApprovalsRequestedStatus\": true\n}\n` + "`" + `` + "`" + `` + "`" + `\n\nThe following service items allow you to update the Port that the shipment will use:\n- PODFSC (Port of Debarkation can be updated)\n- POEFSC (Port of Embarkation can be updated)\n\nAt a MINIMUM, the payload for updating the port should contain the reServiceCode (PODFSC or POEFSC), modelType (UpdateMTOServiceItemInternationalPortFSC), portCode, and id for the service item.\nPlease see the example payload below:\n` + "`" + `` + "`" + `` + "`" + `json\n{\n \"id\": \"1ed224b6-c65e-4616-b88e-8304d26c9562\",\n \"modelType\": \"UpdateMTOServiceItemInternationalPortFSC\",\n \"portCode\": \"SEA\",\n \"reServiceCode\": \"POEFSC\"\n}\n` + "`" + `` + "`" + `` + "`" + `\n", + "description": "Updates MTOServiceItems after creation. Not all service items or fields may be updated, please see details below.\n\nThis endpoint supports different body definitions. In the modelType field below, select the modelType corresponding\n to the service item you wish to update and the documentation will update with the new definition.\n\n* Addresses: To update a destination service item's SIT destination final address, update the shipment delivery address.\nFor approved shipments, please use [updateShipmentDestinationAddress](#mtoShipment/updateShipmentDestinationAddress).\nFor shipments not yet approved, please use [updateMTOShipmentAddress](#mtoShipment/updateMTOShipmentAddress).\n\n* SIT Service Items: Take note that when updating ` + "`" + `sitCustomerContacted` + "`" + `, ` + "`" + `sitDepartureDate` + "`" + `, or ` + "`" + `sitRequestedDelivery` + "`" + `, we want\nthose to be updated on ` + "`" + `DOASIT` + "`" + ` (for origin SIT) and ` + "`" + `DDASIT` + "`" + ` (for destination SIT). If updating those values in other service\nitems, the office users will not have as much attention to those values.\n\nTo create a service item, please use [createMTOServiceItem](#mtoServiceItem/createMTOServiceItem)) endpoint.\n\n* Resubmitting rejected SIT/Accessorial service items: This endpoint will handle the logic of changing the status of rejected SIT/Accessorial service items from\nREJECTED to SUBMITTED. Please provide the ` + "`" + `requestedApprovalsRequestedStatus: true` + "`" + ` when resubmitting as this will give attention to the TOO to\nreview the resubmitted SIT/Accessorial service item. Another note, ` + "`" + `updateReason` + "`" + ` must have a different value than the current ` + "`" + `reason` + "`" + ` value on the service item.\nIf this value is not updated, then an error will be sent back.\n\nThe following SIT service items can be resubmitted following a rejection:\n- DDASIT\n- DDDSIT\n- DDFSIT\n- DOASIT\n- DOPSIT\n- DOFSIT\n- DDSFSC\n- DOSFSC\n\nThe following Accessorial service items can be resubmitted following a rejection:\n- IOSHUT\n- IDSHUT\n\nAt a MINIMUM, the payload for resubmitting a rejected SIT/Accessorial service item must look like this:\n` + "`" + `` + "`" + `` + "`" + `json\n{\n \"reServiceCode\": \"DDFSIT\",\n \"updateReason\": \"A reason that differs from the previous reason\",\n \"modelType\": \"UpdateMTOServiceItemSIT\",\n \"requestApprovalsRequestedStatus\": true\n}\n` + "`" + `` + "`" + `` + "`" + `\n\nThe following service items allow you to update the Port that the shipment will use:\n- PODFSC (Port of Debarkation can be updated)\n- POEFSC (Port of Embarkation can be updated)\n\nAt a MINIMUM, the payload for updating the port should contain the reServiceCode (PODFSC or POEFSC), modelType (UpdateMTOServiceItemInternationalPortFSC), portCode, and id for the service item.\nPlease see the example payload below:\n` + "`" + `` + "`" + `` + "`" + `json\n{\n \"id\": \"1ed224b6-c65e-4616-b88e-8304d26c9562\",\n \"modelType\": \"UpdateMTOServiceItemInternationalPortFSC\",\n \"portCode\": \"SEA\",\n \"reServiceCode\": \"POEFSC\"\n}\n` + "`" + `` + "`" + `` + "`" + `\n\nThe following crating/uncrating service items can be resubmitted following a rejection:\n- ICRT\n- IUCRT\n\nAt a MINIMUM, the payload for resubmitting a rejected crating/uncrating service item must look like this:\n` + "`" + `` + "`" + `` + "`" + `json\n{\n \"item\": {\n \"length\": 10000,\n \"width\": 10000,\n \"height\": 10000\n },\n \"crate\": {\n \"length\": 20000,\n \"width\": 20000,\n \"height\": 20000\n },\n \"updateReason\": \"A reason that differs from the previous reason\",\n \"modelType\": \"UpdateMTOServiceItemCrating\",\n \"requestApprovalsRequestedStatus\": true\n}\n` + "`" + `` + "`" + `` + "`" + `\n", "consumes": [ "application/json" ], @@ -7407,6 +7514,50 @@ func init() { } ] }, + "MTOServiceItemDomesticShuttle": { + "description": "Describes a domestic shuttle service item.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItem" + }, + { + "type": "object", + "required": [ + "reason", + "reServiceCode" + ], + "properties": { + "actualWeight": { + "description": "A record of the actual weight that was shuttled. Provided by the movers, based on weight tickets.", + "type": "integer", + "x-nullable": true, + "x-omitempty": false, + "example": 4000 + }, + "estimatedWeight": { + "description": "An estimate of how much weight from a shipment will be included in the shuttling service.", + "type": "integer", + "x-nullable": true, + "x-omitempty": false, + "example": 4200 + }, + "reServiceCode": { + "description": "A unique code for the service item. Indicates if shuttling is requested for the shipment origin (` + "`" + `DOSHUT` + "`" + `) or destination (` + "`" + `DDSHUT` + "`" + `).\n", + "type": "string", + "enum": [ + "DOSHUT", + "DDSHUT" + ] + }, + "reason": { + "description": "The contractor's explanation for why a shuttle service is requested. Used by the TOO while deciding to approve or reject the service item.\n", + "type": "string", + "example": "Storage items need to be picked up." + } + } + } + ] + }, "MTOServiceItemInternationalCrating": { "description": "Describes a international crating/uncrating service item subtype of a MTOServiceItem.", "allOf": [ @@ -7563,13 +7714,14 @@ func init() { ] }, "MTOServiceItemModelType": { - "description": "Describes all model sub-types for a MTOServiceItem model.\n\nUsing this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DOFSIT, DOASIT - MTOServiceItemOriginSIT\n * DDFSIT, DDASIT - MTOServiceItemDestSIT\n * DOSHUT, DDSHUT - MTOServiceItemShuttle\n * IOSHUT, IDSHUT - MTOServiceItemInternationalShuttle\n * DCRT, DUCRT - MTOServiceItemDomesticCrating\n * ICRT, IUCRT - MTOServiceItemInternationalCrating\n * PODFSC, POEFSC - MTOSerivceItemInternationalFuelSurcharge\n\nThe documentation will then update with the supported fields.\n", + "description": "Describes all model sub-types for a MTOServiceItem model.\n\nUsing this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DOFSIT, DOASIT - MTOServiceItemOriginSIT\n * DDFSIT, DDASIT - MTOServiceItemDestSIT\n * DOSHUT, DDSHUT - MTOServiceItemShuttle\n * DOSHUT, DDSHUT - MTOServiceItemDomesticShuttle\n * IOSHUT, IDSHUT - MTOServiceItemInternationalShuttle\n * DCRT, DUCRT - MTOServiceItemDomesticCrating\n * ICRT, IUCRT - MTOServiceItemInternationalCrating\n * PODFSC, POEFSC - MTOSerivceItemInternationalFuelSurcharge\n\nThe documentation will then update with the supported fields.\n", "type": "string", "enum": [ "MTOServiceItemBasic", "MTOServiceItemOriginSIT", "MTOServiceItemDestSIT", "MTOServiceItemShuttle", + "MTOServiceItemDomesticShuttle", "MTOServiceItemInternationalShuttle", "MTOServiceItemDomesticCrating", "MTOServiceItemInternationalCrating", @@ -9151,6 +9303,67 @@ func init() { }, "discriminator": "modelType" }, + "UpdateMTOServiceItemCrating": { + "description": "Subtype used to provide the size and types for crating. This is not creating a new service item but rather updating an existing service item.\n", + "allOf": [ + { + "$ref": "#/definitions/UpdateMTOServiceItem" + }, + { + "type": "object", + "properties": { + "crate": { + "description": "The dimensions for the crate the item will be shipped in.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItemDimension" + } + ] + }, + "description": { + "description": "A description of the item being crated.", + "type": "string", + "x-nullable": true, + "example": "Decorated horse head to be crated." + }, + "externalCrate": { + "type": "boolean", + "x-nullable": true + }, + "item": { + "description": "The dimensions of the item being crated.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItemDimension" + } + ] + }, + "reServiceCode": { + "description": "Service code allowed for this model type.", + "type": "string", + "enum": [ + "ICRT", + "IUCRT" + ] + }, + "requestApprovalsRequestedStatus": { + "description": "Indicates if \"Approvals Requested\" status is being requested.", + "type": "boolean", + "x-nullable": true + }, + "standaloneCrate": { + "type": "boolean", + "x-nullable": true + }, + "updateReason": { + "description": "Reason for updating service item.", + "type": "string", + "x-nullable": true + } + } + } + ] + }, "UpdateMTOServiceItemInternationalPortFSC": { "description": "Subtype used to provide the port for fuel surcharge. This is not creating a new service item but rather updating an existing service item.\n", "allOf": [ @@ -9220,13 +9433,14 @@ func init() { ] }, "UpdateMTOServiceItemModelType": { - "description": "Using this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DDDSIT - UpdateMTOServiceItemSIT\n * DDFSIT - UpdateMTOServiceItemSIT\n * DDASIT - UpdateMTOServiceItemSIT\n * DOPSIT - UpdateMTOServiceItemSIT\n * DOASIT - UpdateMTOServiceItemSIT\n * DOFSIT - UpdateMTOServiceItemSIT\n * DOSFSC - UpdateMTOServiceItemSIT\n * DDSFSC - UpdateMTOServiceItemSIT\n * DDSHUT - UpdateMTOServiceItemShuttle\n * DOSHUT - UpdateMTOServiceItemShuttle\n * PODFSC - UpdateMTOServiceItemInternationalPortFSC\n * POEFSC - UpdateMTOServiceItemInternationalPortFSC\n * IDSHUT - UpdateMTOServiceItemInternationalShuttle\n * IOSHUT - UpdateMTOServiceItemInternationalShuttle\n\nThe documentation will then update with the supported fields.\n", + "description": "Using this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DDDSIT - UpdateMTOServiceItemSIT\n * DDFSIT - UpdateMTOServiceItemSIT\n * DDASIT - UpdateMTOServiceItemSIT\n * DOPSIT - UpdateMTOServiceItemSIT\n * DOASIT - UpdateMTOServiceItemSIT\n * DOFSIT - UpdateMTOServiceItemSIT\n * DOSFSC - UpdateMTOServiceItemSIT\n * DDSFSC - UpdateMTOServiceItemSIT\n * DDSHUT - UpdateMTOServiceItemShuttle\n * DOSHUT - UpdateMTOServiceItemShuttle\n * PODFSC - UpdateMTOServiceItemInternationalPortFSC\n * POEFSC - UpdateMTOServiceItemInternationalPortFSC\n * IDSHUT - UpdateMTOServiceItemInternationalShuttle\n * IOSHUT - UpdateMTOServiceItemInternationalShuttle\n * ICRT - UpdateMTOServiceItemCrating\n * IUCRT - UpdateMTOServiceItemCrating\n\nThe documentation will then update with the supported fields.\n", "type": "string", "enum": [ "UpdateMTOServiceItemSIT", "UpdateMTOServiceItemShuttle", "UpdateMTOServiceItemInternationalPortFSC", - "UpdateMTOServiceItemInternationalShuttle" + "UpdateMTOServiceItemInternationalShuttle", + "UpdateMTOServiceItemCrating" ] }, "UpdateMTOServiceItemSIT": { diff --git a/pkg/gen/primeapi/primeoperations/mto_service_item/update_m_t_o_service_item.go b/pkg/gen/primeapi/primeoperations/mto_service_item/update_m_t_o_service_item.go index 8bfdb75c0f7..884e2637045 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 @@ -96,6 +96,31 @@ Please see the example payload below: "reServiceCode": "POEFSC" } +``` + +The following crating/uncrating service items can be resubmitted following a rejection: +- ICRT +- IUCRT + +At a MINIMUM, the payload for resubmitting a rejected crating/uncrating service item must look like this: +```json + + { + "item": { + "length": 10000, + "width": 10000, + "height": 10000 + }, + "crate": { + "length": 20000, + "width": 20000, + "height": 20000 + }, + "updateReason": "A reason that differs from the previous reason", + "modelType": "UpdateMTOServiceItemCrating", + "requestApprovalsRequestedStatus": true + } + ``` */ type UpdateMTOServiceItem struct { diff --git a/pkg/gen/primeclient/mto_service_item/mto_service_item_client.go b/pkg/gen/primeclient/mto_service_item/mto_service_item_client.go index 9fe2fa1212d..07040bad20e 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 @@ -279,6 +279,31 @@ Please see the example payload below: "reServiceCode": "POEFSC" } +``` + +The following crating/uncrating service items can be resubmitted following a rejection: +- ICRT +- IUCRT + +At a MINIMUM, the payload for resubmitting a rejected crating/uncrating service item must look like this: +```json + + { + "item": { + "length": 10000, + "width": 10000, + "height": 10000 + }, + "crate": { + "length": 20000, + "width": 20000, + "height": 20000 + }, + "updateReason": "A reason that differs from the previous reason", + "modelType": "UpdateMTOServiceItemCrating", + "requestApprovalsRequestedStatus": true + } + ``` */ func (a *Client) UpdateMTOServiceItem(params *UpdateMTOServiceItemParams, opts ...ClientOption) (*UpdateMTOServiceItemOK, error) { diff --git a/pkg/gen/primemessages/m_t_o_service_item.go b/pkg/gen/primemessages/m_t_o_service_item.go index 5ed0f248ae0..028e219d5df 100644 --- a/pkg/gen/primemessages/m_t_o_service_item.go +++ b/pkg/gen/primemessages/m_t_o_service_item.go @@ -273,6 +273,12 @@ func unmarshalMTOServiceItem(data []byte, consumer runtime.Consumer) (MTOService return nil, err } return &result, nil + case "MTOServiceItemDomesticShuttle": + var result MTOServiceItemDomesticShuttle + if err := consumer.Consume(buf2, &result); err != nil { + return nil, err + } + return &result, nil case "MTOServiceItemInternationalCrating": var result MTOServiceItemInternationalCrating if err := consumer.Consume(buf2, &result); err != nil { diff --git a/pkg/gen/primemessages/m_t_o_service_item_domestic_shuttle.go b/pkg/gen/primemessages/m_t_o_service_item_domestic_shuttle.go new file mode 100644 index 00000000000..1d5e28daaae --- /dev/null +++ b/pkg/gen/primemessages/m_t_o_service_item_domestic_shuttle.go @@ -0,0 +1,633 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package primemessages + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "bytes" + "context" + "encoding/json" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// MTOServiceItemDomesticShuttle Describes a domestic shuttle service item. +// +// swagger:model MTOServiceItemDomesticShuttle +type MTOServiceItemDomesticShuttle struct { + eTagField string + + idField strfmt.UUID + + lockedPriceCentsField *int64 + + moveTaskOrderIdField *strfmt.UUID + + mtoShipmentIdField strfmt.UUID + + reServiceNameField string + + rejectionReasonField *string + + serviceRequestDocumentsField ServiceRequestDocuments + + statusField MTOServiceItemStatus + + // A record of the actual weight that was shuttled. Provided by the movers, based on weight tickets. + // Example: 4000 + ActualWeight *int64 `json:"actualWeight"` + + // An estimate of how much weight from a shipment will be included in the shuttling service. + // Example: 4200 + EstimatedWeight *int64 `json:"estimatedWeight"` + + // A unique code for the service item. Indicates if shuttling is requested for the shipment origin (`DOSHUT`) or destination (`DDSHUT`). + // + // Required: true + // Enum: [DOSHUT DDSHUT] + ReServiceCode *string `json:"reServiceCode"` + + // The contractor's explanation for why a shuttle service is requested. Used by the TOO while deciding to approve or reject the service item. + // + // Example: Storage items need to be picked up. + // Required: true + Reason *string `json:"reason"` +} + +// ETag gets the e tag of this subtype +func (m *MTOServiceItemDomesticShuttle) ETag() string { + return m.eTagField +} + +// SetETag sets the e tag of this subtype +func (m *MTOServiceItemDomesticShuttle) SetETag(val string) { + m.eTagField = val +} + +// ID gets the id of this subtype +func (m *MTOServiceItemDomesticShuttle) ID() strfmt.UUID { + return m.idField +} + +// SetID sets the id of this subtype +func (m *MTOServiceItemDomesticShuttle) SetID(val strfmt.UUID) { + m.idField = val +} + +// LockedPriceCents gets the locked price cents of this subtype +func (m *MTOServiceItemDomesticShuttle) LockedPriceCents() *int64 { + return m.lockedPriceCentsField +} + +// SetLockedPriceCents sets the locked price cents of this subtype +func (m *MTOServiceItemDomesticShuttle) SetLockedPriceCents(val *int64) { + m.lockedPriceCentsField = val +} + +// ModelType gets the model type of this subtype +func (m *MTOServiceItemDomesticShuttle) ModelType() MTOServiceItemModelType { + return "MTOServiceItemDomesticShuttle" +} + +// SetModelType sets the model type of this subtype +func (m *MTOServiceItemDomesticShuttle) SetModelType(val MTOServiceItemModelType) { +} + +// MoveTaskOrderID gets the move task order ID of this subtype +func (m *MTOServiceItemDomesticShuttle) MoveTaskOrderID() *strfmt.UUID { + return m.moveTaskOrderIdField +} + +// SetMoveTaskOrderID sets the move task order ID of this subtype +func (m *MTOServiceItemDomesticShuttle) SetMoveTaskOrderID(val *strfmt.UUID) { + m.moveTaskOrderIdField = val +} + +// MtoShipmentID gets the mto shipment ID of this subtype +func (m *MTOServiceItemDomesticShuttle) MtoShipmentID() strfmt.UUID { + return m.mtoShipmentIdField +} + +// SetMtoShipmentID sets the mto shipment ID of this subtype +func (m *MTOServiceItemDomesticShuttle) SetMtoShipmentID(val strfmt.UUID) { + m.mtoShipmentIdField = val +} + +// ReServiceName gets the re service name of this subtype +func (m *MTOServiceItemDomesticShuttle) ReServiceName() string { + return m.reServiceNameField +} + +// SetReServiceName sets the re service name of this subtype +func (m *MTOServiceItemDomesticShuttle) SetReServiceName(val string) { + m.reServiceNameField = val +} + +// RejectionReason gets the rejection reason of this subtype +func (m *MTOServiceItemDomesticShuttle) RejectionReason() *string { + return m.rejectionReasonField +} + +// SetRejectionReason sets the rejection reason of this subtype +func (m *MTOServiceItemDomesticShuttle) SetRejectionReason(val *string) { + m.rejectionReasonField = val +} + +// ServiceRequestDocuments gets the service request documents of this subtype +func (m *MTOServiceItemDomesticShuttle) ServiceRequestDocuments() ServiceRequestDocuments { + return m.serviceRequestDocumentsField +} + +// SetServiceRequestDocuments sets the service request documents of this subtype +func (m *MTOServiceItemDomesticShuttle) SetServiceRequestDocuments(val ServiceRequestDocuments) { + m.serviceRequestDocumentsField = val +} + +// Status gets the status of this subtype +func (m *MTOServiceItemDomesticShuttle) Status() MTOServiceItemStatus { + return m.statusField +} + +// SetStatus sets the status of this subtype +func (m *MTOServiceItemDomesticShuttle) SetStatus(val MTOServiceItemStatus) { + m.statusField = val +} + +// UnmarshalJSON unmarshals this object with a polymorphic type from a JSON structure +func (m *MTOServiceItemDomesticShuttle) UnmarshalJSON(raw []byte) error { + var data struct { + + // A record of the actual weight that was shuttled. Provided by the movers, based on weight tickets. + // Example: 4000 + ActualWeight *int64 `json:"actualWeight"` + + // An estimate of how much weight from a shipment will be included in the shuttling service. + // Example: 4200 + EstimatedWeight *int64 `json:"estimatedWeight"` + + // A unique code for the service item. Indicates if shuttling is requested for the shipment origin (`DOSHUT`) or destination (`DDSHUT`). + // + // Required: true + // Enum: [DOSHUT DDSHUT] + ReServiceCode *string `json:"reServiceCode"` + + // The contractor's explanation for why a shuttle service is requested. Used by the TOO while deciding to approve or reject the service item. + // + // Example: Storage items need to be picked up. + // Required: true + Reason *string `json:"reason"` + } + buf := bytes.NewBuffer(raw) + dec := json.NewDecoder(buf) + dec.UseNumber() + + if err := dec.Decode(&data); err != nil { + return err + } + + var base struct { + /* Just the base type fields. Used for unmashalling polymorphic types.*/ + + ETag string `json:"eTag,omitempty"` + + ID strfmt.UUID `json:"id,omitempty"` + + LockedPriceCents *int64 `json:"lockedPriceCents,omitempty"` + + ModelType MTOServiceItemModelType `json:"modelType"` + + MoveTaskOrderID *strfmt.UUID `json:"moveTaskOrderID"` + + MtoShipmentID strfmt.UUID `json:"mtoShipmentID,omitempty"` + + ReServiceName string `json:"reServiceName,omitempty"` + + RejectionReason *string `json:"rejectionReason,omitempty"` + + ServiceRequestDocuments ServiceRequestDocuments `json:"serviceRequestDocuments,omitempty"` + + Status MTOServiceItemStatus `json:"status,omitempty"` + } + buf = bytes.NewBuffer(raw) + dec = json.NewDecoder(buf) + dec.UseNumber() + + if err := dec.Decode(&base); err != nil { + return err + } + + var result MTOServiceItemDomesticShuttle + + result.eTagField = base.ETag + + result.idField = base.ID + + result.lockedPriceCentsField = base.LockedPriceCents + + if base.ModelType != result.ModelType() { + /* Not the type we're looking for. */ + return errors.New(422, "invalid modelType value: %q", base.ModelType) + } + result.moveTaskOrderIdField = base.MoveTaskOrderID + + result.mtoShipmentIdField = base.MtoShipmentID + + result.reServiceNameField = base.ReServiceName + + result.rejectionReasonField = base.RejectionReason + + result.serviceRequestDocumentsField = base.ServiceRequestDocuments + + result.statusField = base.Status + + result.ActualWeight = data.ActualWeight + result.EstimatedWeight = data.EstimatedWeight + result.ReServiceCode = data.ReServiceCode + result.Reason = data.Reason + + *m = result + + return nil +} + +// MarshalJSON marshals this object with a polymorphic type to a JSON structure +func (m MTOServiceItemDomesticShuttle) MarshalJSON() ([]byte, error) { + var b1, b2, b3 []byte + var err error + b1, err = json.Marshal(struct { + + // A record of the actual weight that was shuttled. Provided by the movers, based on weight tickets. + // Example: 4000 + ActualWeight *int64 `json:"actualWeight"` + + // An estimate of how much weight from a shipment will be included in the shuttling service. + // Example: 4200 + EstimatedWeight *int64 `json:"estimatedWeight"` + + // A unique code for the service item. Indicates if shuttling is requested for the shipment origin (`DOSHUT`) or destination (`DDSHUT`). + // + // Required: true + // Enum: [DOSHUT DDSHUT] + ReServiceCode *string `json:"reServiceCode"` + + // The contractor's explanation for why a shuttle service is requested. Used by the TOO while deciding to approve or reject the service item. + // + // Example: Storage items need to be picked up. + // Required: true + Reason *string `json:"reason"` + }{ + + ActualWeight: m.ActualWeight, + + EstimatedWeight: m.EstimatedWeight, + + ReServiceCode: m.ReServiceCode, + + Reason: m.Reason, + }) + if err != nil { + return nil, err + } + b2, err = json.Marshal(struct { + ETag string `json:"eTag,omitempty"` + + ID strfmt.UUID `json:"id,omitempty"` + + LockedPriceCents *int64 `json:"lockedPriceCents,omitempty"` + + ModelType MTOServiceItemModelType `json:"modelType"` + + MoveTaskOrderID *strfmt.UUID `json:"moveTaskOrderID"` + + MtoShipmentID strfmt.UUID `json:"mtoShipmentID,omitempty"` + + ReServiceName string `json:"reServiceName,omitempty"` + + RejectionReason *string `json:"rejectionReason,omitempty"` + + ServiceRequestDocuments ServiceRequestDocuments `json:"serviceRequestDocuments,omitempty"` + + Status MTOServiceItemStatus `json:"status,omitempty"` + }{ + + ETag: m.ETag(), + + ID: m.ID(), + + LockedPriceCents: m.LockedPriceCents(), + + ModelType: m.ModelType(), + + MoveTaskOrderID: m.MoveTaskOrderID(), + + MtoShipmentID: m.MtoShipmentID(), + + ReServiceName: m.ReServiceName(), + + RejectionReason: m.RejectionReason(), + + ServiceRequestDocuments: m.ServiceRequestDocuments(), + + Status: m.Status(), + }) + if err != nil { + return nil, err + } + + return swag.ConcatJSON(b1, b2, b3), nil +} + +// Validate validates this m t o service item domestic shuttle +func (m *MTOServiceItemDomesticShuttle) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateID(formats); err != nil { + res = append(res, err) + } + + if err := m.validateMoveTaskOrderID(formats); err != nil { + res = append(res, err) + } + + if err := m.validateMtoShipmentID(formats); err != nil { + res = append(res, err) + } + + if err := m.validateServiceRequestDocuments(formats); err != nil { + res = append(res, err) + } + + if err := m.validateStatus(formats); err != nil { + res = append(res, err) + } + + if err := m.validateReServiceCode(formats); err != nil { + res = append(res, err) + } + + if err := m.validateReason(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *MTOServiceItemDomesticShuttle) validateID(formats strfmt.Registry) error { + + if swag.IsZero(m.ID()) { // not required + return nil + } + + if err := validate.FormatOf("id", "body", "uuid", m.ID().String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) validateMoveTaskOrderID(formats strfmt.Registry) error { + + if err := validate.Required("moveTaskOrderID", "body", m.MoveTaskOrderID()); err != nil { + return err + } + + if err := validate.FormatOf("moveTaskOrderID", "body", "uuid", m.MoveTaskOrderID().String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) validateMtoShipmentID(formats strfmt.Registry) error { + + if swag.IsZero(m.MtoShipmentID()) { // not required + return nil + } + + if err := validate.FormatOf("mtoShipmentID", "body", "uuid", m.MtoShipmentID().String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) validateServiceRequestDocuments(formats strfmt.Registry) error { + + if swag.IsZero(m.ServiceRequestDocuments()) { // not required + return nil + } + + if err := m.ServiceRequestDocuments().Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("serviceRequestDocuments") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("serviceRequestDocuments") + } + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) validateStatus(formats strfmt.Registry) error { + + if swag.IsZero(m.Status()) { // not required + return nil + } + + if err := m.Status().Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("status") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("status") + } + return err + } + + return nil +} + +var mTOServiceItemDomesticShuttleTypeReServiceCodePropEnum []interface{} + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["DOSHUT","DDSHUT"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + mTOServiceItemDomesticShuttleTypeReServiceCodePropEnum = append(mTOServiceItemDomesticShuttleTypeReServiceCodePropEnum, v) + } +} + +// property enum +func (m *MTOServiceItemDomesticShuttle) validateReServiceCodeEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, mTOServiceItemDomesticShuttleTypeReServiceCodePropEnum, true); err != nil { + return err + } + return nil +} + +func (m *MTOServiceItemDomesticShuttle) validateReServiceCode(formats strfmt.Registry) error { + + if err := validate.Required("reServiceCode", "body", m.ReServiceCode); err != nil { + return err + } + + // value enum + if err := m.validateReServiceCodeEnum("reServiceCode", "body", *m.ReServiceCode); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) validateReason(formats strfmt.Registry) error { + + if err := validate.Required("reason", "body", m.Reason); err != nil { + return err + } + + return nil +} + +// ContextValidate validate this m t o service item domestic shuttle based on the context it is used +func (m *MTOServiceItemDomesticShuttle) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateETag(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateID(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateReServiceName(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateRejectionReason(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateServiceRequestDocuments(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateStatus(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *MTOServiceItemDomesticShuttle) contextValidateETag(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "eTag", "body", string(m.ETag())); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) contextValidateID(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "id", "body", strfmt.UUID(m.ID())); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) contextValidateModelType(ctx context.Context, formats strfmt.Registry) error { + + if err := m.ModelType().ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("modelType") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("modelType") + } + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) contextValidateReServiceName(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "reServiceName", "body", string(m.ReServiceName())); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) contextValidateRejectionReason(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "rejectionReason", "body", m.RejectionReason()); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) contextValidateServiceRequestDocuments(ctx context.Context, formats strfmt.Registry) error { + + if err := m.ServiceRequestDocuments().ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("serviceRequestDocuments") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("serviceRequestDocuments") + } + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) contextValidateStatus(ctx context.Context, formats strfmt.Registry) error { + + if swag.IsZero(m.Status()) { // not required + return nil + } + + if err := m.Status().ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("status") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("status") + } + return err + } + + return nil +} + +// MarshalBinary interface implementation +func (m *MTOServiceItemDomesticShuttle) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *MTOServiceItemDomesticShuttle) UnmarshalBinary(b []byte) error { + var res MTOServiceItemDomesticShuttle + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/pkg/gen/primemessages/m_t_o_service_item_model_type.go b/pkg/gen/primemessages/m_t_o_service_item_model_type.go index 9326c1377a1..3c494ac62f3 100644 --- a/pkg/gen/primemessages/m_t_o_service_item_model_type.go +++ b/pkg/gen/primemessages/m_t_o_service_item_model_type.go @@ -20,6 +20,7 @@ import ( // - DOFSIT, DOASIT - MTOServiceItemOriginSIT // - DDFSIT, DDASIT - MTOServiceItemDestSIT // - DOSHUT, DDSHUT - MTOServiceItemShuttle +// - DOSHUT, DDSHUT - MTOServiceItemDomesticShuttle // - IOSHUT, IDSHUT - MTOServiceItemInternationalShuttle // - DCRT, DUCRT - MTOServiceItemDomesticCrating // - ICRT, IUCRT - MTOServiceItemInternationalCrating @@ -53,6 +54,9 @@ const ( // MTOServiceItemModelTypeMTOServiceItemShuttle captures enum value "MTOServiceItemShuttle" MTOServiceItemModelTypeMTOServiceItemShuttle MTOServiceItemModelType = "MTOServiceItemShuttle" + // MTOServiceItemModelTypeMTOServiceItemDomesticShuttle captures enum value "MTOServiceItemDomesticShuttle" + MTOServiceItemModelTypeMTOServiceItemDomesticShuttle MTOServiceItemModelType = "MTOServiceItemDomesticShuttle" + // MTOServiceItemModelTypeMTOServiceItemInternationalShuttle captures enum value "MTOServiceItemInternationalShuttle" MTOServiceItemModelTypeMTOServiceItemInternationalShuttle MTOServiceItemModelType = "MTOServiceItemInternationalShuttle" @@ -71,7 +75,7 @@ var mTOServiceItemModelTypeEnum []interface{} func init() { var res []MTOServiceItemModelType - if err := json.Unmarshal([]byte(`["MTOServiceItemBasic","MTOServiceItemOriginSIT","MTOServiceItemDestSIT","MTOServiceItemShuttle","MTOServiceItemInternationalShuttle","MTOServiceItemDomesticCrating","MTOServiceItemInternationalCrating","MTOSerivceItemInternationalFuelSurcharge"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["MTOServiceItemBasic","MTOServiceItemOriginSIT","MTOServiceItemDestSIT","MTOServiceItemShuttle","MTOServiceItemDomesticShuttle","MTOServiceItemInternationalShuttle","MTOServiceItemDomesticCrating","MTOServiceItemInternationalCrating","MTOSerivceItemInternationalFuelSurcharge"]`), &res); err != nil { panic(err) } for _, v := range res { diff --git a/pkg/gen/primemessages/update_m_t_o_service_item.go b/pkg/gen/primemessages/update_m_t_o_service_item.go index 36fc059bd0a..7a5abce67d9 100644 --- a/pkg/gen/primemessages/update_m_t_o_service_item.go +++ b/pkg/gen/primemessages/update_m_t_o_service_item.go @@ -117,6 +117,12 @@ func unmarshalUpdateMTOServiceItem(data []byte, consumer runtime.Consumer) (Upda return nil, err } return &result, nil + case "UpdateMTOServiceItemCrating": + var result UpdateMTOServiceItemCrating + if err := consumer.Consume(buf2, &result); err != nil { + return nil, err + } + return &result, nil case "UpdateMTOServiceItemInternationalPortFSC": var result UpdateMTOServiceItemInternationalPortFSC if err := consumer.Consume(buf2, &result); err != nil { diff --git a/pkg/gen/primemessages/update_m_t_o_service_item_crating.go b/pkg/gen/primemessages/update_m_t_o_service_item_crating.go new file mode 100644 index 00000000000..45396947c3a --- /dev/null +++ b/pkg/gen/primemessages/update_m_t_o_service_item_crating.go @@ -0,0 +1,377 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package primemessages + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "bytes" + "context" + "encoding/json" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// UpdateMTOServiceItemCrating Subtype used to provide the size and types for crating. This is not creating a new service item but rather updating an existing service item. +// +// swagger:model UpdateMTOServiceItemCrating +type UpdateMTOServiceItemCrating struct { + idField strfmt.UUID + + // The dimensions for the crate the item will be shipped in. + Crate struct { + MTOServiceItemDimension + } `json:"crate,omitempty"` + + // A description of the item being crated. + // Example: Decorated horse head to be crated. + Description *string `json:"description,omitempty"` + + // external crate + ExternalCrate *bool `json:"externalCrate,omitempty"` + + // The dimensions of the item being crated. + Item struct { + MTOServiceItemDimension + } `json:"item,omitempty"` + + // Service code allowed for this model type. + // Enum: [ICRT IUCRT] + ReServiceCode string `json:"reServiceCode,omitempty"` + + // Indicates if "Approvals Requested" status is being requested. + RequestApprovalsRequestedStatus *bool `json:"requestApprovalsRequestedStatus,omitempty"` + + // standalone crate + StandaloneCrate *bool `json:"standaloneCrate,omitempty"` + + // Reason for updating service item. + UpdateReason *string `json:"updateReason,omitempty"` +} + +// ID gets the id of this subtype +func (m *UpdateMTOServiceItemCrating) ID() strfmt.UUID { + return m.idField +} + +// SetID sets the id of this subtype +func (m *UpdateMTOServiceItemCrating) SetID(val strfmt.UUID) { + m.idField = val +} + +// ModelType gets the model type of this subtype +func (m *UpdateMTOServiceItemCrating) ModelType() UpdateMTOServiceItemModelType { + return "UpdateMTOServiceItemCrating" +} + +// SetModelType sets the model type of this subtype +func (m *UpdateMTOServiceItemCrating) SetModelType(val UpdateMTOServiceItemModelType) { +} + +// UnmarshalJSON unmarshals this object with a polymorphic type from a JSON structure +func (m *UpdateMTOServiceItemCrating) UnmarshalJSON(raw []byte) error { + var data struct { + + // The dimensions for the crate the item will be shipped in. + Crate struct { + MTOServiceItemDimension + } `json:"crate,omitempty"` + + // A description of the item being crated. + // Example: Decorated horse head to be crated. + Description *string `json:"description,omitempty"` + + // external crate + ExternalCrate *bool `json:"externalCrate,omitempty"` + + // The dimensions of the item being crated. + Item struct { + MTOServiceItemDimension + } `json:"item,omitempty"` + + // Service code allowed for this model type. + // Enum: [ICRT IUCRT] + ReServiceCode string `json:"reServiceCode,omitempty"` + + // Indicates if "Approvals Requested" status is being requested. + RequestApprovalsRequestedStatus *bool `json:"requestApprovalsRequestedStatus,omitempty"` + + // standalone crate + StandaloneCrate *bool `json:"standaloneCrate,omitempty"` + + // Reason for updating service item. + UpdateReason *string `json:"updateReason,omitempty"` + } + buf := bytes.NewBuffer(raw) + dec := json.NewDecoder(buf) + dec.UseNumber() + + if err := dec.Decode(&data); err != nil { + return err + } + + var base struct { + /* Just the base type fields. Used for unmashalling polymorphic types.*/ + + ID strfmt.UUID `json:"id,omitempty"` + + ModelType UpdateMTOServiceItemModelType `json:"modelType"` + } + buf = bytes.NewBuffer(raw) + dec = json.NewDecoder(buf) + dec.UseNumber() + + if err := dec.Decode(&base); err != nil { + return err + } + + var result UpdateMTOServiceItemCrating + + result.idField = base.ID + + if base.ModelType != result.ModelType() { + /* Not the type we're looking for. */ + return errors.New(422, "invalid modelType value: %q", base.ModelType) + } + + result.Crate = data.Crate + result.Description = data.Description + result.ExternalCrate = data.ExternalCrate + result.Item = data.Item + result.ReServiceCode = data.ReServiceCode + result.RequestApprovalsRequestedStatus = data.RequestApprovalsRequestedStatus + result.StandaloneCrate = data.StandaloneCrate + result.UpdateReason = data.UpdateReason + + *m = result + + return nil +} + +// MarshalJSON marshals this object with a polymorphic type to a JSON structure +func (m UpdateMTOServiceItemCrating) MarshalJSON() ([]byte, error) { + var b1, b2, b3 []byte + var err error + b1, err = json.Marshal(struct { + + // The dimensions for the crate the item will be shipped in. + Crate struct { + MTOServiceItemDimension + } `json:"crate,omitempty"` + + // A description of the item being crated. + // Example: Decorated horse head to be crated. + Description *string `json:"description,omitempty"` + + // external crate + ExternalCrate *bool `json:"externalCrate,omitempty"` + + // The dimensions of the item being crated. + Item struct { + MTOServiceItemDimension + } `json:"item,omitempty"` + + // Service code allowed for this model type. + // Enum: [ICRT IUCRT] + ReServiceCode string `json:"reServiceCode,omitempty"` + + // Indicates if "Approvals Requested" status is being requested. + RequestApprovalsRequestedStatus *bool `json:"requestApprovalsRequestedStatus,omitempty"` + + // standalone crate + StandaloneCrate *bool `json:"standaloneCrate,omitempty"` + + // Reason for updating service item. + UpdateReason *string `json:"updateReason,omitempty"` + }{ + + Crate: m.Crate, + + Description: m.Description, + + ExternalCrate: m.ExternalCrate, + + Item: m.Item, + + ReServiceCode: m.ReServiceCode, + + RequestApprovalsRequestedStatus: m.RequestApprovalsRequestedStatus, + + StandaloneCrate: m.StandaloneCrate, + + UpdateReason: m.UpdateReason, + }) + if err != nil { + return nil, err + } + b2, err = json.Marshal(struct { + ID strfmt.UUID `json:"id,omitempty"` + + ModelType UpdateMTOServiceItemModelType `json:"modelType"` + }{ + + ID: m.ID(), + + ModelType: m.ModelType(), + }) + if err != nil { + return nil, err + } + + return swag.ConcatJSON(b1, b2, b3), nil +} + +// Validate validates this update m t o service item crating +func (m *UpdateMTOServiceItemCrating) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateID(formats); err != nil { + res = append(res, err) + } + + if err := m.validateCrate(formats); err != nil { + res = append(res, err) + } + + if err := m.validateItem(formats); err != nil { + res = append(res, err) + } + + if err := m.validateReServiceCode(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *UpdateMTOServiceItemCrating) validateID(formats strfmt.Registry) error { + + if swag.IsZero(m.ID()) { // not required + return nil + } + + if err := validate.FormatOf("id", "body", "uuid", m.ID().String(), formats); err != nil { + return err + } + + return nil +} + +func (m *UpdateMTOServiceItemCrating) validateCrate(formats strfmt.Registry) error { + + if swag.IsZero(m.Crate) { // not required + return nil + } + + return nil +} + +func (m *UpdateMTOServiceItemCrating) validateItem(formats strfmt.Registry) error { + + if swag.IsZero(m.Item) { // not required + return nil + } + + return nil +} + +var updateMTOServiceItemCratingTypeReServiceCodePropEnum []interface{} + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["ICRT","IUCRT"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + updateMTOServiceItemCratingTypeReServiceCodePropEnum = append(updateMTOServiceItemCratingTypeReServiceCodePropEnum, v) + } +} + +// property enum +func (m *UpdateMTOServiceItemCrating) validateReServiceCodeEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, updateMTOServiceItemCratingTypeReServiceCodePropEnum, true); err != nil { + return err + } + return nil +} + +func (m *UpdateMTOServiceItemCrating) validateReServiceCode(formats strfmt.Registry) error { + + if swag.IsZero(m.ReServiceCode) { // not required + return nil + } + + // value enum + if err := m.validateReServiceCodeEnum("reServiceCode", "body", m.ReServiceCode); err != nil { + return err + } + + return nil +} + +// ContextValidate validate this update m t o service item crating based on the context it is used +func (m *UpdateMTOServiceItemCrating) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateCrate(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateItem(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *UpdateMTOServiceItemCrating) contextValidateModelType(ctx context.Context, formats strfmt.Registry) error { + + if err := m.ModelType().ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("modelType") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("modelType") + } + return err + } + + return nil +} + +func (m *UpdateMTOServiceItemCrating) contextValidateCrate(ctx context.Context, formats strfmt.Registry) error { + + return nil +} + +func (m *UpdateMTOServiceItemCrating) contextValidateItem(ctx context.Context, formats strfmt.Registry) error { + + return nil +} + +// MarshalBinary interface implementation +func (m *UpdateMTOServiceItemCrating) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *UpdateMTOServiceItemCrating) UnmarshalBinary(b []byte) error { + var res UpdateMTOServiceItemCrating + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/pkg/gen/primemessages/update_m_t_o_service_item_model_type.go b/pkg/gen/primemessages/update_m_t_o_service_item_model_type.go index cc59a427e36..24f9aa5707e 100644 --- a/pkg/gen/primemessages/update_m_t_o_service_item_model_type.go +++ b/pkg/gen/primemessages/update_m_t_o_service_item_model_type.go @@ -29,6 +29,8 @@ import ( // - POEFSC - UpdateMTOServiceItemInternationalPortFSC // - IDSHUT - UpdateMTOServiceItemInternationalShuttle // - IOSHUT - UpdateMTOServiceItemInternationalShuttle +// - ICRT - UpdateMTOServiceItemCrating +// - IUCRT - UpdateMTOServiceItemCrating // // The documentation will then update with the supported fields. // @@ -57,6 +59,9 @@ const ( // UpdateMTOServiceItemModelTypeUpdateMTOServiceItemInternationalShuttle captures enum value "UpdateMTOServiceItemInternationalShuttle" UpdateMTOServiceItemModelTypeUpdateMTOServiceItemInternationalShuttle UpdateMTOServiceItemModelType = "UpdateMTOServiceItemInternationalShuttle" + + // UpdateMTOServiceItemModelTypeUpdateMTOServiceItemCrating captures enum value "UpdateMTOServiceItemCrating" + UpdateMTOServiceItemModelTypeUpdateMTOServiceItemCrating UpdateMTOServiceItemModelType = "UpdateMTOServiceItemCrating" ) // for schema @@ -64,7 +69,7 @@ var updateMTOServiceItemModelTypeEnum []interface{} func init() { var res []UpdateMTOServiceItemModelType - if err := json.Unmarshal([]byte(`["UpdateMTOServiceItemSIT","UpdateMTOServiceItemShuttle","UpdateMTOServiceItemInternationalPortFSC","UpdateMTOServiceItemInternationalShuttle"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["UpdateMTOServiceItemSIT","UpdateMTOServiceItemShuttle","UpdateMTOServiceItemInternationalPortFSC","UpdateMTOServiceItemInternationalShuttle","UpdateMTOServiceItemCrating"]`), &res); err != nil { panic(err) } for _, v := range res { diff --git a/pkg/gen/primev2api/embedded_spec.go b/pkg/gen/primev2api/embedded_spec.go index 9e0f66eb6c8..8a15212de76 100644 --- a/pkg/gen/primev2api/embedded_spec.go +++ b/pkg/gen/primev2api/embedded_spec.go @@ -1435,6 +1435,50 @@ func init() { } ] }, + "MTOServiceItemDomesticShuttle": { + "description": "Describes a domestic shuttle service item.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItem" + }, + { + "type": "object", + "required": [ + "reason", + "reServiceCode" + ], + "properties": { + "actualWeight": { + "description": "A record of the actual weight that was shuttled. Provided by the movers, based on weight tickets.", + "type": "integer", + "x-nullable": true, + "x-omitempty": false, + "example": 4000 + }, + "estimatedWeight": { + "description": "An estimate of how much weight from a shipment will be included in the shuttling service.", + "type": "integer", + "x-nullable": true, + "x-omitempty": false, + "example": 4200 + }, + "reServiceCode": { + "description": "A unique code for the service item. Indicates if shuttling is requested for the shipment origin (` + "`" + `DOSHUT` + "`" + `) or destination (` + "`" + `DDSHUT` + "`" + `).\n", + "type": "string", + "enum": [ + "DOSHUT", + "DDSHUT" + ] + }, + "reason": { + "description": "The contractor's explanation for why a shuttle service is requested. Used by the TOO while deciding to approve or reject the service item.\n", + "type": "string", + "example": "Storage items need to be picked up." + } + } + } + ] + }, "MTOServiceItemInternationalCrating": { "description": "Describes a international crating/uncrating service item subtype of a MTOServiceItem.", "allOf": [ @@ -1566,13 +1610,14 @@ func init() { ] }, "MTOServiceItemModelType": { - "description": "Describes all model sub-types for a MTOServiceItem model.\n\nUsing this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DOFSIT, DOASIT - MTOServiceItemOriginSIT\n * DDFSIT, DDASIT - MTOServiceItemDestSIT\n * DOSHUT, DDSHUT - MTOServiceItemShuttle\n * IOSHUT, IDSHUT - MTOServiceItemInternationalShuttle\n * DCRT, DUCRT - MTOServiceItemDomesticCrating\n * ICRT, IUCRT - MTOServiceItemInternationalCrating\n * PODFSC, POEFSC - MTOSerivceItemInternationalFuelSurcharge\n\nThe documentation will then update with the supported fields.\n", + "description": "Describes all model sub-types for a MTOServiceItem model.\n\nUsing this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DOFSIT, DOASIT - MTOServiceItemOriginSIT\n * DDFSIT, DDASIT - MTOServiceItemDestSIT\n * DOSHUT, DDSHUT - MTOServiceItemShuttle\n * DOSHUT, DDSHUT - MTOServiceItemDomesticShuttle\n * IOSHUT, IDSHUT - MTOServiceItemInternationalShuttle\n * DCRT, DUCRT - MTOServiceItemDomesticCrating\n * ICRT, IUCRT - MTOServiceItemInternationalCrating\n * PODFSC, POEFSC - MTOSerivceItemInternationalFuelSurcharge\n\nThe documentation will then update with the supported fields.\n", "type": "string", "enum": [ "MTOServiceItemBasic", "MTOServiceItemOriginSIT", "MTOServiceItemDestSIT", "MTOServiceItemShuttle", + "MTOServiceItemDomesticShuttle", "MTOServiceItemInternationalShuttle", "MTOServiceItemDomesticCrating", "MTOServiceItemInternationalCrating", @@ -5199,6 +5244,50 @@ func init() { } ] }, + "MTOServiceItemDomesticShuttle": { + "description": "Describes a domestic shuttle service item.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItem" + }, + { + "type": "object", + "required": [ + "reason", + "reServiceCode" + ], + "properties": { + "actualWeight": { + "description": "A record of the actual weight that was shuttled. Provided by the movers, based on weight tickets.", + "type": "integer", + "x-nullable": true, + "x-omitempty": false, + "example": 4000 + }, + "estimatedWeight": { + "description": "An estimate of how much weight from a shipment will be included in the shuttling service.", + "type": "integer", + "x-nullable": true, + "x-omitempty": false, + "example": 4200 + }, + "reServiceCode": { + "description": "A unique code for the service item. Indicates if shuttling is requested for the shipment origin (` + "`" + `DOSHUT` + "`" + `) or destination (` + "`" + `DDSHUT` + "`" + `).\n", + "type": "string", + "enum": [ + "DOSHUT", + "DDSHUT" + ] + }, + "reason": { + "description": "The contractor's explanation for why a shuttle service is requested. Used by the TOO while deciding to approve or reject the service item.\n", + "type": "string", + "example": "Storage items need to be picked up." + } + } + } + ] + }, "MTOServiceItemInternationalCrating": { "description": "Describes a international crating/uncrating service item subtype of a MTOServiceItem.", "allOf": [ @@ -5330,13 +5419,14 @@ func init() { ] }, "MTOServiceItemModelType": { - "description": "Describes all model sub-types for a MTOServiceItem model.\n\nUsing this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DOFSIT, DOASIT - MTOServiceItemOriginSIT\n * DDFSIT, DDASIT - MTOServiceItemDestSIT\n * DOSHUT, DDSHUT - MTOServiceItemShuttle\n * IOSHUT, IDSHUT - MTOServiceItemInternationalShuttle\n * DCRT, DUCRT - MTOServiceItemDomesticCrating\n * ICRT, IUCRT - MTOServiceItemInternationalCrating\n * PODFSC, POEFSC - MTOSerivceItemInternationalFuelSurcharge\n\nThe documentation will then update with the supported fields.\n", + "description": "Describes all model sub-types for a MTOServiceItem model.\n\nUsing this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DOFSIT, DOASIT - MTOServiceItemOriginSIT\n * DDFSIT, DDASIT - MTOServiceItemDestSIT\n * DOSHUT, DDSHUT - MTOServiceItemShuttle\n * DOSHUT, DDSHUT - MTOServiceItemDomesticShuttle\n * IOSHUT, IDSHUT - MTOServiceItemInternationalShuttle\n * DCRT, DUCRT - MTOServiceItemDomesticCrating\n * ICRT, IUCRT - MTOServiceItemInternationalCrating\n * PODFSC, POEFSC - MTOSerivceItemInternationalFuelSurcharge\n\nThe documentation will then update with the supported fields.\n", "type": "string", "enum": [ "MTOServiceItemBasic", "MTOServiceItemOriginSIT", "MTOServiceItemDestSIT", "MTOServiceItemShuttle", + "MTOServiceItemDomesticShuttle", "MTOServiceItemInternationalShuttle", "MTOServiceItemDomesticCrating", "MTOServiceItemInternationalCrating", diff --git a/pkg/gen/primev2messages/m_t_o_service_item.go b/pkg/gen/primev2messages/m_t_o_service_item.go index 7dfadf4c428..c06f87e420c 100644 --- a/pkg/gen/primev2messages/m_t_o_service_item.go +++ b/pkg/gen/primev2messages/m_t_o_service_item.go @@ -273,6 +273,12 @@ func unmarshalMTOServiceItem(data []byte, consumer runtime.Consumer) (MTOService return nil, err } return &result, nil + case "MTOServiceItemDomesticShuttle": + var result MTOServiceItemDomesticShuttle + if err := consumer.Consume(buf2, &result); err != nil { + return nil, err + } + return &result, nil case "MTOServiceItemInternationalCrating": var result MTOServiceItemInternationalCrating if err := consumer.Consume(buf2, &result); err != nil { diff --git a/pkg/gen/primev2messages/m_t_o_service_item_domestic_shuttle.go b/pkg/gen/primev2messages/m_t_o_service_item_domestic_shuttle.go new file mode 100644 index 00000000000..c5767d8bddf --- /dev/null +++ b/pkg/gen/primev2messages/m_t_o_service_item_domestic_shuttle.go @@ -0,0 +1,633 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package primev2messages + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "bytes" + "context" + "encoding/json" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// MTOServiceItemDomesticShuttle Describes a domestic shuttle service item. +// +// swagger:model MTOServiceItemDomesticShuttle +type MTOServiceItemDomesticShuttle struct { + eTagField string + + idField strfmt.UUID + + lockedPriceCentsField *int64 + + moveTaskOrderIdField *strfmt.UUID + + mtoShipmentIdField strfmt.UUID + + reServiceNameField string + + rejectionReasonField *string + + serviceRequestDocumentsField ServiceRequestDocuments + + statusField MTOServiceItemStatus + + // A record of the actual weight that was shuttled. Provided by the movers, based on weight tickets. + // Example: 4000 + ActualWeight *int64 `json:"actualWeight"` + + // An estimate of how much weight from a shipment will be included in the shuttling service. + // Example: 4200 + EstimatedWeight *int64 `json:"estimatedWeight"` + + // A unique code for the service item. Indicates if shuttling is requested for the shipment origin (`DOSHUT`) or destination (`DDSHUT`). + // + // Required: true + // Enum: [DOSHUT DDSHUT] + ReServiceCode *string `json:"reServiceCode"` + + // The contractor's explanation for why a shuttle service is requested. Used by the TOO while deciding to approve or reject the service item. + // + // Example: Storage items need to be picked up. + // Required: true + Reason *string `json:"reason"` +} + +// ETag gets the e tag of this subtype +func (m *MTOServiceItemDomesticShuttle) ETag() string { + return m.eTagField +} + +// SetETag sets the e tag of this subtype +func (m *MTOServiceItemDomesticShuttle) SetETag(val string) { + m.eTagField = val +} + +// ID gets the id of this subtype +func (m *MTOServiceItemDomesticShuttle) ID() strfmt.UUID { + return m.idField +} + +// SetID sets the id of this subtype +func (m *MTOServiceItemDomesticShuttle) SetID(val strfmt.UUID) { + m.idField = val +} + +// LockedPriceCents gets the locked price cents of this subtype +func (m *MTOServiceItemDomesticShuttle) LockedPriceCents() *int64 { + return m.lockedPriceCentsField +} + +// SetLockedPriceCents sets the locked price cents of this subtype +func (m *MTOServiceItemDomesticShuttle) SetLockedPriceCents(val *int64) { + m.lockedPriceCentsField = val +} + +// ModelType gets the model type of this subtype +func (m *MTOServiceItemDomesticShuttle) ModelType() MTOServiceItemModelType { + return "MTOServiceItemDomesticShuttle" +} + +// SetModelType sets the model type of this subtype +func (m *MTOServiceItemDomesticShuttle) SetModelType(val MTOServiceItemModelType) { +} + +// MoveTaskOrderID gets the move task order ID of this subtype +func (m *MTOServiceItemDomesticShuttle) MoveTaskOrderID() *strfmt.UUID { + return m.moveTaskOrderIdField +} + +// SetMoveTaskOrderID sets the move task order ID of this subtype +func (m *MTOServiceItemDomesticShuttle) SetMoveTaskOrderID(val *strfmt.UUID) { + m.moveTaskOrderIdField = val +} + +// MtoShipmentID gets the mto shipment ID of this subtype +func (m *MTOServiceItemDomesticShuttle) MtoShipmentID() strfmt.UUID { + return m.mtoShipmentIdField +} + +// SetMtoShipmentID sets the mto shipment ID of this subtype +func (m *MTOServiceItemDomesticShuttle) SetMtoShipmentID(val strfmt.UUID) { + m.mtoShipmentIdField = val +} + +// ReServiceName gets the re service name of this subtype +func (m *MTOServiceItemDomesticShuttle) ReServiceName() string { + return m.reServiceNameField +} + +// SetReServiceName sets the re service name of this subtype +func (m *MTOServiceItemDomesticShuttle) SetReServiceName(val string) { + m.reServiceNameField = val +} + +// RejectionReason gets the rejection reason of this subtype +func (m *MTOServiceItemDomesticShuttle) RejectionReason() *string { + return m.rejectionReasonField +} + +// SetRejectionReason sets the rejection reason of this subtype +func (m *MTOServiceItemDomesticShuttle) SetRejectionReason(val *string) { + m.rejectionReasonField = val +} + +// ServiceRequestDocuments gets the service request documents of this subtype +func (m *MTOServiceItemDomesticShuttle) ServiceRequestDocuments() ServiceRequestDocuments { + return m.serviceRequestDocumentsField +} + +// SetServiceRequestDocuments sets the service request documents of this subtype +func (m *MTOServiceItemDomesticShuttle) SetServiceRequestDocuments(val ServiceRequestDocuments) { + m.serviceRequestDocumentsField = val +} + +// Status gets the status of this subtype +func (m *MTOServiceItemDomesticShuttle) Status() MTOServiceItemStatus { + return m.statusField +} + +// SetStatus sets the status of this subtype +func (m *MTOServiceItemDomesticShuttle) SetStatus(val MTOServiceItemStatus) { + m.statusField = val +} + +// UnmarshalJSON unmarshals this object with a polymorphic type from a JSON structure +func (m *MTOServiceItemDomesticShuttle) UnmarshalJSON(raw []byte) error { + var data struct { + + // A record of the actual weight that was shuttled. Provided by the movers, based on weight tickets. + // Example: 4000 + ActualWeight *int64 `json:"actualWeight"` + + // An estimate of how much weight from a shipment will be included in the shuttling service. + // Example: 4200 + EstimatedWeight *int64 `json:"estimatedWeight"` + + // A unique code for the service item. Indicates if shuttling is requested for the shipment origin (`DOSHUT`) or destination (`DDSHUT`). + // + // Required: true + // Enum: [DOSHUT DDSHUT] + ReServiceCode *string `json:"reServiceCode"` + + // The contractor's explanation for why a shuttle service is requested. Used by the TOO while deciding to approve or reject the service item. + // + // Example: Storage items need to be picked up. + // Required: true + Reason *string `json:"reason"` + } + buf := bytes.NewBuffer(raw) + dec := json.NewDecoder(buf) + dec.UseNumber() + + if err := dec.Decode(&data); err != nil { + return err + } + + var base struct { + /* Just the base type fields. Used for unmashalling polymorphic types.*/ + + ETag string `json:"eTag,omitempty"` + + ID strfmt.UUID `json:"id,omitempty"` + + LockedPriceCents *int64 `json:"lockedPriceCents,omitempty"` + + ModelType MTOServiceItemModelType `json:"modelType"` + + MoveTaskOrderID *strfmt.UUID `json:"moveTaskOrderID"` + + MtoShipmentID strfmt.UUID `json:"mtoShipmentID,omitempty"` + + ReServiceName string `json:"reServiceName,omitempty"` + + RejectionReason *string `json:"rejectionReason,omitempty"` + + ServiceRequestDocuments ServiceRequestDocuments `json:"serviceRequestDocuments,omitempty"` + + Status MTOServiceItemStatus `json:"status,omitempty"` + } + buf = bytes.NewBuffer(raw) + dec = json.NewDecoder(buf) + dec.UseNumber() + + if err := dec.Decode(&base); err != nil { + return err + } + + var result MTOServiceItemDomesticShuttle + + result.eTagField = base.ETag + + result.idField = base.ID + + result.lockedPriceCentsField = base.LockedPriceCents + + if base.ModelType != result.ModelType() { + /* Not the type we're looking for. */ + return errors.New(422, "invalid modelType value: %q", base.ModelType) + } + result.moveTaskOrderIdField = base.MoveTaskOrderID + + result.mtoShipmentIdField = base.MtoShipmentID + + result.reServiceNameField = base.ReServiceName + + result.rejectionReasonField = base.RejectionReason + + result.serviceRequestDocumentsField = base.ServiceRequestDocuments + + result.statusField = base.Status + + result.ActualWeight = data.ActualWeight + result.EstimatedWeight = data.EstimatedWeight + result.ReServiceCode = data.ReServiceCode + result.Reason = data.Reason + + *m = result + + return nil +} + +// MarshalJSON marshals this object with a polymorphic type to a JSON structure +func (m MTOServiceItemDomesticShuttle) MarshalJSON() ([]byte, error) { + var b1, b2, b3 []byte + var err error + b1, err = json.Marshal(struct { + + // A record of the actual weight that was shuttled. Provided by the movers, based on weight tickets. + // Example: 4000 + ActualWeight *int64 `json:"actualWeight"` + + // An estimate of how much weight from a shipment will be included in the shuttling service. + // Example: 4200 + EstimatedWeight *int64 `json:"estimatedWeight"` + + // A unique code for the service item. Indicates if shuttling is requested for the shipment origin (`DOSHUT`) or destination (`DDSHUT`). + // + // Required: true + // Enum: [DOSHUT DDSHUT] + ReServiceCode *string `json:"reServiceCode"` + + // The contractor's explanation for why a shuttle service is requested. Used by the TOO while deciding to approve or reject the service item. + // + // Example: Storage items need to be picked up. + // Required: true + Reason *string `json:"reason"` + }{ + + ActualWeight: m.ActualWeight, + + EstimatedWeight: m.EstimatedWeight, + + ReServiceCode: m.ReServiceCode, + + Reason: m.Reason, + }) + if err != nil { + return nil, err + } + b2, err = json.Marshal(struct { + ETag string `json:"eTag,omitempty"` + + ID strfmt.UUID `json:"id,omitempty"` + + LockedPriceCents *int64 `json:"lockedPriceCents,omitempty"` + + ModelType MTOServiceItemModelType `json:"modelType"` + + MoveTaskOrderID *strfmt.UUID `json:"moveTaskOrderID"` + + MtoShipmentID strfmt.UUID `json:"mtoShipmentID,omitempty"` + + ReServiceName string `json:"reServiceName,omitempty"` + + RejectionReason *string `json:"rejectionReason,omitempty"` + + ServiceRequestDocuments ServiceRequestDocuments `json:"serviceRequestDocuments,omitempty"` + + Status MTOServiceItemStatus `json:"status,omitempty"` + }{ + + ETag: m.ETag(), + + ID: m.ID(), + + LockedPriceCents: m.LockedPriceCents(), + + ModelType: m.ModelType(), + + MoveTaskOrderID: m.MoveTaskOrderID(), + + MtoShipmentID: m.MtoShipmentID(), + + ReServiceName: m.ReServiceName(), + + RejectionReason: m.RejectionReason(), + + ServiceRequestDocuments: m.ServiceRequestDocuments(), + + Status: m.Status(), + }) + if err != nil { + return nil, err + } + + return swag.ConcatJSON(b1, b2, b3), nil +} + +// Validate validates this m t o service item domestic shuttle +func (m *MTOServiceItemDomesticShuttle) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateID(formats); err != nil { + res = append(res, err) + } + + if err := m.validateMoveTaskOrderID(formats); err != nil { + res = append(res, err) + } + + if err := m.validateMtoShipmentID(formats); err != nil { + res = append(res, err) + } + + if err := m.validateServiceRequestDocuments(formats); err != nil { + res = append(res, err) + } + + if err := m.validateStatus(formats); err != nil { + res = append(res, err) + } + + if err := m.validateReServiceCode(formats); err != nil { + res = append(res, err) + } + + if err := m.validateReason(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *MTOServiceItemDomesticShuttle) validateID(formats strfmt.Registry) error { + + if swag.IsZero(m.ID()) { // not required + return nil + } + + if err := validate.FormatOf("id", "body", "uuid", m.ID().String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) validateMoveTaskOrderID(formats strfmt.Registry) error { + + if err := validate.Required("moveTaskOrderID", "body", m.MoveTaskOrderID()); err != nil { + return err + } + + if err := validate.FormatOf("moveTaskOrderID", "body", "uuid", m.MoveTaskOrderID().String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) validateMtoShipmentID(formats strfmt.Registry) error { + + if swag.IsZero(m.MtoShipmentID()) { // not required + return nil + } + + if err := validate.FormatOf("mtoShipmentID", "body", "uuid", m.MtoShipmentID().String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) validateServiceRequestDocuments(formats strfmt.Registry) error { + + if swag.IsZero(m.ServiceRequestDocuments()) { // not required + return nil + } + + if err := m.ServiceRequestDocuments().Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("serviceRequestDocuments") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("serviceRequestDocuments") + } + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) validateStatus(formats strfmt.Registry) error { + + if swag.IsZero(m.Status()) { // not required + return nil + } + + if err := m.Status().Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("status") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("status") + } + return err + } + + return nil +} + +var mTOServiceItemDomesticShuttleTypeReServiceCodePropEnum []interface{} + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["DOSHUT","DDSHUT"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + mTOServiceItemDomesticShuttleTypeReServiceCodePropEnum = append(mTOServiceItemDomesticShuttleTypeReServiceCodePropEnum, v) + } +} + +// property enum +func (m *MTOServiceItemDomesticShuttle) validateReServiceCodeEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, mTOServiceItemDomesticShuttleTypeReServiceCodePropEnum, true); err != nil { + return err + } + return nil +} + +func (m *MTOServiceItemDomesticShuttle) validateReServiceCode(formats strfmt.Registry) error { + + if err := validate.Required("reServiceCode", "body", m.ReServiceCode); err != nil { + return err + } + + // value enum + if err := m.validateReServiceCodeEnum("reServiceCode", "body", *m.ReServiceCode); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) validateReason(formats strfmt.Registry) error { + + if err := validate.Required("reason", "body", m.Reason); err != nil { + return err + } + + return nil +} + +// ContextValidate validate this m t o service item domestic shuttle based on the context it is used +func (m *MTOServiceItemDomesticShuttle) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateETag(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateID(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateReServiceName(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateRejectionReason(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateServiceRequestDocuments(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateStatus(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *MTOServiceItemDomesticShuttle) contextValidateETag(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "eTag", "body", string(m.ETag())); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) contextValidateID(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "id", "body", strfmt.UUID(m.ID())); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) contextValidateModelType(ctx context.Context, formats strfmt.Registry) error { + + if err := m.ModelType().ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("modelType") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("modelType") + } + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) contextValidateReServiceName(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "reServiceName", "body", string(m.ReServiceName())); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) contextValidateRejectionReason(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "rejectionReason", "body", m.RejectionReason()); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) contextValidateServiceRequestDocuments(ctx context.Context, formats strfmt.Registry) error { + + if err := m.ServiceRequestDocuments().ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("serviceRequestDocuments") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("serviceRequestDocuments") + } + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) contextValidateStatus(ctx context.Context, formats strfmt.Registry) error { + + if swag.IsZero(m.Status()) { // not required + return nil + } + + if err := m.Status().ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("status") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("status") + } + return err + } + + return nil +} + +// MarshalBinary interface implementation +func (m *MTOServiceItemDomesticShuttle) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *MTOServiceItemDomesticShuttle) UnmarshalBinary(b []byte) error { + var res MTOServiceItemDomesticShuttle + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/pkg/gen/primev2messages/m_t_o_service_item_model_type.go b/pkg/gen/primev2messages/m_t_o_service_item_model_type.go index 97d0c5272dc..77247b78fe0 100644 --- a/pkg/gen/primev2messages/m_t_o_service_item_model_type.go +++ b/pkg/gen/primev2messages/m_t_o_service_item_model_type.go @@ -20,6 +20,7 @@ import ( // - DOFSIT, DOASIT - MTOServiceItemOriginSIT // - DDFSIT, DDASIT - MTOServiceItemDestSIT // - DOSHUT, DDSHUT - MTOServiceItemShuttle +// - DOSHUT, DDSHUT - MTOServiceItemDomesticShuttle // - IOSHUT, IDSHUT - MTOServiceItemInternationalShuttle // - DCRT, DUCRT - MTOServiceItemDomesticCrating // - ICRT, IUCRT - MTOServiceItemInternationalCrating @@ -53,6 +54,9 @@ const ( // MTOServiceItemModelTypeMTOServiceItemShuttle captures enum value "MTOServiceItemShuttle" MTOServiceItemModelTypeMTOServiceItemShuttle MTOServiceItemModelType = "MTOServiceItemShuttle" + // MTOServiceItemModelTypeMTOServiceItemDomesticShuttle captures enum value "MTOServiceItemDomesticShuttle" + MTOServiceItemModelTypeMTOServiceItemDomesticShuttle MTOServiceItemModelType = "MTOServiceItemDomesticShuttle" + // MTOServiceItemModelTypeMTOServiceItemInternationalShuttle captures enum value "MTOServiceItemInternationalShuttle" MTOServiceItemModelTypeMTOServiceItemInternationalShuttle MTOServiceItemModelType = "MTOServiceItemInternationalShuttle" @@ -71,7 +75,7 @@ var mTOServiceItemModelTypeEnum []interface{} func init() { var res []MTOServiceItemModelType - if err := json.Unmarshal([]byte(`["MTOServiceItemBasic","MTOServiceItemOriginSIT","MTOServiceItemDestSIT","MTOServiceItemShuttle","MTOServiceItemInternationalShuttle","MTOServiceItemDomesticCrating","MTOServiceItemInternationalCrating","MTOSerivceItemInternationalFuelSurcharge"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["MTOServiceItemBasic","MTOServiceItemOriginSIT","MTOServiceItemDestSIT","MTOServiceItemShuttle","MTOServiceItemDomesticShuttle","MTOServiceItemInternationalShuttle","MTOServiceItemDomesticCrating","MTOServiceItemInternationalCrating","MTOSerivceItemInternationalFuelSurcharge"]`), &res); err != nil { panic(err) } for _, v := range res { diff --git a/pkg/gen/primev3api/embedded_spec.go b/pkg/gen/primev3api/embedded_spec.go index e5bde117279..b926e318a3e 100644 --- a/pkg/gen/primev3api/embedded_spec.go +++ b/pkg/gen/primev3api/embedded_spec.go @@ -1597,6 +1597,50 @@ func init() { } ] }, + "MTOServiceItemDomesticShuttle": { + "description": "Describes a domestic shuttle service item.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItem" + }, + { + "type": "object", + "required": [ + "reason", + "reServiceCode" + ], + "properties": { + "actualWeight": { + "description": "A record of the actual weight that was shuttled. Provided by the movers, based on weight tickets.", + "type": "integer", + "x-nullable": true, + "x-omitempty": false, + "example": 4000 + }, + "estimatedWeight": { + "description": "An estimate of how much weight from a shipment will be included in the shuttling service.", + "type": "integer", + "x-nullable": true, + "x-omitempty": false, + "example": 4200 + }, + "reServiceCode": { + "description": "A unique code for the service item. Indicates if shuttling is requested for the shipment origin (` + "`" + `DOSHUT` + "`" + `) or destination (` + "`" + `DDSHUT` + "`" + `).\n", + "type": "string", + "enum": [ + "DOSHUT", + "DDSHUT" + ] + }, + "reason": { + "description": "The contractor's explanation for why a shuttle service is requested. Used by the TOO while deciding to approve or reject the service item.\n", + "type": "string", + "example": "Storage items need to be picked up." + } + } + } + ] + }, "MTOServiceItemInternationalCrating": { "description": "Describes a international crating/uncrating service item subtype of a MTOServiceItem.", "allOf": [ @@ -1753,13 +1797,14 @@ func init() { ] }, "MTOServiceItemModelType": { - "description": "Describes all model sub-types for a MTOServiceItem model.\n\nUsing this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DOFSIT, DOASIT - MTOServiceItemOriginSIT\n * DDFSIT, DDASIT - MTOServiceItemDestSIT\n * DOSHUT, DDSHUT - MTOServiceItemShuttle\n * IOSHUT, IDSHUT - MTOServiceItemInternationalShuttle\n * DCRT, DUCRT - MTOServiceItemDomesticCrating\n * ICRT, IUCRT - MTOServiceItemInternationalCrating\n * PODFSC, POEFSC - MTOSerivceItemInternationalFuelSurcharge\n\nThe documentation will then update with the supported fields.\n", + "description": "Describes all model sub-types for a MTOServiceItem model.\n\nUsing this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DOFSIT, DOASIT - MTOServiceItemOriginSIT\n * DDFSIT, DDASIT - MTOServiceItemDestSIT\n * DOSHUT, DDSHUT - MTOServiceItemShuttle\n * DOSHUT, DDSHUT - MTOServiceItemDomesticShuttle\n * IOSHUT, IDSHUT - MTOServiceItemInternationalShuttle\n * DCRT, DUCRT - MTOServiceItemDomesticCrating\n * ICRT, IUCRT - MTOServiceItemInternationalCrating\n * PODFSC, POEFSC - MTOSerivceItemInternationalFuelSurcharge\n\nThe documentation will then update with the supported fields.\n", "type": "string", "enum": [ "MTOServiceItemBasic", "MTOServiceItemOriginSIT", "MTOServiceItemDestSIT", "MTOServiceItemShuttle", + "MTOServiceItemDomesticShuttle", "MTOServiceItemInternationalShuttle", "MTOServiceItemDomesticCrating", "MTOServiceItemInternationalCrating", @@ -6071,6 +6116,50 @@ func init() { } ] }, + "MTOServiceItemDomesticShuttle": { + "description": "Describes a domestic shuttle service item.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItem" + }, + { + "type": "object", + "required": [ + "reason", + "reServiceCode" + ], + "properties": { + "actualWeight": { + "description": "A record of the actual weight that was shuttled. Provided by the movers, based on weight tickets.", + "type": "integer", + "x-nullable": true, + "x-omitempty": false, + "example": 4000 + }, + "estimatedWeight": { + "description": "An estimate of how much weight from a shipment will be included in the shuttling service.", + "type": "integer", + "x-nullable": true, + "x-omitempty": false, + "example": 4200 + }, + "reServiceCode": { + "description": "A unique code for the service item. Indicates if shuttling is requested for the shipment origin (` + "`" + `DOSHUT` + "`" + `) or destination (` + "`" + `DDSHUT` + "`" + `).\n", + "type": "string", + "enum": [ + "DOSHUT", + "DDSHUT" + ] + }, + "reason": { + "description": "The contractor's explanation for why a shuttle service is requested. Used by the TOO while deciding to approve or reject the service item.\n", + "type": "string", + "example": "Storage items need to be picked up." + } + } + } + ] + }, "MTOServiceItemInternationalCrating": { "description": "Describes a international crating/uncrating service item subtype of a MTOServiceItem.", "allOf": [ @@ -6227,13 +6316,14 @@ func init() { ] }, "MTOServiceItemModelType": { - "description": "Describes all model sub-types for a MTOServiceItem model.\n\nUsing this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DOFSIT, DOASIT - MTOServiceItemOriginSIT\n * DDFSIT, DDASIT - MTOServiceItemDestSIT\n * DOSHUT, DDSHUT - MTOServiceItemShuttle\n * IOSHUT, IDSHUT - MTOServiceItemInternationalShuttle\n * DCRT, DUCRT - MTOServiceItemDomesticCrating\n * ICRT, IUCRT - MTOServiceItemInternationalCrating\n * PODFSC, POEFSC - MTOSerivceItemInternationalFuelSurcharge\n\nThe documentation will then update with the supported fields.\n", + "description": "Describes all model sub-types for a MTOServiceItem model.\n\nUsing this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DOFSIT, DOASIT - MTOServiceItemOriginSIT\n * DDFSIT, DDASIT - MTOServiceItemDestSIT\n * DOSHUT, DDSHUT - MTOServiceItemShuttle\n * DOSHUT, DDSHUT - MTOServiceItemDomesticShuttle\n * IOSHUT, IDSHUT - MTOServiceItemInternationalShuttle\n * DCRT, DUCRT - MTOServiceItemDomesticCrating\n * ICRT, IUCRT - MTOServiceItemInternationalCrating\n * PODFSC, POEFSC - MTOSerivceItemInternationalFuelSurcharge\n\nThe documentation will then update with the supported fields.\n", "type": "string", "enum": [ "MTOServiceItemBasic", "MTOServiceItemOriginSIT", "MTOServiceItemDestSIT", "MTOServiceItemShuttle", + "MTOServiceItemDomesticShuttle", "MTOServiceItemInternationalShuttle", "MTOServiceItemDomesticCrating", "MTOServiceItemInternationalCrating", diff --git a/pkg/gen/primev3messages/m_t_o_service_item.go b/pkg/gen/primev3messages/m_t_o_service_item.go index 75d33c217f1..06f0d63c776 100644 --- a/pkg/gen/primev3messages/m_t_o_service_item.go +++ b/pkg/gen/primev3messages/m_t_o_service_item.go @@ -273,6 +273,12 @@ func unmarshalMTOServiceItem(data []byte, consumer runtime.Consumer) (MTOService return nil, err } return &result, nil + case "MTOServiceItemDomesticShuttle": + var result MTOServiceItemDomesticShuttle + if err := consumer.Consume(buf2, &result); err != nil { + return nil, err + } + return &result, nil case "MTOServiceItemInternationalCrating": var result MTOServiceItemInternationalCrating if err := consumer.Consume(buf2, &result); err != nil { diff --git a/pkg/gen/primev3messages/m_t_o_service_item_domestic_shuttle.go b/pkg/gen/primev3messages/m_t_o_service_item_domestic_shuttle.go new file mode 100644 index 00000000000..caea94b6010 --- /dev/null +++ b/pkg/gen/primev3messages/m_t_o_service_item_domestic_shuttle.go @@ -0,0 +1,633 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package primev3messages + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "bytes" + "context" + "encoding/json" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// MTOServiceItemDomesticShuttle Describes a domestic shuttle service item. +// +// swagger:model MTOServiceItemDomesticShuttle +type MTOServiceItemDomesticShuttle struct { + eTagField string + + idField strfmt.UUID + + lockedPriceCentsField *int64 + + moveTaskOrderIdField *strfmt.UUID + + mtoShipmentIdField strfmt.UUID + + reServiceNameField string + + rejectionReasonField *string + + serviceRequestDocumentsField ServiceRequestDocuments + + statusField MTOServiceItemStatus + + // A record of the actual weight that was shuttled. Provided by the movers, based on weight tickets. + // Example: 4000 + ActualWeight *int64 `json:"actualWeight"` + + // An estimate of how much weight from a shipment will be included in the shuttling service. + // Example: 4200 + EstimatedWeight *int64 `json:"estimatedWeight"` + + // A unique code for the service item. Indicates if shuttling is requested for the shipment origin (`DOSHUT`) or destination (`DDSHUT`). + // + // Required: true + // Enum: [DOSHUT DDSHUT] + ReServiceCode *string `json:"reServiceCode"` + + // The contractor's explanation for why a shuttle service is requested. Used by the TOO while deciding to approve or reject the service item. + // + // Example: Storage items need to be picked up. + // Required: true + Reason *string `json:"reason"` +} + +// ETag gets the e tag of this subtype +func (m *MTOServiceItemDomesticShuttle) ETag() string { + return m.eTagField +} + +// SetETag sets the e tag of this subtype +func (m *MTOServiceItemDomesticShuttle) SetETag(val string) { + m.eTagField = val +} + +// ID gets the id of this subtype +func (m *MTOServiceItemDomesticShuttle) ID() strfmt.UUID { + return m.idField +} + +// SetID sets the id of this subtype +func (m *MTOServiceItemDomesticShuttle) SetID(val strfmt.UUID) { + m.idField = val +} + +// LockedPriceCents gets the locked price cents of this subtype +func (m *MTOServiceItemDomesticShuttle) LockedPriceCents() *int64 { + return m.lockedPriceCentsField +} + +// SetLockedPriceCents sets the locked price cents of this subtype +func (m *MTOServiceItemDomesticShuttle) SetLockedPriceCents(val *int64) { + m.lockedPriceCentsField = val +} + +// ModelType gets the model type of this subtype +func (m *MTOServiceItemDomesticShuttle) ModelType() MTOServiceItemModelType { + return "MTOServiceItemDomesticShuttle" +} + +// SetModelType sets the model type of this subtype +func (m *MTOServiceItemDomesticShuttle) SetModelType(val MTOServiceItemModelType) { +} + +// MoveTaskOrderID gets the move task order ID of this subtype +func (m *MTOServiceItemDomesticShuttle) MoveTaskOrderID() *strfmt.UUID { + return m.moveTaskOrderIdField +} + +// SetMoveTaskOrderID sets the move task order ID of this subtype +func (m *MTOServiceItemDomesticShuttle) SetMoveTaskOrderID(val *strfmt.UUID) { + m.moveTaskOrderIdField = val +} + +// MtoShipmentID gets the mto shipment ID of this subtype +func (m *MTOServiceItemDomesticShuttle) MtoShipmentID() strfmt.UUID { + return m.mtoShipmentIdField +} + +// SetMtoShipmentID sets the mto shipment ID of this subtype +func (m *MTOServiceItemDomesticShuttle) SetMtoShipmentID(val strfmt.UUID) { + m.mtoShipmentIdField = val +} + +// ReServiceName gets the re service name of this subtype +func (m *MTOServiceItemDomesticShuttle) ReServiceName() string { + return m.reServiceNameField +} + +// SetReServiceName sets the re service name of this subtype +func (m *MTOServiceItemDomesticShuttle) SetReServiceName(val string) { + m.reServiceNameField = val +} + +// RejectionReason gets the rejection reason of this subtype +func (m *MTOServiceItemDomesticShuttle) RejectionReason() *string { + return m.rejectionReasonField +} + +// SetRejectionReason sets the rejection reason of this subtype +func (m *MTOServiceItemDomesticShuttle) SetRejectionReason(val *string) { + m.rejectionReasonField = val +} + +// ServiceRequestDocuments gets the service request documents of this subtype +func (m *MTOServiceItemDomesticShuttle) ServiceRequestDocuments() ServiceRequestDocuments { + return m.serviceRequestDocumentsField +} + +// SetServiceRequestDocuments sets the service request documents of this subtype +func (m *MTOServiceItemDomesticShuttle) SetServiceRequestDocuments(val ServiceRequestDocuments) { + m.serviceRequestDocumentsField = val +} + +// Status gets the status of this subtype +func (m *MTOServiceItemDomesticShuttle) Status() MTOServiceItemStatus { + return m.statusField +} + +// SetStatus sets the status of this subtype +func (m *MTOServiceItemDomesticShuttle) SetStatus(val MTOServiceItemStatus) { + m.statusField = val +} + +// UnmarshalJSON unmarshals this object with a polymorphic type from a JSON structure +func (m *MTOServiceItemDomesticShuttle) UnmarshalJSON(raw []byte) error { + var data struct { + + // A record of the actual weight that was shuttled. Provided by the movers, based on weight tickets. + // Example: 4000 + ActualWeight *int64 `json:"actualWeight"` + + // An estimate of how much weight from a shipment will be included in the shuttling service. + // Example: 4200 + EstimatedWeight *int64 `json:"estimatedWeight"` + + // A unique code for the service item. Indicates if shuttling is requested for the shipment origin (`DOSHUT`) or destination (`DDSHUT`). + // + // Required: true + // Enum: [DOSHUT DDSHUT] + ReServiceCode *string `json:"reServiceCode"` + + // The contractor's explanation for why a shuttle service is requested. Used by the TOO while deciding to approve or reject the service item. + // + // Example: Storage items need to be picked up. + // Required: true + Reason *string `json:"reason"` + } + buf := bytes.NewBuffer(raw) + dec := json.NewDecoder(buf) + dec.UseNumber() + + if err := dec.Decode(&data); err != nil { + return err + } + + var base struct { + /* Just the base type fields. Used for unmashalling polymorphic types.*/ + + ETag string `json:"eTag,omitempty"` + + ID strfmt.UUID `json:"id,omitempty"` + + LockedPriceCents *int64 `json:"lockedPriceCents,omitempty"` + + ModelType MTOServiceItemModelType `json:"modelType"` + + MoveTaskOrderID *strfmt.UUID `json:"moveTaskOrderID"` + + MtoShipmentID strfmt.UUID `json:"mtoShipmentID,omitempty"` + + ReServiceName string `json:"reServiceName,omitempty"` + + RejectionReason *string `json:"rejectionReason,omitempty"` + + ServiceRequestDocuments ServiceRequestDocuments `json:"serviceRequestDocuments,omitempty"` + + Status MTOServiceItemStatus `json:"status,omitempty"` + } + buf = bytes.NewBuffer(raw) + dec = json.NewDecoder(buf) + dec.UseNumber() + + if err := dec.Decode(&base); err != nil { + return err + } + + var result MTOServiceItemDomesticShuttle + + result.eTagField = base.ETag + + result.idField = base.ID + + result.lockedPriceCentsField = base.LockedPriceCents + + if base.ModelType != result.ModelType() { + /* Not the type we're looking for. */ + return errors.New(422, "invalid modelType value: %q", base.ModelType) + } + result.moveTaskOrderIdField = base.MoveTaskOrderID + + result.mtoShipmentIdField = base.MtoShipmentID + + result.reServiceNameField = base.ReServiceName + + result.rejectionReasonField = base.RejectionReason + + result.serviceRequestDocumentsField = base.ServiceRequestDocuments + + result.statusField = base.Status + + result.ActualWeight = data.ActualWeight + result.EstimatedWeight = data.EstimatedWeight + result.ReServiceCode = data.ReServiceCode + result.Reason = data.Reason + + *m = result + + return nil +} + +// MarshalJSON marshals this object with a polymorphic type to a JSON structure +func (m MTOServiceItemDomesticShuttle) MarshalJSON() ([]byte, error) { + var b1, b2, b3 []byte + var err error + b1, err = json.Marshal(struct { + + // A record of the actual weight that was shuttled. Provided by the movers, based on weight tickets. + // Example: 4000 + ActualWeight *int64 `json:"actualWeight"` + + // An estimate of how much weight from a shipment will be included in the shuttling service. + // Example: 4200 + EstimatedWeight *int64 `json:"estimatedWeight"` + + // A unique code for the service item. Indicates if shuttling is requested for the shipment origin (`DOSHUT`) or destination (`DDSHUT`). + // + // Required: true + // Enum: [DOSHUT DDSHUT] + ReServiceCode *string `json:"reServiceCode"` + + // The contractor's explanation for why a shuttle service is requested. Used by the TOO while deciding to approve or reject the service item. + // + // Example: Storage items need to be picked up. + // Required: true + Reason *string `json:"reason"` + }{ + + ActualWeight: m.ActualWeight, + + EstimatedWeight: m.EstimatedWeight, + + ReServiceCode: m.ReServiceCode, + + Reason: m.Reason, + }) + if err != nil { + return nil, err + } + b2, err = json.Marshal(struct { + ETag string `json:"eTag,omitempty"` + + ID strfmt.UUID `json:"id,omitempty"` + + LockedPriceCents *int64 `json:"lockedPriceCents,omitempty"` + + ModelType MTOServiceItemModelType `json:"modelType"` + + MoveTaskOrderID *strfmt.UUID `json:"moveTaskOrderID"` + + MtoShipmentID strfmt.UUID `json:"mtoShipmentID,omitempty"` + + ReServiceName string `json:"reServiceName,omitempty"` + + RejectionReason *string `json:"rejectionReason,omitempty"` + + ServiceRequestDocuments ServiceRequestDocuments `json:"serviceRequestDocuments,omitempty"` + + Status MTOServiceItemStatus `json:"status,omitempty"` + }{ + + ETag: m.ETag(), + + ID: m.ID(), + + LockedPriceCents: m.LockedPriceCents(), + + ModelType: m.ModelType(), + + MoveTaskOrderID: m.MoveTaskOrderID(), + + MtoShipmentID: m.MtoShipmentID(), + + ReServiceName: m.ReServiceName(), + + RejectionReason: m.RejectionReason(), + + ServiceRequestDocuments: m.ServiceRequestDocuments(), + + Status: m.Status(), + }) + if err != nil { + return nil, err + } + + return swag.ConcatJSON(b1, b2, b3), nil +} + +// Validate validates this m t o service item domestic shuttle +func (m *MTOServiceItemDomesticShuttle) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateID(formats); err != nil { + res = append(res, err) + } + + if err := m.validateMoveTaskOrderID(formats); err != nil { + res = append(res, err) + } + + if err := m.validateMtoShipmentID(formats); err != nil { + res = append(res, err) + } + + if err := m.validateServiceRequestDocuments(formats); err != nil { + res = append(res, err) + } + + if err := m.validateStatus(formats); err != nil { + res = append(res, err) + } + + if err := m.validateReServiceCode(formats); err != nil { + res = append(res, err) + } + + if err := m.validateReason(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *MTOServiceItemDomesticShuttle) validateID(formats strfmt.Registry) error { + + if swag.IsZero(m.ID()) { // not required + return nil + } + + if err := validate.FormatOf("id", "body", "uuid", m.ID().String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) validateMoveTaskOrderID(formats strfmt.Registry) error { + + if err := validate.Required("moveTaskOrderID", "body", m.MoveTaskOrderID()); err != nil { + return err + } + + if err := validate.FormatOf("moveTaskOrderID", "body", "uuid", m.MoveTaskOrderID().String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) validateMtoShipmentID(formats strfmt.Registry) error { + + if swag.IsZero(m.MtoShipmentID()) { // not required + return nil + } + + if err := validate.FormatOf("mtoShipmentID", "body", "uuid", m.MtoShipmentID().String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) validateServiceRequestDocuments(formats strfmt.Registry) error { + + if swag.IsZero(m.ServiceRequestDocuments()) { // not required + return nil + } + + if err := m.ServiceRequestDocuments().Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("serviceRequestDocuments") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("serviceRequestDocuments") + } + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) validateStatus(formats strfmt.Registry) error { + + if swag.IsZero(m.Status()) { // not required + return nil + } + + if err := m.Status().Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("status") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("status") + } + return err + } + + return nil +} + +var mTOServiceItemDomesticShuttleTypeReServiceCodePropEnum []interface{} + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["DOSHUT","DDSHUT"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + mTOServiceItemDomesticShuttleTypeReServiceCodePropEnum = append(mTOServiceItemDomesticShuttleTypeReServiceCodePropEnum, v) + } +} + +// property enum +func (m *MTOServiceItemDomesticShuttle) validateReServiceCodeEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, mTOServiceItemDomesticShuttleTypeReServiceCodePropEnum, true); err != nil { + return err + } + return nil +} + +func (m *MTOServiceItemDomesticShuttle) validateReServiceCode(formats strfmt.Registry) error { + + if err := validate.Required("reServiceCode", "body", m.ReServiceCode); err != nil { + return err + } + + // value enum + if err := m.validateReServiceCodeEnum("reServiceCode", "body", *m.ReServiceCode); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) validateReason(formats strfmt.Registry) error { + + if err := validate.Required("reason", "body", m.Reason); err != nil { + return err + } + + return nil +} + +// ContextValidate validate this m t o service item domestic shuttle based on the context it is used +func (m *MTOServiceItemDomesticShuttle) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateETag(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateID(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateReServiceName(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateRejectionReason(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateServiceRequestDocuments(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateStatus(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *MTOServiceItemDomesticShuttle) contextValidateETag(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "eTag", "body", string(m.ETag())); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) contextValidateID(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "id", "body", strfmt.UUID(m.ID())); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) contextValidateModelType(ctx context.Context, formats strfmt.Registry) error { + + if err := m.ModelType().ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("modelType") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("modelType") + } + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) contextValidateReServiceName(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "reServiceName", "body", string(m.ReServiceName())); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) contextValidateRejectionReason(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "rejectionReason", "body", m.RejectionReason()); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) contextValidateServiceRequestDocuments(ctx context.Context, formats strfmt.Registry) error { + + if err := m.ServiceRequestDocuments().ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("serviceRequestDocuments") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("serviceRequestDocuments") + } + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) contextValidateStatus(ctx context.Context, formats strfmt.Registry) error { + + if swag.IsZero(m.Status()) { // not required + return nil + } + + if err := m.Status().ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("status") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("status") + } + return err + } + + return nil +} + +// MarshalBinary interface implementation +func (m *MTOServiceItemDomesticShuttle) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *MTOServiceItemDomesticShuttle) UnmarshalBinary(b []byte) error { + var res MTOServiceItemDomesticShuttle + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/pkg/gen/primev3messages/m_t_o_service_item_model_type.go b/pkg/gen/primev3messages/m_t_o_service_item_model_type.go index 53d2a0450f6..4dea531b524 100644 --- a/pkg/gen/primev3messages/m_t_o_service_item_model_type.go +++ b/pkg/gen/primev3messages/m_t_o_service_item_model_type.go @@ -20,6 +20,7 @@ import ( // - DOFSIT, DOASIT - MTOServiceItemOriginSIT // - DDFSIT, DDASIT - MTOServiceItemDestSIT // - DOSHUT, DDSHUT - MTOServiceItemShuttle +// - DOSHUT, DDSHUT - MTOServiceItemDomesticShuttle // - IOSHUT, IDSHUT - MTOServiceItemInternationalShuttle // - DCRT, DUCRT - MTOServiceItemDomesticCrating // - ICRT, IUCRT - MTOServiceItemInternationalCrating @@ -53,6 +54,9 @@ const ( // MTOServiceItemModelTypeMTOServiceItemShuttle captures enum value "MTOServiceItemShuttle" MTOServiceItemModelTypeMTOServiceItemShuttle MTOServiceItemModelType = "MTOServiceItemShuttle" + // MTOServiceItemModelTypeMTOServiceItemDomesticShuttle captures enum value "MTOServiceItemDomesticShuttle" + MTOServiceItemModelTypeMTOServiceItemDomesticShuttle MTOServiceItemModelType = "MTOServiceItemDomesticShuttle" + // MTOServiceItemModelTypeMTOServiceItemInternationalShuttle captures enum value "MTOServiceItemInternationalShuttle" MTOServiceItemModelTypeMTOServiceItemInternationalShuttle MTOServiceItemModelType = "MTOServiceItemInternationalShuttle" @@ -71,7 +75,7 @@ var mTOServiceItemModelTypeEnum []interface{} func init() { var res []MTOServiceItemModelType - if err := json.Unmarshal([]byte(`["MTOServiceItemBasic","MTOServiceItemOriginSIT","MTOServiceItemDestSIT","MTOServiceItemShuttle","MTOServiceItemInternationalShuttle","MTOServiceItemDomesticCrating","MTOServiceItemInternationalCrating","MTOSerivceItemInternationalFuelSurcharge"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["MTOServiceItemBasic","MTOServiceItemOriginSIT","MTOServiceItemDestSIT","MTOServiceItemShuttle","MTOServiceItemDomesticShuttle","MTOServiceItemInternationalShuttle","MTOServiceItemDomesticCrating","MTOServiceItemInternationalCrating","MTOSerivceItemInternationalFuelSurcharge"]`), &res); err != nil { panic(err) } for _, v := range res { diff --git a/pkg/gen/supportapi/embedded_spec.go b/pkg/gen/supportapi/embedded_spec.go index b794a05a20e..3c46242150b 100644 --- a/pkg/gen/supportapi/embedded_spec.go +++ b/pkg/gen/supportapi/embedded_spec.go @@ -1536,14 +1536,114 @@ func init() { } ] }, + "MTOServiceItemDomesticShuttle": { + "description": "Describes a shuttle service item.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItem" + }, + { + "type": "object", + "required": [ + "reason", + "reServiceCode", + "description" + ], + "properties": { + "actualWeight": { + "description": "Provided by the movers, based on weight tickets. Relevant for shuttling (DDSHUT \u0026 DOSHUT) service items.", + "type": "integer", + "x-nullable": true, + "x-omitempty": false, + "example": 4000 + }, + "estimatedWeight": { + "description": "An estimate of how much weight from a shipment will be included in a shuttling (DDSHUT \u0026 DOSHUT) service item.", + "type": "integer", + "x-nullable": true, + "x-omitempty": false, + "example": 4200 + }, + "reServiceCode": { + "description": "Service codes allowed for this model type.", + "type": "string", + "enum": [ + "DOSHUT", + "DDSHUT" + ] + }, + "reason": { + "description": "Explanation of why a shuttle service is required.", + "type": "string", + "example": "Storage items need to be picked up." + } + } + } + ] + }, + "MTOServiceItemInternationalShuttle": { + "description": "Describes an interntional shuttle service item.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItem" + }, + { + "type": "object", + "required": [ + "reason", + "reServiceCode", + "description" + ], + "properties": { + "actualWeight": { + "description": "Provided by the movers, based on weight tickets. Relevant for shuttling (DDSHUT \u0026 DOSHUT) service items.", + "type": "integer", + "x-nullable": true, + "x-omitempty": false, + "example": 4000 + }, + "estimatedWeight": { + "description": "An estimate of how much weight from a shipment will be included in a shuttling (IDSHUT \u0026 IOSHUT) service item.", + "type": "integer", + "x-nullable": true, + "x-omitempty": false, + "example": 4200 + }, + "market": { + "description": "To identify whether the service was provided within (CONUS) or (OCONUS)", + "type": "string", + "enum": [ + "CONUS", + "OCONUS" + ], + "example": "CONUS" + }, + "reServiceCode": { + "description": "Service codes allowed for this model type.", + "type": "string", + "enum": [ + "IOSHUT", + "IDSHUT" + ] + }, + "reason": { + "description": "Explanation of why a shuttle service is required.", + "type": "string", + "example": "Storage items need to be picked up." + } + } + } + ] + }, "MTOServiceItemModelType": { - "description": "Describes all model sub-types for a MTOServiceItem model.\n\nUsing this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DOFSIT, DOASIT - MTOServiceItemOriginSIT\n * DDFSIT, DDASIT - MTOServiceItemDestSIT\n * DOSHUT, DDSHUT - MTOServiceItemShuttle\n * DCRT, DUCRT - MTOServiceItemDomesticCrating\n * ICRT, IUCRT - MTOServiceItemInternationalCrating\n\nThe documentation will then update with the supported fields.\n", + "description": "Describes all model sub-types for a MTOServiceItem model.\n\nUsing this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DOFSIT, DOASIT - MTOServiceItemOriginSIT\n * DDFSIT, DDASIT - MTOServiceItemDestSIT\n * DOSHUT, DDSHUT - MTOServiceItemShuttle\n * DOSHUT, DDSHUT - MTOServiceItemDomesticShuttle\n * IOSHUT, IDSHUT - MTOServiceItemInternationalShuttle\n * DCRT, DUCRT - MTOServiceItemDomesticCrating\n * ICRT, IUCRT - MTOServiceItemInternationalCrating\n\nThe documentation will then update with the supported fields.\n", "type": "string", "enum": [ "MTOServiceItemBasic", "MTOServiceItemOriginSIT", "MTOServiceItemDestSIT", "MTOServiceItemShuttle", + "MTOServiceItemDomesticShuttle", "MTOServiceItemInternationalShuttle", "MTOServiceItemDomesticCrating", "MTOServiceItemInternationalCrating" @@ -4409,14 +4509,114 @@ func init() { } ] }, + "MTOServiceItemDomesticShuttle": { + "description": "Describes a shuttle service item.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItem" + }, + { + "type": "object", + "required": [ + "reason", + "reServiceCode", + "description" + ], + "properties": { + "actualWeight": { + "description": "Provided by the movers, based on weight tickets. Relevant for shuttling (DDSHUT \u0026 DOSHUT) service items.", + "type": "integer", + "x-nullable": true, + "x-omitempty": false, + "example": 4000 + }, + "estimatedWeight": { + "description": "An estimate of how much weight from a shipment will be included in a shuttling (DDSHUT \u0026 DOSHUT) service item.", + "type": "integer", + "x-nullable": true, + "x-omitempty": false, + "example": 4200 + }, + "reServiceCode": { + "description": "Service codes allowed for this model type.", + "type": "string", + "enum": [ + "DOSHUT", + "DDSHUT" + ] + }, + "reason": { + "description": "Explanation of why a shuttle service is required.", + "type": "string", + "example": "Storage items need to be picked up." + } + } + } + ] + }, + "MTOServiceItemInternationalShuttle": { + "description": "Describes an interntional shuttle service item.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItem" + }, + { + "type": "object", + "required": [ + "reason", + "reServiceCode", + "description" + ], + "properties": { + "actualWeight": { + "description": "Provided by the movers, based on weight tickets. Relevant for shuttling (DDSHUT \u0026 DOSHUT) service items.", + "type": "integer", + "x-nullable": true, + "x-omitempty": false, + "example": 4000 + }, + "estimatedWeight": { + "description": "An estimate of how much weight from a shipment will be included in a shuttling (IDSHUT \u0026 IOSHUT) service item.", + "type": "integer", + "x-nullable": true, + "x-omitempty": false, + "example": 4200 + }, + "market": { + "description": "To identify whether the service was provided within (CONUS) or (OCONUS)", + "type": "string", + "enum": [ + "CONUS", + "OCONUS" + ], + "example": "CONUS" + }, + "reServiceCode": { + "description": "Service codes allowed for this model type.", + "type": "string", + "enum": [ + "IOSHUT", + "IDSHUT" + ] + }, + "reason": { + "description": "Explanation of why a shuttle service is required.", + "type": "string", + "example": "Storage items need to be picked up." + } + } + } + ] + }, "MTOServiceItemModelType": { - "description": "Describes all model sub-types for a MTOServiceItem model.\n\nUsing this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DOFSIT, DOASIT - MTOServiceItemOriginSIT\n * DDFSIT, DDASIT - MTOServiceItemDestSIT\n * DOSHUT, DDSHUT - MTOServiceItemShuttle\n * DCRT, DUCRT - MTOServiceItemDomesticCrating\n * ICRT, IUCRT - MTOServiceItemInternationalCrating\n\nThe documentation will then update with the supported fields.\n", + "description": "Describes all model sub-types for a MTOServiceItem model.\n\nUsing this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DOFSIT, DOASIT - MTOServiceItemOriginSIT\n * DDFSIT, DDASIT - MTOServiceItemDestSIT\n * DOSHUT, DDSHUT - MTOServiceItemShuttle\n * DOSHUT, DDSHUT - MTOServiceItemDomesticShuttle\n * IOSHUT, IDSHUT - MTOServiceItemInternationalShuttle\n * DCRT, DUCRT - MTOServiceItemDomesticCrating\n * ICRT, IUCRT - MTOServiceItemInternationalCrating\n\nThe documentation will then update with the supported fields.\n", "type": "string", "enum": [ "MTOServiceItemBasic", "MTOServiceItemOriginSIT", "MTOServiceItemDestSIT", "MTOServiceItemShuttle", + "MTOServiceItemDomesticShuttle", "MTOServiceItemInternationalShuttle", "MTOServiceItemDomesticCrating", "MTOServiceItemInternationalCrating" diff --git a/pkg/gen/supportmessages/m_t_o_service_item.go b/pkg/gen/supportmessages/m_t_o_service_item.go index 23f35835eb2..4b3db62900d 100644 --- a/pkg/gen/supportmessages/m_t_o_service_item.go +++ b/pkg/gen/supportmessages/m_t_o_service_item.go @@ -239,6 +239,18 @@ func unmarshalMTOServiceItem(data []byte, consumer runtime.Consumer) (MTOService return nil, err } return &result, nil + case "MTOServiceItemDomesticShuttle": + var result MTOServiceItemDomesticShuttle + if err := consumer.Consume(buf2, &result); err != nil { + return nil, err + } + return &result, nil + case "MTOServiceItemInternationalShuttle": + var result MTOServiceItemInternationalShuttle + if err := consumer.Consume(buf2, &result); err != nil { + return nil, err + } + return &result, nil case "MTOServiceItemOriginSIT": var result MTOServiceItemOriginSIT if err := consumer.Consume(buf2, &result); err != nil { diff --git a/pkg/gen/supportmessages/m_t_o_service_item_domestic_shuttle.go b/pkg/gen/supportmessages/m_t_o_service_item_domestic_shuttle.go new file mode 100644 index 00000000000..47b7ad784ce --- /dev/null +++ b/pkg/gen/supportmessages/m_t_o_service_item_domestic_shuttle.go @@ -0,0 +1,521 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package supportmessages + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "bytes" + "context" + "encoding/json" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// MTOServiceItemDomesticShuttle Describes a shuttle service item. +// +// swagger:model MTOServiceItemDomesticShuttle +type MTOServiceItemDomesticShuttle struct { + eTagField string + + idField strfmt.UUID + + moveTaskOrderIdField *strfmt.UUID + + mtoShipmentIdField strfmt.UUID + + reServiceNameField string + + rejectionReasonField *string + + statusField MTOServiceItemStatus + + // Provided by the movers, based on weight tickets. Relevant for shuttling (DDSHUT & DOSHUT) service items. + // Example: 4000 + ActualWeight *int64 `json:"actualWeight"` + + // An estimate of how much weight from a shipment will be included in a shuttling (DDSHUT & DOSHUT) service item. + // Example: 4200 + EstimatedWeight *int64 `json:"estimatedWeight"` + + // Service codes allowed for this model type. + // Required: true + // Enum: [DOSHUT DDSHUT] + ReServiceCode *string `json:"reServiceCode"` + + // Explanation of why a shuttle service is required. + // Example: Storage items need to be picked up. + // Required: true + Reason *string `json:"reason"` +} + +// ETag gets the e tag of this subtype +func (m *MTOServiceItemDomesticShuttle) ETag() string { + return m.eTagField +} + +// SetETag sets the e tag of this subtype +func (m *MTOServiceItemDomesticShuttle) SetETag(val string) { + m.eTagField = val +} + +// ID gets the id of this subtype +func (m *MTOServiceItemDomesticShuttle) ID() strfmt.UUID { + return m.idField +} + +// SetID sets the id of this subtype +func (m *MTOServiceItemDomesticShuttle) SetID(val strfmt.UUID) { + m.idField = val +} + +// ModelType gets the model type of this subtype +func (m *MTOServiceItemDomesticShuttle) ModelType() MTOServiceItemModelType { + return "MTOServiceItemDomesticShuttle" +} + +// SetModelType sets the model type of this subtype +func (m *MTOServiceItemDomesticShuttle) SetModelType(val MTOServiceItemModelType) { +} + +// MoveTaskOrderID gets the move task order ID of this subtype +func (m *MTOServiceItemDomesticShuttle) MoveTaskOrderID() *strfmt.UUID { + return m.moveTaskOrderIdField +} + +// SetMoveTaskOrderID sets the move task order ID of this subtype +func (m *MTOServiceItemDomesticShuttle) SetMoveTaskOrderID(val *strfmt.UUID) { + m.moveTaskOrderIdField = val +} + +// MtoShipmentID gets the mto shipment ID of this subtype +func (m *MTOServiceItemDomesticShuttle) MtoShipmentID() strfmt.UUID { + return m.mtoShipmentIdField +} + +// SetMtoShipmentID sets the mto shipment ID of this subtype +func (m *MTOServiceItemDomesticShuttle) SetMtoShipmentID(val strfmt.UUID) { + m.mtoShipmentIdField = val +} + +// ReServiceName gets the re service name of this subtype +func (m *MTOServiceItemDomesticShuttle) ReServiceName() string { + return m.reServiceNameField +} + +// SetReServiceName sets the re service name of this subtype +func (m *MTOServiceItemDomesticShuttle) SetReServiceName(val string) { + m.reServiceNameField = val +} + +// RejectionReason gets the rejection reason of this subtype +func (m *MTOServiceItemDomesticShuttle) RejectionReason() *string { + return m.rejectionReasonField +} + +// SetRejectionReason sets the rejection reason of this subtype +func (m *MTOServiceItemDomesticShuttle) SetRejectionReason(val *string) { + m.rejectionReasonField = val +} + +// Status gets the status of this subtype +func (m *MTOServiceItemDomesticShuttle) Status() MTOServiceItemStatus { + return m.statusField +} + +// SetStatus sets the status of this subtype +func (m *MTOServiceItemDomesticShuttle) SetStatus(val MTOServiceItemStatus) { + m.statusField = val +} + +// UnmarshalJSON unmarshals this object with a polymorphic type from a JSON structure +func (m *MTOServiceItemDomesticShuttle) UnmarshalJSON(raw []byte) error { + var data struct { + + // Provided by the movers, based on weight tickets. Relevant for shuttling (DDSHUT & DOSHUT) service items. + // Example: 4000 + ActualWeight *int64 `json:"actualWeight"` + + // An estimate of how much weight from a shipment will be included in a shuttling (DDSHUT & DOSHUT) service item. + // Example: 4200 + EstimatedWeight *int64 `json:"estimatedWeight"` + + // Service codes allowed for this model type. + // Required: true + // Enum: [DOSHUT DDSHUT] + ReServiceCode *string `json:"reServiceCode"` + + // Explanation of why a shuttle service is required. + // Example: Storage items need to be picked up. + // Required: true + Reason *string `json:"reason"` + } + buf := bytes.NewBuffer(raw) + dec := json.NewDecoder(buf) + dec.UseNumber() + + if err := dec.Decode(&data); err != nil { + return err + } + + var base struct { + /* Just the base type fields. Used for unmashalling polymorphic types.*/ + + ETag string `json:"eTag,omitempty"` + + ID strfmt.UUID `json:"id,omitempty"` + + ModelType MTOServiceItemModelType `json:"modelType"` + + MoveTaskOrderID *strfmt.UUID `json:"moveTaskOrderID"` + + MtoShipmentID strfmt.UUID `json:"mtoShipmentID,omitempty"` + + ReServiceName string `json:"reServiceName,omitempty"` + + RejectionReason *string `json:"rejectionReason,omitempty"` + + Status MTOServiceItemStatus `json:"status,omitempty"` + } + buf = bytes.NewBuffer(raw) + dec = json.NewDecoder(buf) + dec.UseNumber() + + if err := dec.Decode(&base); err != nil { + return err + } + + var result MTOServiceItemDomesticShuttle + + result.eTagField = base.ETag + + result.idField = base.ID + + if base.ModelType != result.ModelType() { + /* Not the type we're looking for. */ + return errors.New(422, "invalid modelType value: %q", base.ModelType) + } + result.moveTaskOrderIdField = base.MoveTaskOrderID + + result.mtoShipmentIdField = base.MtoShipmentID + + result.reServiceNameField = base.ReServiceName + + result.rejectionReasonField = base.RejectionReason + + result.statusField = base.Status + + result.ActualWeight = data.ActualWeight + result.EstimatedWeight = data.EstimatedWeight + result.ReServiceCode = data.ReServiceCode + result.Reason = data.Reason + + *m = result + + return nil +} + +// MarshalJSON marshals this object with a polymorphic type to a JSON structure +func (m MTOServiceItemDomesticShuttle) MarshalJSON() ([]byte, error) { + var b1, b2, b3 []byte + var err error + b1, err = json.Marshal(struct { + + // Provided by the movers, based on weight tickets. Relevant for shuttling (DDSHUT & DOSHUT) service items. + // Example: 4000 + ActualWeight *int64 `json:"actualWeight"` + + // An estimate of how much weight from a shipment will be included in a shuttling (DDSHUT & DOSHUT) service item. + // Example: 4200 + EstimatedWeight *int64 `json:"estimatedWeight"` + + // Service codes allowed for this model type. + // Required: true + // Enum: [DOSHUT DDSHUT] + ReServiceCode *string `json:"reServiceCode"` + + // Explanation of why a shuttle service is required. + // Example: Storage items need to be picked up. + // Required: true + Reason *string `json:"reason"` + }{ + + ActualWeight: m.ActualWeight, + + EstimatedWeight: m.EstimatedWeight, + + ReServiceCode: m.ReServiceCode, + + Reason: m.Reason, + }) + if err != nil { + return nil, err + } + b2, err = json.Marshal(struct { + ETag string `json:"eTag,omitempty"` + + ID strfmt.UUID `json:"id,omitempty"` + + ModelType MTOServiceItemModelType `json:"modelType"` + + MoveTaskOrderID *strfmt.UUID `json:"moveTaskOrderID"` + + MtoShipmentID strfmt.UUID `json:"mtoShipmentID,omitempty"` + + ReServiceName string `json:"reServiceName,omitempty"` + + RejectionReason *string `json:"rejectionReason,omitempty"` + + Status MTOServiceItemStatus `json:"status,omitempty"` + }{ + + ETag: m.ETag(), + + ID: m.ID(), + + ModelType: m.ModelType(), + + MoveTaskOrderID: m.MoveTaskOrderID(), + + MtoShipmentID: m.MtoShipmentID(), + + ReServiceName: m.ReServiceName(), + + RejectionReason: m.RejectionReason(), + + Status: m.Status(), + }) + if err != nil { + return nil, err + } + + return swag.ConcatJSON(b1, b2, b3), nil +} + +// Validate validates this m t o service item domestic shuttle +func (m *MTOServiceItemDomesticShuttle) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateID(formats); err != nil { + res = append(res, err) + } + + if err := m.validateMoveTaskOrderID(formats); err != nil { + res = append(res, err) + } + + if err := m.validateMtoShipmentID(formats); err != nil { + res = append(res, err) + } + + if err := m.validateStatus(formats); err != nil { + res = append(res, err) + } + + if err := m.validateReServiceCode(formats); err != nil { + res = append(res, err) + } + + if err := m.validateReason(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *MTOServiceItemDomesticShuttle) validateID(formats strfmt.Registry) error { + + if swag.IsZero(m.ID()) { // not required + return nil + } + + if err := validate.FormatOf("id", "body", "uuid", m.ID().String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) validateMoveTaskOrderID(formats strfmt.Registry) error { + + if err := validate.Required("moveTaskOrderID", "body", m.MoveTaskOrderID()); err != nil { + return err + } + + if err := validate.FormatOf("moveTaskOrderID", "body", "uuid", m.MoveTaskOrderID().String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) validateMtoShipmentID(formats strfmt.Registry) error { + + if swag.IsZero(m.MtoShipmentID()) { // not required + return nil + } + + if err := validate.FormatOf("mtoShipmentID", "body", "uuid", m.MtoShipmentID().String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) validateStatus(formats strfmt.Registry) error { + + if swag.IsZero(m.Status()) { // not required + return nil + } + + if err := m.Status().Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("status") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("status") + } + return err + } + + return nil +} + +var mTOServiceItemDomesticShuttleTypeReServiceCodePropEnum []interface{} + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["DOSHUT","DDSHUT"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + mTOServiceItemDomesticShuttleTypeReServiceCodePropEnum = append(mTOServiceItemDomesticShuttleTypeReServiceCodePropEnum, v) + } +} + +// property enum +func (m *MTOServiceItemDomesticShuttle) validateReServiceCodeEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, mTOServiceItemDomesticShuttleTypeReServiceCodePropEnum, true); err != nil { + return err + } + return nil +} + +func (m *MTOServiceItemDomesticShuttle) validateReServiceCode(formats strfmt.Registry) error { + + if err := validate.Required("reServiceCode", "body", m.ReServiceCode); err != nil { + return err + } + + // value enum + if err := m.validateReServiceCodeEnum("reServiceCode", "body", *m.ReServiceCode); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) validateReason(formats strfmt.Registry) error { + + if err := validate.Required("reason", "body", m.Reason); err != nil { + return err + } + + return nil +} + +// ContextValidate validate this m t o service item domestic shuttle based on the context it is used +func (m *MTOServiceItemDomesticShuttle) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateETag(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateReServiceName(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateStatus(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *MTOServiceItemDomesticShuttle) contextValidateETag(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "eTag", "body", string(m.ETag())); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) contextValidateModelType(ctx context.Context, formats strfmt.Registry) error { + + if err := m.ModelType().ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("modelType") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("modelType") + } + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) contextValidateReServiceName(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "reServiceName", "body", string(m.ReServiceName())); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) contextValidateStatus(ctx context.Context, formats strfmt.Registry) error { + + if swag.IsZero(m.Status()) { // not required + return nil + } + + if err := m.Status().ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("status") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("status") + } + return err + } + + return nil +} + +// MarshalBinary interface implementation +func (m *MTOServiceItemDomesticShuttle) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *MTOServiceItemDomesticShuttle) UnmarshalBinary(b []byte) error { + var res MTOServiceItemDomesticShuttle + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/pkg/gen/supportmessages/m_t_o_service_item_international_shuttle.go b/pkg/gen/supportmessages/m_t_o_service_item_international_shuttle.go new file mode 100644 index 00000000000..8286a2e6dd6 --- /dev/null +++ b/pkg/gen/supportmessages/m_t_o_service_item_international_shuttle.go @@ -0,0 +1,577 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package supportmessages + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "bytes" + "context" + "encoding/json" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// MTOServiceItemInternationalShuttle Describes an interntional shuttle service item. +// +// swagger:model MTOServiceItemInternationalShuttle +type MTOServiceItemInternationalShuttle struct { + eTagField string + + idField strfmt.UUID + + moveTaskOrderIdField *strfmt.UUID + + mtoShipmentIdField strfmt.UUID + + reServiceNameField string + + rejectionReasonField *string + + statusField MTOServiceItemStatus + + // Provided by the movers, based on weight tickets. Relevant for shuttling (DDSHUT & DOSHUT) service items. + // Example: 4000 + ActualWeight *int64 `json:"actualWeight"` + + // An estimate of how much weight from a shipment will be included in a shuttling (IDSHUT & IOSHUT) service item. + // Example: 4200 + EstimatedWeight *int64 `json:"estimatedWeight"` + + // To identify whether the service was provided within (CONUS) or (OCONUS) + // Example: CONUS + // Enum: [CONUS OCONUS] + Market string `json:"market,omitempty"` + + // Service codes allowed for this model type. + // Required: true + // Enum: [IOSHUT IDSHUT] + ReServiceCode *string `json:"reServiceCode"` + + // Explanation of why a shuttle service is required. + // Example: Storage items need to be picked up. + // Required: true + Reason *string `json:"reason"` +} + +// ETag gets the e tag of this subtype +func (m *MTOServiceItemInternationalShuttle) ETag() string { + return m.eTagField +} + +// SetETag sets the e tag of this subtype +func (m *MTOServiceItemInternationalShuttle) SetETag(val string) { + m.eTagField = val +} + +// ID gets the id of this subtype +func (m *MTOServiceItemInternationalShuttle) ID() strfmt.UUID { + return m.idField +} + +// SetID sets the id of this subtype +func (m *MTOServiceItemInternationalShuttle) SetID(val strfmt.UUID) { + m.idField = val +} + +// ModelType gets the model type of this subtype +func (m *MTOServiceItemInternationalShuttle) ModelType() MTOServiceItemModelType { + return "MTOServiceItemInternationalShuttle" +} + +// SetModelType sets the model type of this subtype +func (m *MTOServiceItemInternationalShuttle) SetModelType(val MTOServiceItemModelType) { +} + +// MoveTaskOrderID gets the move task order ID of this subtype +func (m *MTOServiceItemInternationalShuttle) MoveTaskOrderID() *strfmt.UUID { + return m.moveTaskOrderIdField +} + +// SetMoveTaskOrderID sets the move task order ID of this subtype +func (m *MTOServiceItemInternationalShuttle) SetMoveTaskOrderID(val *strfmt.UUID) { + m.moveTaskOrderIdField = val +} + +// MtoShipmentID gets the mto shipment ID of this subtype +func (m *MTOServiceItemInternationalShuttle) MtoShipmentID() strfmt.UUID { + return m.mtoShipmentIdField +} + +// SetMtoShipmentID sets the mto shipment ID of this subtype +func (m *MTOServiceItemInternationalShuttle) SetMtoShipmentID(val strfmt.UUID) { + m.mtoShipmentIdField = val +} + +// ReServiceName gets the re service name of this subtype +func (m *MTOServiceItemInternationalShuttle) ReServiceName() string { + return m.reServiceNameField +} + +// SetReServiceName sets the re service name of this subtype +func (m *MTOServiceItemInternationalShuttle) SetReServiceName(val string) { + m.reServiceNameField = val +} + +// RejectionReason gets the rejection reason of this subtype +func (m *MTOServiceItemInternationalShuttle) RejectionReason() *string { + return m.rejectionReasonField +} + +// SetRejectionReason sets the rejection reason of this subtype +func (m *MTOServiceItemInternationalShuttle) SetRejectionReason(val *string) { + m.rejectionReasonField = val +} + +// Status gets the status of this subtype +func (m *MTOServiceItemInternationalShuttle) Status() MTOServiceItemStatus { + return m.statusField +} + +// SetStatus sets the status of this subtype +func (m *MTOServiceItemInternationalShuttle) SetStatus(val MTOServiceItemStatus) { + m.statusField = val +} + +// UnmarshalJSON unmarshals this object with a polymorphic type from a JSON structure +func (m *MTOServiceItemInternationalShuttle) UnmarshalJSON(raw []byte) error { + var data struct { + + // Provided by the movers, based on weight tickets. Relevant for shuttling (DDSHUT & DOSHUT) service items. + // Example: 4000 + ActualWeight *int64 `json:"actualWeight"` + + // An estimate of how much weight from a shipment will be included in a shuttling (IDSHUT & IOSHUT) service item. + // Example: 4200 + EstimatedWeight *int64 `json:"estimatedWeight"` + + // To identify whether the service was provided within (CONUS) or (OCONUS) + // Example: CONUS + // Enum: [CONUS OCONUS] + Market string `json:"market,omitempty"` + + // Service codes allowed for this model type. + // Required: true + // Enum: [IOSHUT IDSHUT] + ReServiceCode *string `json:"reServiceCode"` + + // Explanation of why a shuttle service is required. + // Example: Storage items need to be picked up. + // Required: true + Reason *string `json:"reason"` + } + buf := bytes.NewBuffer(raw) + dec := json.NewDecoder(buf) + dec.UseNumber() + + if err := dec.Decode(&data); err != nil { + return err + } + + var base struct { + /* Just the base type fields. Used for unmashalling polymorphic types.*/ + + ETag string `json:"eTag,omitempty"` + + ID strfmt.UUID `json:"id,omitempty"` + + ModelType MTOServiceItemModelType `json:"modelType"` + + MoveTaskOrderID *strfmt.UUID `json:"moveTaskOrderID"` + + MtoShipmentID strfmt.UUID `json:"mtoShipmentID,omitempty"` + + ReServiceName string `json:"reServiceName,omitempty"` + + RejectionReason *string `json:"rejectionReason,omitempty"` + + Status MTOServiceItemStatus `json:"status,omitempty"` + } + buf = bytes.NewBuffer(raw) + dec = json.NewDecoder(buf) + dec.UseNumber() + + if err := dec.Decode(&base); err != nil { + return err + } + + var result MTOServiceItemInternationalShuttle + + result.eTagField = base.ETag + + result.idField = base.ID + + if base.ModelType != result.ModelType() { + /* Not the type we're looking for. */ + return errors.New(422, "invalid modelType value: %q", base.ModelType) + } + result.moveTaskOrderIdField = base.MoveTaskOrderID + + result.mtoShipmentIdField = base.MtoShipmentID + + result.reServiceNameField = base.ReServiceName + + result.rejectionReasonField = base.RejectionReason + + result.statusField = base.Status + + result.ActualWeight = data.ActualWeight + result.EstimatedWeight = data.EstimatedWeight + result.Market = data.Market + result.ReServiceCode = data.ReServiceCode + result.Reason = data.Reason + + *m = result + + return nil +} + +// MarshalJSON marshals this object with a polymorphic type to a JSON structure +func (m MTOServiceItemInternationalShuttle) MarshalJSON() ([]byte, error) { + var b1, b2, b3 []byte + var err error + b1, err = json.Marshal(struct { + + // Provided by the movers, based on weight tickets. Relevant for shuttling (DDSHUT & DOSHUT) service items. + // Example: 4000 + ActualWeight *int64 `json:"actualWeight"` + + // An estimate of how much weight from a shipment will be included in a shuttling (IDSHUT & IOSHUT) service item. + // Example: 4200 + EstimatedWeight *int64 `json:"estimatedWeight"` + + // To identify whether the service was provided within (CONUS) or (OCONUS) + // Example: CONUS + // Enum: [CONUS OCONUS] + Market string `json:"market,omitempty"` + + // Service codes allowed for this model type. + // Required: true + // Enum: [IOSHUT IDSHUT] + ReServiceCode *string `json:"reServiceCode"` + + // Explanation of why a shuttle service is required. + // Example: Storage items need to be picked up. + // Required: true + Reason *string `json:"reason"` + }{ + + ActualWeight: m.ActualWeight, + + EstimatedWeight: m.EstimatedWeight, + + Market: m.Market, + + ReServiceCode: m.ReServiceCode, + + Reason: m.Reason, + }) + if err != nil { + return nil, err + } + b2, err = json.Marshal(struct { + ETag string `json:"eTag,omitempty"` + + ID strfmt.UUID `json:"id,omitempty"` + + ModelType MTOServiceItemModelType `json:"modelType"` + + MoveTaskOrderID *strfmt.UUID `json:"moveTaskOrderID"` + + MtoShipmentID strfmt.UUID `json:"mtoShipmentID,omitempty"` + + ReServiceName string `json:"reServiceName,omitempty"` + + RejectionReason *string `json:"rejectionReason,omitempty"` + + Status MTOServiceItemStatus `json:"status,omitempty"` + }{ + + ETag: m.ETag(), + + ID: m.ID(), + + ModelType: m.ModelType(), + + MoveTaskOrderID: m.MoveTaskOrderID(), + + MtoShipmentID: m.MtoShipmentID(), + + ReServiceName: m.ReServiceName(), + + RejectionReason: m.RejectionReason(), + + Status: m.Status(), + }) + if err != nil { + return nil, err + } + + return swag.ConcatJSON(b1, b2, b3), nil +} + +// Validate validates this m t o service item international shuttle +func (m *MTOServiceItemInternationalShuttle) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateID(formats); err != nil { + res = append(res, err) + } + + if err := m.validateMoveTaskOrderID(formats); err != nil { + res = append(res, err) + } + + if err := m.validateMtoShipmentID(formats); err != nil { + res = append(res, err) + } + + if err := m.validateStatus(formats); err != nil { + res = append(res, err) + } + + if err := m.validateMarket(formats); err != nil { + res = append(res, err) + } + + if err := m.validateReServiceCode(formats); err != nil { + res = append(res, err) + } + + if err := m.validateReason(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *MTOServiceItemInternationalShuttle) validateID(formats strfmt.Registry) error { + + if swag.IsZero(m.ID()) { // not required + return nil + } + + if err := validate.FormatOf("id", "body", "uuid", m.ID().String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalShuttle) validateMoveTaskOrderID(formats strfmt.Registry) error { + + if err := validate.Required("moveTaskOrderID", "body", m.MoveTaskOrderID()); err != nil { + return err + } + + if err := validate.FormatOf("moveTaskOrderID", "body", "uuid", m.MoveTaskOrderID().String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalShuttle) validateMtoShipmentID(formats strfmt.Registry) error { + + if swag.IsZero(m.MtoShipmentID()) { // not required + return nil + } + + if err := validate.FormatOf("mtoShipmentID", "body", "uuid", m.MtoShipmentID().String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalShuttle) validateStatus(formats strfmt.Registry) error { + + if swag.IsZero(m.Status()) { // not required + return nil + } + + if err := m.Status().Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("status") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("status") + } + return err + } + + return nil +} + +var mTOServiceItemInternationalShuttleTypeMarketPropEnum []interface{} + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["CONUS","OCONUS"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + mTOServiceItemInternationalShuttleTypeMarketPropEnum = append(mTOServiceItemInternationalShuttleTypeMarketPropEnum, v) + } +} + +// property enum +func (m *MTOServiceItemInternationalShuttle) validateMarketEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, mTOServiceItemInternationalShuttleTypeMarketPropEnum, true); err != nil { + return err + } + return nil +} + +func (m *MTOServiceItemInternationalShuttle) validateMarket(formats strfmt.Registry) error { + + if swag.IsZero(m.Market) { // not required + return nil + } + + // value enum + if err := m.validateMarketEnum("market", "body", m.Market); err != nil { + return err + } + + return nil +} + +var mTOServiceItemInternationalShuttleTypeReServiceCodePropEnum []interface{} + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["IOSHUT","IDSHUT"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + mTOServiceItemInternationalShuttleTypeReServiceCodePropEnum = append(mTOServiceItemInternationalShuttleTypeReServiceCodePropEnum, v) + } +} + +// property enum +func (m *MTOServiceItemInternationalShuttle) validateReServiceCodeEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, mTOServiceItemInternationalShuttleTypeReServiceCodePropEnum, true); err != nil { + return err + } + return nil +} + +func (m *MTOServiceItemInternationalShuttle) validateReServiceCode(formats strfmt.Registry) error { + + if err := validate.Required("reServiceCode", "body", m.ReServiceCode); err != nil { + return err + } + + // value enum + if err := m.validateReServiceCodeEnum("reServiceCode", "body", *m.ReServiceCode); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalShuttle) validateReason(formats strfmt.Registry) error { + + if err := validate.Required("reason", "body", m.Reason); err != nil { + return err + } + + return nil +} + +// ContextValidate validate this m t o service item international shuttle based on the context it is used +func (m *MTOServiceItemInternationalShuttle) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateETag(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateReServiceName(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateStatus(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *MTOServiceItemInternationalShuttle) contextValidateETag(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "eTag", "body", string(m.ETag())); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalShuttle) contextValidateModelType(ctx context.Context, formats strfmt.Registry) error { + + if err := m.ModelType().ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("modelType") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("modelType") + } + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalShuttle) contextValidateReServiceName(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "reServiceName", "body", string(m.ReServiceName())); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalShuttle) contextValidateStatus(ctx context.Context, formats strfmt.Registry) error { + + if swag.IsZero(m.Status()) { // not required + return nil + } + + if err := m.Status().ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("status") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("status") + } + return err + } + + return nil +} + +// MarshalBinary interface implementation +func (m *MTOServiceItemInternationalShuttle) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *MTOServiceItemInternationalShuttle) UnmarshalBinary(b []byte) error { + var res MTOServiceItemInternationalShuttle + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/pkg/gen/supportmessages/m_t_o_service_item_model_type.go b/pkg/gen/supportmessages/m_t_o_service_item_model_type.go index 3f957023e8a..52f3729cdab 100644 --- a/pkg/gen/supportmessages/m_t_o_service_item_model_type.go +++ b/pkg/gen/supportmessages/m_t_o_service_item_model_type.go @@ -20,6 +20,8 @@ import ( // - DOFSIT, DOASIT - MTOServiceItemOriginSIT // - DDFSIT, DDASIT - MTOServiceItemDestSIT // - DOSHUT, DDSHUT - MTOServiceItemShuttle +// - DOSHUT, DDSHUT - MTOServiceItemDomesticShuttle +// - IOSHUT, IDSHUT - MTOServiceItemInternationalShuttle // - DCRT, DUCRT - MTOServiceItemDomesticCrating // - ICRT, IUCRT - MTOServiceItemInternationalCrating // @@ -51,6 +53,9 @@ const ( // MTOServiceItemModelTypeMTOServiceItemShuttle captures enum value "MTOServiceItemShuttle" MTOServiceItemModelTypeMTOServiceItemShuttle MTOServiceItemModelType = "MTOServiceItemShuttle" + // MTOServiceItemModelTypeMTOServiceItemDomesticShuttle captures enum value "MTOServiceItemDomesticShuttle" + MTOServiceItemModelTypeMTOServiceItemDomesticShuttle MTOServiceItemModelType = "MTOServiceItemDomesticShuttle" + // MTOServiceItemModelTypeMTOServiceItemInternationalShuttle captures enum value "MTOServiceItemInternationalShuttle" MTOServiceItemModelTypeMTOServiceItemInternationalShuttle MTOServiceItemModelType = "MTOServiceItemInternationalShuttle" @@ -66,7 +71,7 @@ var mTOServiceItemModelTypeEnum []interface{} func init() { var res []MTOServiceItemModelType - if err := json.Unmarshal([]byte(`["MTOServiceItemBasic","MTOServiceItemOriginSIT","MTOServiceItemDestSIT","MTOServiceItemShuttle","MTOServiceItemInternationalShuttle","MTOServiceItemDomesticCrating","MTOServiceItemInternationalCrating"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["MTOServiceItemBasic","MTOServiceItemOriginSIT","MTOServiceItemDestSIT","MTOServiceItemShuttle","MTOServiceItemDomesticShuttle","MTOServiceItemInternationalShuttle","MTOServiceItemDomesticCrating","MTOServiceItemInternationalCrating"]`), &res); err != nil { panic(err) } for _, v := range res { diff --git a/pkg/handlers/ghcapi/api.go b/pkg/handlers/ghcapi/api.go index 975ca79ca75..4402e196f67 100644 --- a/pkg/handlers/ghcapi/api.go +++ b/pkg/handlers/ghcapi/api.go @@ -84,9 +84,22 @@ func NewGhcAPIHandler(handlerConfig handlers.HandlerConfig) *ghcops.MymoveAPI { signedCertificationCreator := signedcertification.NewSignedCertificationCreator() signedCertificationUpdater := signedcertification.NewSignedCertificationUpdater() ppmEstimator := ppmshipment.NewEstimatePPM(handlerConfig.DTODPlanner(), &paymentrequesthelper.RequestPaymentHelper{}) + + mtoServiceItemCreator := mtoserviceitem.NewMTOServiceItemCreator( + handlerConfig.HHGPlanner(), + queryBuilder, + moveRouter, + ghcrateengine.NewDomesticUnpackPricer(), + ghcrateengine.NewDomesticPackPricer(), + ghcrateengine.NewDomesticLinehaulPricer(), + ghcrateengine.NewDomesticShorthaulPricer(), + ghcrateengine.NewDomesticOriginPricer(), + ghcrateengine.NewDomesticDestinationPricer(), + ghcrateengine.NewFuelSurchargePricer()) + moveTaskOrderUpdater := movetaskorder.NewMoveTaskOrderUpdater( queryBuilder, - mtoserviceitem.NewMTOServiceItemCreator(handlerConfig.HHGPlanner(), queryBuilder, moveRouter, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticPackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticShorthaulPricer(), ghcrateengine.NewDomesticOriginPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()), + mtoServiceItemCreator, moveRouter, signedCertificationCreator, signedCertificationUpdater, ppmEstimator, ) @@ -235,7 +248,7 @@ func NewGhcAPIHandler(handlerConfig handlers.HandlerConfig) *ghcops.MymoveAPI { paymentRequestShipmentRecalculator, addressUpdater, addressCreator) - sitExtensionShipmentUpdater := shipment.NewShipmentUpdater(noCheckUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater) + sitExtensionShipmentUpdater := shipment.NewShipmentUpdater(noCheckUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater, mtoServiceItemCreator) ghcAPI.MtoServiceItemUpdateServiceItemSitEntryDateHandler = UpdateServiceItemSitEntryDateHandler{ HandlerConfig: handlerConfig, @@ -247,7 +260,7 @@ func NewGhcAPIHandler(handlerConfig handlers.HandlerConfig) *ghcops.MymoveAPI { ghcAPI.MtoServiceItemUpdateMTOServiceItemStatusHandler = UpdateMTOServiceItemStatusHandler{ HandlerConfig: handlerConfig, - MTOServiceItemUpdater: mtoserviceitem.NewMTOServiceItemUpdater(handlerConfig.HHGPlanner(), queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher), + MTOServiceItemUpdater: mtoserviceitem.NewMTOServiceItemUpdater(handlerConfig.HHGPlanner(), queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()), Fetcher: fetch.NewFetcher(queryBuilder), ShipmentSITStatus: sitstatus.NewShipmentSITStatus(), MTOShipmentFetcher: mtoshipment.NewMTOShipmentFetcher(), @@ -429,7 +442,7 @@ func NewGhcAPIHandler(handlerConfig handlers.HandlerConfig) *ghcops.MymoveAPI { handlerConfig, mtoshipment.NewShipmentApprover( mtoshipment.NewShipmentRouter(), - mtoserviceitem.NewMTOServiceItemCreator(handlerConfig.HHGPlanner(), queryBuilder, moveRouter, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticPackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticShorthaulPricer(), ghcrateengine.NewDomesticOriginPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()), + mtoServiceItemCreator, handlerConfig.HHGPlanner(), move.NewMoveWeights(mtoshipment.NewShipmentReweighRequester(), waf), moveTaskOrderUpdater, @@ -500,7 +513,7 @@ func NewGhcAPIHandler(handlerConfig handlers.HandlerConfig) *ghcops.MymoveAPI { addressCreator, ) - shipmentUpdater := shipment.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater) + shipmentUpdater := shipment.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater, mtoServiceItemCreator) ghcAPI.MoveSearchMovesHandler = SearchMovesHandler{ HandlerConfig: handlerConfig, @@ -541,7 +554,7 @@ func NewGhcAPIHandler(handlerConfig handlers.HandlerConfig) *ghcops.MymoveAPI { ghcAPI.ShipmentUpdateSITServiceItemCustomerExpenseHandler = UpdateSITServiceItemCustomerExpenseHandler{ handlerConfig, - mtoserviceitem.NewMTOServiceItemUpdater(handlerConfig.HHGPlanner(), queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher), + mtoserviceitem.NewMTOServiceItemUpdater(handlerConfig.HHGPlanner(), queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()), mtoshipment.NewMTOShipmentFetcher(), shipmentSITStatus, } @@ -569,6 +582,13 @@ func NewGhcAPIHandler(handlerConfig handlers.HandlerConfig) *ghcops.MymoveAPI { officeusercreator.NewOfficeUserFetcherPop(), } + ghcAPI.QueuesGetDestinationRequestsQueueHandler = GetDestinationRequestsQueueHandler{ + handlerConfig, + order.NewOrderFetcher(waf), + movelocker.NewMoveUnlocker(), + officeusercreator.NewOfficeUserFetcherPop(), + } + ghcAPI.QueuesListPrimeMovesHandler = ListPrimeMovesHandler{ handlerConfig, movetaskorder.NewMoveTaskOrderFetcher(waf), diff --git a/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go b/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go index ffbbe5e4e4f..d384f326414 100644 --- a/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go +++ b/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go @@ -54,7 +54,7 @@ func OfficeUser(officeUser *models.OfficeUser) *ghcmessages.LockedOfficeUser { } func AssignedOfficeUser(officeUser *models.OfficeUser) *ghcmessages.AssignedOfficeUser { - if officeUser != nil { + if officeUser != nil && officeUser.FirstName != "" && officeUser.LastName != "" { payload := ghcmessages.AssignedOfficeUser{ OfficeUserID: strfmt.UUID(officeUser.ID.String()), FirstName: officeUser.FirstName, diff --git a/pkg/handlers/ghcapi/internal/payloads/model_to_payload_test.go b/pkg/handlers/ghcapi/internal/payloads/model_to_payload_test.go index e1f22b64ee8..5ffd6055a9c 100644 --- a/pkg/handlers/ghcapi/internal/payloads/model_to_payload_test.go +++ b/pkg/handlers/ghcapi/internal/payloads/model_to_payload_test.go @@ -905,7 +905,7 @@ func (suite *PayloadsSuite) TestReServiceItem() { isAutoApproved := true marketCodeInternational := models.MarketCodeInternational reServiceCode := models.ReServiceCodePOEFSC - poefscServiceName := "International POE Fuel Surcharge" + poefscServiceName := "International POE fuel surcharge" reService := models.ReService{ Code: reServiceCode, Name: poefscServiceName, @@ -937,8 +937,8 @@ func (suite *PayloadsSuite) TestReServiceItems() { marketCodeDomestic := models.MarketCodeDomestic poefscReServiceCode := models.ReServiceCodePOEFSC podfscReServiceCode := models.ReServiceCodePODFSC - poefscServiceName := "International POE Fuel Surcharge" - podfscServiceName := "International POD Fuel Surcharge" + poefscServiceName := "International POE fuel surcharge" + podfscServiceName := "International POD fuel surcharge" poefscService := models.ReService{ Code: poefscReServiceCode, Name: poefscServiceName, diff --git a/pkg/handlers/ghcapi/mto_service_items.go b/pkg/handlers/ghcapi/mto_service_items.go index e5f5572580d..99939aaf063 100644 --- a/pkg/handlers/ghcapi/mto_service_items.go +++ b/pkg/handlers/ghcapi/mto_service_items.go @@ -352,6 +352,8 @@ func (h ListMTOServiceItemsHandler) Handle(params mtoserviceitemop.ListMTOServic query.NewQueryAssociation("SITOriginHHGActualAddress"), query.NewQueryAssociation("ReService.ReServiceItems"), query.NewQueryAssociation("MTOShipment"), + query.NewQueryAssociation("MTOShipment.PickupAddress"), + query.NewQueryAssociation("MTOShipment.DestinationAddress"), }) var serviceItems models.MTOServiceItems diff --git a/pkg/handlers/ghcapi/mto_service_items_test.go b/pkg/handlers/ghcapi/mto_service_items_test.go index a380a72356e..8044580f0b0 100644 --- a/pkg/handlers/ghcapi/mto_service_items_test.go +++ b/pkg/handlers/ghcapi/mto_service_items_test.go @@ -425,7 +425,7 @@ func (suite *HandlerSuite) TestUpdateMTOServiceItemStatusHandler() { ppmShipmentUpdater := ppmshipment.NewPPMShipmentUpdater(&ppmEstimator, addressCreator, addressUpdater) boatShipmentUpdater := boatshipment.NewBoatShipmentUpdater() mobileHomeShipmentUpdater := mobilehomeshipment.NewMobileHomeShipmentUpdater() - shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(noCheckUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater) + shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(noCheckUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater, nil) shipmentFetcher := mtoshipment.NewMTOShipmentFetcher() moveTaskOrderID, _ := uuid.NewV4() @@ -661,7 +661,7 @@ func (suite *HandlerSuite) TestUpdateMTOServiceItemStatusHandler() { mock.Anything, false, ).Return(400, nil) - mtoServiceItemStatusUpdater := mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher) + mtoServiceItemStatusUpdater := mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) handler := UpdateMTOServiceItemStatusHandler{ HandlerConfig: suite.HandlerConfig(), @@ -723,7 +723,7 @@ func (suite *HandlerSuite) TestUpdateMTOServiceItemStatusHandler() { mock.Anything, false, ).Return(400, nil) - mtoServiceItemStatusUpdater := mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher) + mtoServiceItemStatusUpdater := mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) handler := UpdateMTOServiceItemStatusHandler{ HandlerConfig: suite.HandlerConfig(), @@ -877,7 +877,7 @@ func (suite *HandlerSuite) TestUpdateServiceItemSitEntryDateHandler() { ppmShipmentUpdater := ppmshipment.NewPPMShipmentUpdater(&ppmEstimator, addressCreator, addressUpdater) boatShipmentUpdater := boatshipment.NewBoatShipmentUpdater() mobileHomeShipmentUpdater := mobilehomeshipment.NewMobileHomeShipmentUpdater() - shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(noCheckUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater) + shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(noCheckUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater, nil) shipmentFetcher := mtoshipment.NewMTOShipmentFetcher() suite.Run("200 - success response", func() { diff --git a/pkg/handlers/ghcapi/mto_shipment_test.go b/pkg/handlers/ghcapi/mto_shipment_test.go index 70ac78cbaac..247ef1ae1fb 100644 --- a/pkg/handlers/ghcapi/mto_shipment_test.go +++ b/pkg/handlers/ghcapi/mto_shipment_test.go @@ -3293,7 +3293,7 @@ func (suite *HandlerSuite) TestApproveSITExtensionHandler() { boatShipmentUpdater := boatshipment.NewBoatShipmentUpdater() mobileHomeShipmentUpdater := mobilehomeshipment.NewMobileHomeShipmentUpdater() - sitExtensionShipmentUpdater := shipmentorchestrator.NewShipmentUpdater(noCheckUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater) + sitExtensionShipmentUpdater := shipmentorchestrator.NewShipmentUpdater(noCheckUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater, nil) handler := ApproveSITExtensionHandler{ handlerConfig, @@ -3437,7 +3437,7 @@ func (suite *HandlerSuite) CreateApprovedSITDurationUpdate() { boatShipmentUpdater := boatshipment.NewBoatShipmentUpdater() mobileHomeShipmentUpdater := mobilehomeshipment.NewMobileHomeShipmentUpdater() - sitExtensionShipmentUpdater := shipmentorchestrator.NewShipmentUpdater(noCheckUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater) + sitExtensionShipmentUpdater := shipmentorchestrator.NewShipmentUpdater(noCheckUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater, nil) handler := CreateApprovedSITDurationUpdateHandler{ handlerConfig, @@ -3523,7 +3523,7 @@ func (suite *HandlerSuite) CreateApprovedSITDurationUpdate() { mobilehomeshipmentUpdater := mobilehomeshipment.NewMobileHomeShipmentUpdater() - sitExtensionShipmentUpdater := shipmentorchestrator.NewShipmentUpdater(noCheckUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobilehomeshipmentUpdater) + sitExtensionShipmentUpdater := shipmentorchestrator.NewShipmentUpdater(noCheckUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobilehomeshipmentUpdater, nil) handler := CreateApprovedSITDurationUpdateHandler{ handlerConfig, @@ -4593,7 +4593,7 @@ func (suite *HandlerSuite) TestUpdateShipmentHandler() { ppmShipmentUpdater := ppmshipment.NewPPMShipmentUpdater(&ppmEstimator, addressCreator, addressUpdater) boatShipmentUpdater := boatshipment.NewBoatShipmentUpdater() mobileHomeShipmentUpdater := mobilehomeshipment.NewMobileHomeShipmentUpdater() - shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater) + shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater, nil) handler := UpdateShipmentHandler{ suite.HandlerConfig(), shipmentUpdater, @@ -4661,7 +4661,7 @@ func (suite *HandlerSuite) TestUpdateShipmentHandler() { boatShipmentUpdater := boatshipment.NewBoatShipmentUpdater() mobileHomeShipmentUpdater := mobilehomeshipment.NewMobileHomeShipmentUpdater() - shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater) + shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater, nil) handler := UpdateShipmentHandler{ suite.HandlerConfig(), shipmentUpdater, @@ -4847,7 +4847,7 @@ func (suite *HandlerSuite) TestUpdateShipmentHandler() { boatShipmentUpdater := boatshipment.NewBoatShipmentUpdater() mobileHomeShipmentUpdater := mobilehomeshipment.NewMobileHomeShipmentUpdater() - shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater) + shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater, nil) handler := UpdateShipmentHandler{ suite.HandlerConfig(), shipmentUpdater, @@ -4927,7 +4927,7 @@ func (suite *HandlerSuite) TestUpdateShipmentHandler() { boatShipmentUpdater := boatshipment.NewBoatShipmentUpdater() mobileHomeShipmentUpdater := mobilehomeshipment.NewMobileHomeShipmentUpdater() - shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater) + shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater, nil) handler := UpdateShipmentHandler{ suite.HandlerConfig(), shipmentUpdater, @@ -5007,7 +5007,7 @@ func (suite *HandlerSuite) TestUpdateShipmentHandler() { boatShipmentUpdater := boatshipment.NewBoatShipmentUpdater() mobileHomeShipmentUpdater := mobilehomeshipment.NewMobileHomeShipmentUpdater() - shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater) + shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater, nil) handler := UpdateShipmentHandler{ suite.HandlerConfig(), shipmentUpdater, @@ -5045,7 +5045,7 @@ func (suite *HandlerSuite) TestUpdateShipmentHandler() { boatShipmentUpdater := boatshipment.NewBoatShipmentUpdater() mobileHomeShipmentUpdater := mobilehomeshipment.NewMobileHomeShipmentUpdater() - shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater) + shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater, nil) handler := UpdateShipmentHandler{ suite.HandlerConfig(), shipmentUpdater, @@ -5085,7 +5085,7 @@ func (suite *HandlerSuite) TestUpdateShipmentHandler() { boatShipmentUpdater := boatshipment.NewBoatShipmentUpdater() mobileHomeShipmentUpdater := mobilehomeshipment.NewMobileHomeShipmentUpdater() - shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater) + shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater, nil) handler := UpdateShipmentHandler{ suite.HandlerConfig(), shipmentUpdater, @@ -5126,7 +5126,7 @@ func (suite *HandlerSuite) TestUpdateShipmentHandler() { boatShipmentUpdater := boatshipment.NewBoatShipmentUpdater() mobileHomeShipmentUpdater := mobilehomeshipment.NewMobileHomeShipmentUpdater() - shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater) + shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater, nil) handler := UpdateShipmentHandler{ suite.HandlerConfig(), shipmentUpdater, @@ -5243,7 +5243,7 @@ func (suite *HandlerSuite) TestUpdateSITServiceItemCustomerExpenseHandler() { mock.Anything, false, ).Return(400, nil) - updater := mtoserviceitem.NewMTOServiceItemUpdater(planner, builder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher) + updater := mtoserviceitem.NewMTOServiceItemUpdater(planner, builder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) req := httptest.NewRequest("PATCH", fmt.Sprintf("/shipments/%s/sit-service-item/convert-to-customer-expense", approvedShipment.ID.String()), nil) req = suite.AuthenticateOfficeRequest(req, officeUser) handlerConfig := suite.HandlerConfig() @@ -5320,7 +5320,7 @@ func (suite *HandlerSuite) TestUpdateSITServiceItemCustomerExpenseHandler() { mock.Anything, false, ).Return(400, nil) - updater := mtoserviceitem.NewMTOServiceItemUpdater(planner, builder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher) + updater := mtoserviceitem.NewMTOServiceItemUpdater(planner, builder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) req := httptest.NewRequest("PATCH", fmt.Sprintf("/shipments/%s/sit-service-item/convert-to-customer-expense", approvedShipment.ID.String()), nil) req = suite.AuthenticateOfficeRequest(req, officeUser) handlerConfig := suite.HandlerConfig() diff --git a/pkg/handlers/ghcapi/queues.go b/pkg/handlers/ghcapi/queues.go index 202e5d3d6a0..4d708d415b4 100644 --- a/pkg/handlers/ghcapi/queues.go +++ b/pkg/handlers/ghcapi/queues.go @@ -183,6 +183,127 @@ func (h GetMovesQueueHandler) Handle(params queues.GetMovesQueueParams) middlewa }) } +// GetDestinationRequestsQueueHandler returns the moves for the TOO queue user via GET /queues/destination-requests +type GetDestinationRequestsQueueHandler struct { + handlers.HandlerConfig + services.OrderFetcher + services.MoveUnlocker + services.OfficeUserFetcherPop +} + +// Handle returns the paginated list of moves with destination requests for a TOO user +func (h GetDestinationRequestsQueueHandler) Handle(params queues.GetDestinationRequestsQueueParams) middleware.Responder { + return h.AuditableAppContextFromRequestWithErrors(params.HTTPRequest, + func(appCtx appcontext.AppContext) (middleware.Responder, error) { + if !appCtx.Session().IsOfficeUser() || + (!appCtx.Session().Roles.HasRole(roles.RoleTypeTOO)) { + forbiddenErr := apperror.NewForbiddenError( + "user is not authenticated with TOO role", + ) + appCtx.Logger().Error(forbiddenErr.Error()) + return queues.NewGetDestinationRequestsQueueForbidden(), forbiddenErr + } + + ListOrderParams := services.ListOrderParams{ + Branch: params.Branch, + Locator: params.Locator, + Edipi: params.Edipi, + Emplid: params.Emplid, + CustomerName: params.CustomerName, + DestinationDutyLocation: params.DestinationDutyLocation, + OriginDutyLocation: params.OriginDutyLocation, + AppearedInTOOAt: handlers.FmtDateTimePtrToPopPtr(params.AppearedInTooAt), + RequestedMoveDate: params.RequestedMoveDate, + Status: params.Status, + Page: params.Page, + PerPage: params.PerPage, + Sort: params.Sort, + Order: params.Order, + TOOAssignedUser: params.AssignedTo, + CounselingOffice: params.CounselingOffice, + } + + // we only care about moves in APPROVALS REQUESTED status + if params.Status == nil { + ListOrderParams.Status = []string{string(models.MoveStatusAPPROVALSREQUESTED)} + } + + // default pagination values + if params.Page == nil { + ListOrderParams.Page = models.Int64Pointer(1) + } + if params.PerPage == nil { + ListOrderParams.PerPage = models.Int64Pointer(20) + } + + moves, count, err := h.OrderFetcher.ListDestinationRequestsOrders( + appCtx, + appCtx.Session().OfficeUserID, + roles.RoleTypeTOO, + &ListOrderParams, + ) + if err != nil { + appCtx.Logger(). + Error("error fetching destinaton queue for office user", zap.Error(err)) + return queues.NewGetDestinationRequestsQueueInternalServerError(), err + } + + var officeUser models.OfficeUser + if appCtx.Session().OfficeUserID != uuid.Nil { + officeUser, err = h.OfficeUserFetcherPop.FetchOfficeUserByID(appCtx, appCtx.Session().OfficeUserID) + if err != nil { + appCtx.Logger().Error("Error retrieving office user", zap.Error(err)) + return queues.NewGetDestinationRequestsQueueInternalServerError(), err + } + } + privileges, err := models.FetchPrivilegesForUser(appCtx.DB(), appCtx.Session().UserID) + if err != nil { + appCtx.Logger().Error("Error retreiving user privileges", zap.Error(err)) + } + officeUser.User.Privileges = privileges + officeUser.User.Roles = appCtx.Session().Roles + + if err != nil { + appCtx.Logger(). + Error("error fetching office users", zap.Error(err)) + return queues.NewGetDestinationRequestsQueueInternalServerError(), err + } + + // if the TOO is accessing the queue, we need to unlock move/moves they have locked + if appCtx.Session().IsOfficeUser() { + officeUserID := appCtx.Session().OfficeUserID + for i, move := range moves { + lockedOfficeUserID := move.LockedByOfficeUserID + if lockedOfficeUserID != nil && *lockedOfficeUserID == officeUserID { + copyOfMove := move + unlockedMove, err := h.UnlockMove(appCtx, ©OfMove, officeUserID) + if err != nil { + return queues.NewGetDestinationRequestsQueueInternalServerError(), err + } + moves[i] = *unlockedMove + } + } + err := h.CheckForLockedMovesAndUnlock(appCtx, officeUserID) + if err != nil { + appCtx.Logger().Error(fmt.Sprintf("failed to unlock moves for office user ID: %s", officeUserID), zap.Error(err)) + } + } + + var activeRole string + officeUsers := models.OfficeUsers{officeUser} + queueMoves := payloads.QueueMoves(moves, officeUsers, nil, officeUser, nil, activeRole) + + result := &ghcmessages.QueueMovesResult{ + Page: *ListOrderParams.Page, + PerPage: *ListOrderParams.PerPage, + TotalCount: int64(count), + QueueMoves: *queueMoves, + } + + return queues.NewGetDestinationRequestsQueueOK().WithPayload(result), nil + }) +} + // ListMovesHandler lists moves with the option to filter since a particular date. Optimized ver. type ListPrimeMovesHandler struct { handlers.HandlerConfig diff --git a/pkg/handlers/ghcapi/queues_test.go b/pkg/handlers/ghcapi/queues_test.go index 6a658d6c2aa..d3d39152f8c 100644 --- a/pkg/handlers/ghcapi/queues_test.go +++ b/pkg/handlers/ghcapi/queues_test.go @@ -478,6 +478,11 @@ func (suite *HandlerSuite) TestGetMoveQueuesHandlerFilters() { Status: models.MTOServiceItemStatusSubmitted, }, }, + { + Model: models.ReService{ + Code: models.ReServiceCodeDOFSIT, + }, + }, }, nil) // Service Counseling Completed Move @@ -2249,3 +2254,144 @@ func (suite *HandlerSuite) TestAvailableOfficeUsers() { }) } + +func (suite *HandlerSuite) TestGetDestinationRequestsQueuesHandler() { + waf := entitlements.NewWeightAllotmentFetcher() + // default GBLOC is KKFA + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), factory.GetTraitActiveOfficeUser(), []roles.RoleType{roles.RoleTypeTOO}) + officeUser.User.Roles = append(officeUser.User.Roles, roles.Role{ + RoleType: roles.RoleTypeTOO, + }) + + postalCode := "90210" + postalCode2 := "73064" + factory.FetchOrBuildPostalCodeToGBLOC(suite.DB(), "90210", "KKFA") + factory.FetchOrBuildPostalCodeToGBLOC(suite.DB(), "73064", "JEAT") + + // setting up two moves, one we will see and the other we won't + move := factory.BuildAvailableToPrimeMove(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + Show: models.BoolPointer(true), + }, + }}, nil) + + destinationAddress := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{PostalCode: postalCode}, + }, + }, nil) + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + Status: models.MTOShipmentStatusApproved, + }, + }, + { + Model: move, + LinkOnly: true, + }, + { + Model: destinationAddress, + LinkOnly: true, + }, + }, nil) + + // destination service item in SUBMITTED status + factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: models.ReService{ + Code: models.ReServiceCodeDDFSIT, + }, + }, + { + Model: move, + LinkOnly: true, + }, + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusSubmitted, + }, + }, + }, nil) + + move2 := factory.BuildAvailableToPrimeMove(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + Show: models.BoolPointer(true), + }, + }}, nil) + + destinationAddress2 := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{PostalCode: postalCode2}, + }, + }, nil) + shipment2 := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + Status: models.MTOShipmentStatusApproved, + }, + }, + { + Model: move2, + LinkOnly: true, + }, + { + Model: destinationAddress2, + LinkOnly: true, + }, + }, nil) + + // destination shuttle + factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: models.ReService{ + Code: models.ReServiceCodeDDSHUT, + }, + }, + { + Model: move2, + LinkOnly: true, + }, + { + Model: shipment2, + LinkOnly: true, + }, + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusSubmitted, + }, + }, + }, nil) + + request := httptest.NewRequest("GET", "/queues/destination-requests", nil) + request = suite.AuthenticateOfficeRequest(request, officeUser) + params := queues.GetDestinationRequestsQueueParams{ + HTTPRequest: request, + } + handlerConfig := suite.HandlerConfig() + mockUnlocker := movelocker.NewMoveUnlocker() + handler := GetDestinationRequestsQueueHandler{ + handlerConfig, + order.NewOrderFetcher(waf), + mockUnlocker, + officeusercreator.NewOfficeUserFetcherPop(), + } + + response := handler.Handle(params) + suite.IsNotErrResponse(response) + suite.IsType(&queues.GetDestinationRequestsQueueOK{}, response) + payload := response.(*queues.GetDestinationRequestsQueueOK).Payload + + // should only have one move + result := payload.QueueMoves[0] + suite.Len(payload.QueueMoves, 1) + suite.Equal(move.ID.String(), result.ID.String()) +} diff --git a/pkg/handlers/internalapi/api.go b/pkg/handlers/internalapi/api.go index cbdba27433a..31010c7e3da 100644 --- a/pkg/handlers/internalapi/api.go +++ b/pkg/handlers/internalapi/api.go @@ -193,11 +193,23 @@ func NewInternalAPI(handlerConfig handlers.HandlerConfig) *internalops.MymoveAPI postalcodeservice.NewPostalCodeValidator(clock.New()), } + mtoServiceItemCreator := mtoserviceitem.NewMTOServiceItemCreator( + handlerConfig.HHGPlanner(), + builder, + moveRouter, + ghcrateengine.NewDomesticUnpackPricer(), + ghcrateengine.NewDomesticPackPricer(), + ghcrateengine.NewDomesticLinehaulPricer(), + ghcrateengine.NewDomesticShorthaulPricer(), + ghcrateengine.NewDomesticOriginPricer(), + ghcrateengine.NewDomesticDestinationPricer(), + ghcrateengine.NewFuelSurchargePricer()) + mtoShipmentCreator := mtoshipment.NewMTOShipmentCreatorV1(builder, fetcher, moveRouter, addressCreator) shipmentRouter := mtoshipment.NewShipmentRouter() moveTaskOrderUpdater := movetaskorder.NewMoveTaskOrderUpdater( builder, - mtoserviceitem.NewMTOServiceItemCreator(handlerConfig.HHGPlanner(), builder, moveRouter, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticPackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticShorthaulPricer(), ghcrateengine.NewDomesticOriginPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()), + mtoServiceItemCreator, moveRouter, signedCertificationCreator, signedCertificationUpdater, ppmEstimator, ) boatShipmentCreator := boatshipment.NewBoatShipmentCreator() @@ -233,6 +245,7 @@ func NewInternalAPI(handlerConfig handlers.HandlerConfig) *internalops.MymoveAPI ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater, + mtoServiceItemCreator, ) internalAPI.MtoShipmentUpdateMTOShipmentHandler = UpdateMTOShipmentHandler{ diff --git a/pkg/handlers/internalapi/mto_shipment_test.go b/pkg/handlers/internalapi/mto_shipment_test.go index bdd7770f408..2a06643c691 100644 --- a/pkg/handlers/internalapi/mto_shipment_test.go +++ b/pkg/handlers/internalapi/mto_shipment_test.go @@ -763,7 +763,7 @@ func (suite *HandlerSuite) TestUpdateMTOShipmentHandler() { boatShipmentUpdater := boatshipment.NewBoatShipmentUpdater() mobileHomeShipmentUpdater := mobilehomeshipment.NewMobileHomeShipmentUpdater() - shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater) + shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater, nil) authRequestAndSetUpHandlerAndParams := func(originalShipment models.MTOShipment, mockShipmentUpdater *mocks.ShipmentUpdater) (UpdateMTOShipmentHandler, mtoshipmentops.UpdateMTOShipmentParams) { endpoint := fmt.Sprintf("/mto-shipments/%s", originalShipment.ID.String()) diff --git a/pkg/handlers/primeapi/api.go b/pkg/handlers/primeapi/api.go index 2d8814925bc..4eab1923c9f 100644 --- a/pkg/handlers/primeapi/api.go +++ b/pkg/handlers/primeapi/api.go @@ -53,7 +53,7 @@ func NewPrimeAPI(handlerConfig handlers.HandlerConfig) *primeoperations.MymoveAP moveWeights := move.NewMoveWeights(mtoshipment.NewShipmentReweighRequester(), waf) uploadCreator := upload.NewUploadCreator(handlerConfig.FileStorer()) ppmEstimator := ppmshipment.NewEstimatePPM(handlerConfig.DTODPlanner(), &paymentrequesthelper.RequestPaymentHelper{}) - serviceItemUpdater := mtoserviceitem.NewMTOServiceItemUpdater(handlerConfig.HHGPlanner(), queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher) + serviceItemUpdater := mtoserviceitem.NewMTOServiceItemUpdater(handlerConfig.HHGPlanner(), queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) userUploader, err := uploader.NewUserUploader(handlerConfig.FileStorer(), uploader.MaxCustomerUserUploadFileSizeLimit) if err != nil { diff --git a/pkg/handlers/primeapi/move_task_order_test.go b/pkg/handlers/primeapi/move_task_order_test.go index cfbc270140d..f22e72e5e41 100644 --- a/pkg/handlers/primeapi/move_task_order_test.go +++ b/pkg/handlers/primeapi/move_task_order_test.go @@ -1329,7 +1329,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { suite.NotNil(payload.ETag()) }) - suite.Run("Success - return all MTOServiceItemShuttle fields assoicated with the getMoveTaskOrder", func() { + suite.Run("Success - return all MTOServiceItemDomesticShuttle fields assoicated with the getMoveTaskOrder", func() { handler := GetMoveTaskOrderHandler{ suite.HandlerConfig(), movetaskorder.NewMoveTaskOrderFetcher(waf), @@ -1396,14 +1396,14 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { json, err := json.Marshal(serviceItemPayload) suite.NoError(err) - payload := primemessages.MTOServiceItemShuttle{} + payload := primemessages.MTOServiceItemDomesticShuttle{} err = payload.UnmarshalJSON(json) suite.NoError(err) suite.Equal(serviceItem.MoveTaskOrderID.String(), payload.MoveTaskOrderID().String()) suite.Equal(serviceItem.MTOShipmentID.String(), payload.MtoShipmentID().String()) suite.Equal(serviceItem.ID.String(), payload.ID().String()) - suite.Equal("MTOServiceItemShuttle", string(payload.ModelType())) + suite.Equal("MTOServiceItemDomesticShuttle", string(payload.ModelType())) suite.Equal(string(serviceItem.ReService.Code), string(*payload.ReServiceCode)) suite.Equal(serviceItem.ReService.Name, payload.ReServiceName()) suite.Equal(string(serviceItem.Status), string(payload.Status())) diff --git a/pkg/handlers/primeapi/mto_service_item.go b/pkg/handlers/primeapi/mto_service_item.go index 646c6d17bfb..c78f37b6950 100644 --- a/pkg/handlers/primeapi/mto_service_item.go +++ b/pkg/handlers/primeapi/mto_service_item.go @@ -28,6 +28,7 @@ var CreateableServiceItemMap = map[primemessages.MTOServiceItemModelType]bool{ primemessages.MTOServiceItemModelTypeMTOServiceItemOriginSIT: true, primemessages.MTOServiceItemModelTypeMTOServiceItemDestSIT: true, primemessages.MTOServiceItemModelTypeMTOServiceItemShuttle: true, + primemessages.MTOServiceItemModelTypeMTOServiceItemDomesticShuttle: true, primemessages.MTOServiceItemModelTypeMTOServiceItemInternationalShuttle: true, primemessages.MTOServiceItemModelTypeMTOServiceItemDomesticCrating: true, primemessages.MTOServiceItemModelTypeMTOServiceItemInternationalCrating: true, diff --git a/pkg/handlers/primeapi/mto_service_item_test.go b/pkg/handlers/primeapi/mto_service_item_test.go index 362ca66f6be..319b3223705 100644 --- a/pkg/handlers/primeapi/mto_service_item_test.go +++ b/pkg/handlers/primeapi/mto_service_item_test.go @@ -1693,7 +1693,7 @@ func (suite *HandlerSuite) TestUpdateMTOServiceItemDDDSIT() { ).Return(400, nil) subtestData.handler = UpdateMTOServiceItemHandler{ suite.HandlerConfig(), - mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher), + mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()), } // create the params struct @@ -1977,7 +1977,7 @@ func (suite *HandlerSuite) TestUpdateMTOServiceItemDOPSIT() { ).Return(400, nil) subtestData.handler = UpdateMTOServiceItemHandler{ suite.HandlerConfig(), - mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher), + mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()), } // create the params struct diff --git a/pkg/handlers/primeapi/payloads/model_to_payload.go b/pkg/handlers/primeapi/payloads/model_to_payload.go index 3c8a3f1b292..5a675099271 100644 --- a/pkg/handlers/primeapi/payloads/model_to_payload.go +++ b/pkg/handlers/primeapi/payloads/model_to_payload.go @@ -814,7 +814,7 @@ func MTOServiceItem(mtoServiceItem *models.MTOServiceItem) primemessages.MTOServ payload = &cratingSI case models.ReServiceCodeDDSHUT, models.ReServiceCodeDOSHUT: - payload = &primemessages.MTOServiceItemShuttle{ + payload = &primemessages.MTOServiceItemDomesticShuttle{ ReServiceCode: handlers.FmtString(string(mtoServiceItem.ReService.Code)), Reason: mtoServiceItem.Reason, EstimatedWeight: handlers.FmtPoundPtr(mtoServiceItem.EstimatedWeight), diff --git a/pkg/handlers/primeapi/payloads/model_to_payload_test.go b/pkg/handlers/primeapi/payloads/model_to_payload_test.go index f070c4342d2..e54c61bd5fb 100644 --- a/pkg/handlers/primeapi/payloads/model_to_payload_test.go +++ b/pkg/handlers/primeapi/payloads/model_to_payload_test.go @@ -960,7 +960,7 @@ func (suite *PayloadsSuite) TestMTOServiceItemDDSHUT() { suite.NotNil(resultDDSHUT) - _, ok := resultDDSHUT.(*primemessages.MTOServiceItemShuttle) + _, ok := resultDDSHUT.(*primemessages.MTOServiceItemDomesticShuttle) suite.True(ok) } diff --git a/pkg/handlers/primeapi/payloads/payload_to_model.go b/pkg/handlers/primeapi/payloads/payload_to_model.go index 53362a88b84..77d0db1e1f6 100644 --- a/pkg/handlers/primeapi/payloads/payload_to_model.go +++ b/pkg/handlers/primeapi/payloads/payload_to_model.go @@ -537,6 +537,14 @@ func MTOServiceItemModel(mtoServiceItem primemessages.MTOServiceItem) (*models.M model.EstimatedWeight = handlers.PoundPtrFromInt64Ptr(shuttleService.EstimatedWeight) model.ActualWeight = handlers.PoundPtrFromInt64Ptr(shuttleService.ActualWeight) + case primemessages.MTOServiceItemModelTypeMTOServiceItemDomesticShuttle: + shuttleService := mtoServiceItem.(*primemessages.MTOServiceItemDomesticShuttle) + // values to get from payload + model.ReService.Code = models.ReServiceCode(*shuttleService.ReServiceCode) + model.Reason = shuttleService.Reason + model.EstimatedWeight = handlers.PoundPtrFromInt64Ptr(shuttleService.EstimatedWeight) + model.ActualWeight = handlers.PoundPtrFromInt64Ptr(shuttleService.ActualWeight) + case primemessages.MTOServiceItemModelTypeMTOServiceItemInternationalShuttle: shuttleService := mtoServiceItem.(*primemessages.MTOServiceItemInternationalShuttle) // values to get from payload diff --git a/pkg/handlers/primeapi/payloads/payload_to_model_test.go b/pkg/handlers/primeapi/payloads/payload_to_model_test.go index 9b5ec6f69a5..d45071aa7fa 100644 --- a/pkg/handlers/primeapi/payloads/payload_to_model_test.go +++ b/pkg/handlers/primeapi/payloads/payload_to_model_test.go @@ -65,7 +65,7 @@ func (suite *PayloadsSuite) TestMTOServiceItemModel() { DCRTServiceItem.SetMoveTaskOrderID(handlers.FmtUUID(moveTaskOrderIDField)) DCRTServiceItem.SetMtoShipmentID(*mtoShipmentIDString) - DDSHUTServiceItem := &primemessages.MTOServiceItemShuttle{ + DDSHUTServiceItem := &primemessages.MTOServiceItemDomesticShuttle{ ReServiceCode: &ddshutCode, Reason: &reason, EstimatedWeight: &estimatedWeight, @@ -74,7 +74,7 @@ func (suite *PayloadsSuite) TestMTOServiceItemModel() { DDSHUTServiceItem.SetMoveTaskOrderID(handlers.FmtUUID(moveTaskOrderIDField)) DDSHUTServiceItem.SetMtoShipmentID(*mtoShipmentIDString) - DOSHUTServiceItem := &primemessages.MTOServiceItemShuttle{ + DOSHUTServiceItem := &primemessages.MTOServiceItemDomesticShuttle{ ReServiceCode: &doshutCode, Reason: &reason, EstimatedWeight: &estimatedWeight, diff --git a/pkg/handlers/primeapiv2/api.go b/pkg/handlers/primeapiv2/api.go index 2d22387e986..44e8ca916ef 100644 --- a/pkg/handlers/primeapiv2/api.go +++ b/pkg/handlers/primeapiv2/api.go @@ -53,9 +53,21 @@ func NewPrimeAPI(handlerConfig handlers.HandlerConfig) *primev2operations.Mymove signedCertificationUpdater := signedcertification.NewSignedCertificationUpdater() ppmEstimator := ppmshipment.NewEstimatePPM(handlerConfig.DTODPlanner(), &paymentrequesthelper.RequestPaymentHelper{}) + mtoServiceItemCreator := mtoserviceitem.NewMTOServiceItemCreator( + handlerConfig.HHGPlanner(), + queryBuilder, + moveRouter, + ghcrateengine.NewDomesticUnpackPricer(), + ghcrateengine.NewDomesticPackPricer(), + ghcrateengine.NewDomesticLinehaulPricer(), + ghcrateengine.NewDomesticShorthaulPricer(), + ghcrateengine.NewDomesticOriginPricer(), + ghcrateengine.NewDomesticDestinationPricer(), + ghcrateengine.NewFuelSurchargePricer()) + moveTaskOrderUpdater := movetaskorder.NewMoveTaskOrderUpdater( queryBuilder, - mtoserviceitem.NewMTOServiceItemCreator(handlerConfig.HHGPlanner(), queryBuilder, moveRouter, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticPackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticShorthaulPricer(), ghcrateengine.NewDomesticOriginPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()), + mtoServiceItemCreator, moveRouter, signedCertificationCreator, signedCertificationUpdater, ppmEstimator, ) @@ -97,10 +109,11 @@ func NewPrimeAPI(handlerConfig handlers.HandlerConfig) *primev2operations.Mymove ppmShipmentUpdater := ppmshipment.NewPPMShipmentUpdater(ppmEstimator, addressCreator, addressUpdater) boatShipmentUpdater := boatshipment.NewBoatShipmentUpdater() mobileHomeShipmentUpdater := mobilehomeshipment.NewMobileHomeShipmentUpdater() - shipmentUpdater := shipment.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater) + shipmentUpdater := shipment.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater, mtoServiceItemCreator) primeAPIV2.MtoShipmentUpdateMTOShipmentHandler = UpdateMTOShipmentHandler{ handlerConfig, shipmentUpdater, + handlerConfig.DTODPlanner(), } return primeAPIV2 diff --git a/pkg/handlers/primeapiv2/move_task_order_test.go b/pkg/handlers/primeapiv2/move_task_order_test.go index 0b4fc4c56d8..f173af34bda 100644 --- a/pkg/handlers/primeapiv2/move_task_order_test.go +++ b/pkg/handlers/primeapiv2/move_task_order_test.go @@ -1190,7 +1190,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { suite.NotNil(payload.ETag()) }) - suite.Run("Success - return all MTOServiceItemShuttle fields assoicated with the getMoveTaskOrder", func() { + suite.Run("Success - return all MTOServiceItemDomesticShuttle fields assoicated with the getMoveTaskOrder", func() { handler := GetMoveTaskOrderHandler{ suite.HandlerConfig(), movetaskorder.NewMoveTaskOrderFetcher(waf), @@ -1257,14 +1257,14 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { json, err := json.Marshal(serviceItemPayload) suite.NoError(err) - payload := primev2messages.MTOServiceItemShuttle{} + payload := primev2messages.MTOServiceItemDomesticShuttle{} err = payload.UnmarshalJSON(json) suite.NoError(err) suite.Equal(serviceItem.MoveTaskOrderID.String(), payload.MoveTaskOrderID().String()) suite.Equal(serviceItem.MTOShipmentID.String(), payload.MtoShipmentID().String()) suite.Equal(serviceItem.ID.String(), payload.ID().String()) - suite.Equal("MTOServiceItemShuttle", string(payload.ModelType())) + suite.Equal("MTOServiceItemDomesticShuttle", string(payload.ModelType())) suite.Equal(string(serviceItem.ReService.Code), string(*payload.ReServiceCode)) suite.Equal(serviceItem.ReService.Name, payload.ReServiceName()) suite.Equal(string(serviceItem.Status), string(payload.Status())) diff --git a/pkg/handlers/primeapiv2/mto_service_item.go b/pkg/handlers/primeapiv2/mto_service_item.go index 5188ccea511..495a597b88f 100644 --- a/pkg/handlers/primeapiv2/mto_service_item.go +++ b/pkg/handlers/primeapiv2/mto_service_item.go @@ -26,6 +26,7 @@ var CreateableServiceItemMap = map[primev2messages.MTOServiceItemModelType]bool{ primev2messages.MTOServiceItemModelTypeMTOServiceItemOriginSIT: true, primev2messages.MTOServiceItemModelTypeMTOServiceItemDestSIT: true, primev2messages.MTOServiceItemModelTypeMTOServiceItemShuttle: true, + primev2messages.MTOServiceItemModelTypeMTOServiceItemDomesticShuttle: true, primev2messages.MTOServiceItemModelTypeMTOServiceItemInternationalShuttle: true, primev2messages.MTOServiceItemModelTypeMTOServiceItemDomesticCrating: true, primev2messages.MTOServiceItemModelTypeMTOServiceItemInternationalCrating: true, diff --git a/pkg/handlers/primeapiv2/mto_shipment.go b/pkg/handlers/primeapiv2/mto_shipment.go index 35672bbc02e..d4a5e5012da 100644 --- a/pkg/handlers/primeapiv2/mto_shipment.go +++ b/pkg/handlers/primeapiv2/mto_shipment.go @@ -17,6 +17,7 @@ import ( "github.com/transcom/mymove/pkg/handlers/primeapi" "github.com/transcom/mymove/pkg/handlers/primeapiv2/payloads" "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/route" "github.com/transcom/mymove/pkg/services" mtoshipment "github.com/transcom/mymove/pkg/services/mto_shipment" ) @@ -169,6 +170,7 @@ func (h CreateMTOShipmentHandler) Handle(params mtoshipmentops.CreateMTOShipment type UpdateMTOShipmentHandler struct { handlers.HandlerConfig services.ShipmentUpdater + planner route.Planner } // Handle handler that updates a mto shipment diff --git a/pkg/handlers/primeapiv2/payloads/model_to_payload.go b/pkg/handlers/primeapiv2/payloads/model_to_payload.go index 106a3bd5cf3..09f107a9e04 100644 --- a/pkg/handlers/primeapiv2/payloads/model_to_payload.go +++ b/pkg/handlers/primeapiv2/payloads/model_to_payload.go @@ -725,7 +725,7 @@ func MTOServiceItem(mtoServiceItem *models.MTOServiceItem) primev2messages.MTOSe payload = &cratingSI case models.ReServiceCodeDDSHUT, models.ReServiceCodeDOSHUT: - payload = &primev2messages.MTOServiceItemShuttle{ + payload = &primev2messages.MTOServiceItemDomesticShuttle{ ReServiceCode: handlers.FmtString(string(mtoServiceItem.ReService.Code)), Reason: mtoServiceItem.Reason, EstimatedWeight: handlers.FmtPoundPtr(mtoServiceItem.EstimatedWeight), diff --git a/pkg/handlers/primeapiv2/payloads/model_to_payload_test.go b/pkg/handlers/primeapiv2/payloads/model_to_payload_test.go index 7b68e2a8e69..2119ecd1a8e 100644 --- a/pkg/handlers/primeapiv2/payloads/model_to_payload_test.go +++ b/pkg/handlers/primeapiv2/payloads/model_to_payload_test.go @@ -870,7 +870,7 @@ func (suite *PayloadsSuite) TestMTOServiceItemDDSHUT() { suite.NotNil(resultDDSHUT) - _, ok := resultDDSHUT.(*primev2messages.MTOServiceItemShuttle) + _, ok := resultDDSHUT.(*primev2messages.MTOServiceItemDomesticShuttle) suite.True(ok) } diff --git a/pkg/handlers/primeapiv2/payloads/payload_to_model.go b/pkg/handlers/primeapiv2/payloads/payload_to_model.go index b628bcc0502..b57e5ca541b 100644 --- a/pkg/handlers/primeapiv2/payloads/payload_to_model.go +++ b/pkg/handlers/primeapiv2/payloads/payload_to_model.go @@ -621,7 +621,6 @@ func MTOServiceItemModel(mtoServiceItem primev2messages.MTOServiceItem) (*models if model.SITDestinationFinalAddress != nil { model.SITDestinationFinalAddressID = &model.SITDestinationFinalAddress.ID } - case primev2messages.MTOServiceItemModelTypeMTOServiceItemShuttle: shuttleService := mtoServiceItem.(*primev2messages.MTOServiceItemShuttle) // values to get from payload @@ -630,6 +629,14 @@ func MTOServiceItemModel(mtoServiceItem primev2messages.MTOServiceItem) (*models model.EstimatedWeight = handlers.PoundPtrFromInt64Ptr(shuttleService.EstimatedWeight) model.ActualWeight = handlers.PoundPtrFromInt64Ptr(shuttleService.ActualWeight) + case primev2messages.MTOServiceItemModelTypeMTOServiceItemDomesticShuttle: + shuttleService := mtoServiceItem.(*primev2messages.MTOServiceItemDomesticShuttle) + // values to get from payload + model.ReService.Code = models.ReServiceCode(*shuttleService.ReServiceCode) + model.Reason = shuttleService.Reason + model.EstimatedWeight = handlers.PoundPtrFromInt64Ptr(shuttleService.EstimatedWeight) + model.ActualWeight = handlers.PoundPtrFromInt64Ptr(shuttleService.ActualWeight) + case primev2messages.MTOServiceItemModelTypeMTOServiceItemInternationalShuttle: shuttleService := mtoServiceItem.(*primev2messages.MTOServiceItemInternationalShuttle) // values to get from payload diff --git a/pkg/handlers/primeapiv2/payloads/payload_to_model_test.go b/pkg/handlers/primeapiv2/payloads/payload_to_model_test.go index 3df180b58ea..5a1e7844ab6 100644 --- a/pkg/handlers/primeapiv2/payloads/payload_to_model_test.go +++ b/pkg/handlers/primeapiv2/payloads/payload_to_model_test.go @@ -52,7 +52,7 @@ func (suite *PayloadsSuite) TestMTOServiceItemModel() { Length: &crateMeasurement, } - DDSHUTServiceItem := &primev2messages.MTOServiceItemShuttle{ + DDSHUTServiceItem := &primev2messages.MTOServiceItemDomesticShuttle{ ReServiceCode: &ddshutCode, Reason: &reason, EstimatedWeight: &estimatedWeight, @@ -61,7 +61,7 @@ func (suite *PayloadsSuite) TestMTOServiceItemModel() { DDSHUTServiceItem.SetMoveTaskOrderID(handlers.FmtUUID(moveTaskOrderIDField)) DDSHUTServiceItem.SetMtoShipmentID(*mtoShipmentIDString) - DOSHUTServiceItem := &primev2messages.MTOServiceItemShuttle{ + DOSHUTServiceItem := &primev2messages.MTOServiceItemDomesticShuttle{ ReServiceCode: &doshutCode, Reason: &reason, EstimatedWeight: &estimatedWeight, diff --git a/pkg/handlers/primeapiv3/api.go b/pkg/handlers/primeapiv3/api.go index 1662d151464..e46f49c8ad1 100644 --- a/pkg/handlers/primeapiv3/api.go +++ b/pkg/handlers/primeapiv3/api.go @@ -95,13 +95,26 @@ func NewPrimeAPI(handlerConfig handlers.HandlerConfig) *primev3operations.Mymove addressCreator, ) + mtoServiceItemCreator := mtoserviceitem.NewMTOServiceItemCreator( + handlerConfig.HHGPlanner(), + queryBuilder, + moveRouter, + ghcrateengine.NewDomesticUnpackPricer(), + ghcrateengine.NewDomesticPackPricer(), + ghcrateengine.NewDomesticLinehaulPricer(), + ghcrateengine.NewDomesticShorthaulPricer(), + ghcrateengine.NewDomesticOriginPricer(), + ghcrateengine.NewDomesticDestinationPricer(), + ghcrateengine.NewFuelSurchargePricer()) + ppmShipmentUpdater := ppmshipment.NewPPMShipmentUpdater(ppmEstimator, addressCreator, addressUpdater) boatShipmentUpdater := boatshipment.NewBoatShipmentUpdater() mobileHomeShipmentUpdater := mobilehomeshipment.NewMobileHomeShipmentUpdater() - shipmentUpdater := shipment.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater) + shipmentUpdater := shipment.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater, mtoServiceItemCreator) primeAPIV3.MtoShipmentUpdateMTOShipmentHandler = UpdateMTOShipmentHandler{ handlerConfig, shipmentUpdater, + handlerConfig.DTODPlanner(), } return primeAPIV3 diff --git a/pkg/handlers/primeapiv3/move_task_order_test.go b/pkg/handlers/primeapiv3/move_task_order_test.go index 45b39cef0e4..06880f84230 100644 --- a/pkg/handlers/primeapiv3/move_task_order_test.go +++ b/pkg/handlers/primeapiv3/move_task_order_test.go @@ -1169,7 +1169,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { suite.NotNil(payload.ETag()) }) - suite.Run("Success - return all MTOServiceItemShuttle fields assoicated with the getMoveTaskOrder", func() { + suite.Run("Success - return all MTOServiceItemDomesticShuttle fields assoicated with the getMoveTaskOrder", func() { handler := setupDefaultTestHandler() successMove := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) @@ -1233,14 +1233,14 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { json, err := json.Marshal(serviceItemPayload) suite.NoError(err) - payload := primev3messages.MTOServiceItemShuttle{} + payload := primev3messages.MTOServiceItemDomesticShuttle{} err = payload.UnmarshalJSON(json) suite.NoError(err) suite.Equal(serviceItem.MoveTaskOrderID.String(), payload.MoveTaskOrderID().String()) suite.Equal(serviceItem.MTOShipmentID.String(), payload.MtoShipmentID().String()) suite.Equal(serviceItem.ID.String(), payload.ID().String()) - suite.Equal("MTOServiceItemShuttle", string(payload.ModelType())) + suite.Equal("MTOServiceItemDomesticShuttle", string(payload.ModelType())) suite.Equal(string(serviceItem.ReService.Code), string(*payload.ReServiceCode)) suite.Equal(serviceItem.ReService.Name, payload.ReServiceName()) suite.Equal(string(serviceItem.Status), string(payload.Status())) diff --git a/pkg/handlers/primeapiv3/mto_service_item.go b/pkg/handlers/primeapiv3/mto_service_item.go index f3c16b46e60..d3ab85fac3b 100644 --- a/pkg/handlers/primeapiv3/mto_service_item.go +++ b/pkg/handlers/primeapiv3/mto_service_item.go @@ -26,6 +26,7 @@ var CreateableServiceItemMap = map[primev3messages.MTOServiceItemModelType]bool{ primev3messages.MTOServiceItemModelTypeMTOServiceItemOriginSIT: true, primev3messages.MTOServiceItemModelTypeMTOServiceItemDestSIT: true, primev3messages.MTOServiceItemModelTypeMTOServiceItemShuttle: true, + primev3messages.MTOServiceItemModelTypeMTOServiceItemDomesticShuttle: true, primev3messages.MTOServiceItemModelTypeMTOServiceItemInternationalShuttle: true, primev3messages.MTOServiceItemModelTypeMTOServiceItemDomesticCrating: true, primev3messages.MTOServiceItemModelTypeMTOServiceItemInternationalCrating: true, diff --git a/pkg/handlers/primeapiv3/mto_shipment.go b/pkg/handlers/primeapiv3/mto_shipment.go index cddeeaab45b..d2f6221ac9f 100644 --- a/pkg/handlers/primeapiv3/mto_shipment.go +++ b/pkg/handlers/primeapiv3/mto_shipment.go @@ -17,6 +17,7 @@ import ( "github.com/transcom/mymove/pkg/handlers/primeapi" "github.com/transcom/mymove/pkg/handlers/primeapiv3/payloads" "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/route" "github.com/transcom/mymove/pkg/services" mtoshipment "github.com/transcom/mymove/pkg/services/mto_shipment" ) @@ -169,6 +170,7 @@ func (h CreateMTOShipmentHandler) Handle(params mtoshipmentops.CreateMTOShipment type UpdateMTOShipmentHandler struct { handlers.HandlerConfig services.ShipmentUpdater + planner route.Planner } // Handle handler that updates a mto shipment diff --git a/pkg/handlers/primeapiv3/mto_shipment_test.go b/pkg/handlers/primeapiv3/mto_shipment_test.go index 8bcef3ec944..085d9eab254 100644 --- a/pkg/handlers/primeapiv3/mto_shipment_test.go +++ b/pkg/handlers/primeapiv3/mto_shipment_test.go @@ -86,10 +86,10 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { return mockUpdater } - + mtoServiceItemCreator := mtoserviceitem.NewMTOServiceItemCreator(planner, builder, moveRouter, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticPackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticShorthaulPricer(), ghcrateengine.NewDomesticOriginPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) moveTaskOrderUpdater := movetaskorder.NewMoveTaskOrderUpdater( builder, - mtoserviceitem.NewMTOServiceItemCreator(planner, builder, moveRouter, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticPackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticShorthaulPricer(), ghcrateengine.NewDomesticOriginPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()), + mtoServiceItemCreator, moveRouter, setUpSignedCertificationCreatorMock(nil, nil), setUpSignedCertificationUpdaterMock(nil, nil), &ppmEstimator, ) shipmentCreator := shipmentorchestrator.NewShipmentCreator(mtoShipmentCreator, ppmShipmentCreator, boatShipmentCreator, mobileHomeShipmentCreator, shipmentRouter, moveTaskOrderUpdater) @@ -112,7 +112,7 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { boatShipmentUpdater := boatshipment.NewBoatShipmentUpdater() mobileHomeShipmentUpdater := mobilehomeshipment.NewMobileHomeShipmentUpdater() mtoShipmentUpdater := mtoshipment.NewPrimeMTOShipmentUpdater(builder, fetcher, planner, moveRouter, moveWeights, suite.TestNotificationSender(), paymentRequestShipmentRecalculator, addressUpdater, addressCreator) - shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater) + shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater, mtoServiceItemCreator) setupTestData := func(boatFeatureFlag bool, ubFeatureFlag bool) (CreateMTOShipmentHandler, models.Move) { @@ -551,6 +551,7 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { patchHandler := UpdateMTOShipmentHandler{ suite.HandlerConfig(), shipmentUpdater, + planner, } patchReq := httptest.NewRequest("PATCH", fmt.Sprintf("/mto-shipments/%s", createdPPM.ShipmentID.String()), nil) @@ -832,6 +833,7 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { patchHandler := UpdateMTOShipmentHandler{ suite.HandlerConfig(), shipmentUpdater, + planner, } patchReq := httptest.NewRequest("PATCH", fmt.Sprintf("/mto-shipments/%s", createdPPM.ShipmentID.String()), nil) @@ -1547,10 +1549,11 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { // Setup: If underlying CreateMTOShipment returns error, handler should return 422 response // Expected: 422 Response returned - shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater) + shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater, mtoServiceItemCreator) patchHandler := UpdateMTOShipmentHandler{ suite.HandlerConfig(), shipmentUpdater, + planner, } now := time.Now() @@ -1622,10 +1625,11 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { // Setup: If underlying UpdateMTOShipment returns error, handler should return 422 response // Expected: 422 Response returned - shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater) + shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater, mtoServiceItemCreator) patchHandler := UpdateMTOShipmentHandler{ suite.HandlerConfig(), shipmentUpdater, + planner, } move := factory.BuildAvailableToPrimeMove(suite.DB(), []factory.Customization{}, nil) @@ -1670,10 +1674,11 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { // Setup: If underlying CreateMTOShipment returns error, handler should return 422 response // Expected: 422 Response returned - shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater) + shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater, mtoServiceItemCreator) patchHandler := UpdateMTOShipmentHandler{ suite.HandlerConfig(), shipmentUpdater, + planner, } now := time.Now() diff --git a/pkg/handlers/primeapiv3/payloads/model_to_payload.go b/pkg/handlers/primeapiv3/payloads/model_to_payload.go index 971db88dc97..aba7a9c1718 100644 --- a/pkg/handlers/primeapiv3/payloads/model_to_payload.go +++ b/pkg/handlers/primeapiv3/payloads/model_to_payload.go @@ -868,7 +868,7 @@ func MTOServiceItem(mtoServiceItem *models.MTOServiceItem) primev3messages.MTOSe payload = &cratingSI case models.ReServiceCodeDDSHUT, models.ReServiceCodeDOSHUT: - payload = &primev3messages.MTOServiceItemShuttle{ + payload = &primev3messages.MTOServiceItemDomesticShuttle{ ReServiceCode: handlers.FmtString(string(mtoServiceItem.ReService.Code)), Reason: mtoServiceItem.Reason, EstimatedWeight: handlers.FmtPoundPtr(mtoServiceItem.EstimatedWeight), diff --git a/pkg/handlers/primeapiv3/payloads/model_to_payload_test.go b/pkg/handlers/primeapiv3/payloads/model_to_payload_test.go index a5a2d1d8d15..f0f7036eac6 100644 --- a/pkg/handlers/primeapiv3/payloads/model_to_payload_test.go +++ b/pkg/handlers/primeapiv3/payloads/model_to_payload_test.go @@ -1147,7 +1147,7 @@ func (suite *PayloadsSuite) TestMTOServiceItemDDSHUT() { suite.NotNil(resultDDSHUT) - _, ok := resultDDSHUT.(*primev3messages.MTOServiceItemShuttle) + _, ok := resultDDSHUT.(*primev3messages.MTOServiceItemDomesticShuttle) suite.True(ok) } diff --git a/pkg/handlers/primeapiv3/payloads/payload_to_model.go b/pkg/handlers/primeapiv3/payloads/payload_to_model.go index f33d8b2ff34..2acc20eb04a 100644 --- a/pkg/handlers/primeapiv3/payloads/payload_to_model.go +++ b/pkg/handlers/primeapiv3/payloads/payload_to_model.go @@ -786,7 +786,6 @@ func MTOServiceItemModel(mtoServiceItem primev3messages.MTOServiceItem) (*models if model.SITDestinationFinalAddress != nil { model.SITDestinationFinalAddressID = &model.SITDestinationFinalAddress.ID } - case primev3messages.MTOServiceItemModelTypeMTOServiceItemShuttle: shuttleService := mtoServiceItem.(*primev3messages.MTOServiceItemShuttle) // values to get from payload @@ -795,6 +794,14 @@ func MTOServiceItemModel(mtoServiceItem primev3messages.MTOServiceItem) (*models model.EstimatedWeight = handlers.PoundPtrFromInt64Ptr(shuttleService.EstimatedWeight) model.ActualWeight = handlers.PoundPtrFromInt64Ptr(shuttleService.ActualWeight) + case primev3messages.MTOServiceItemModelTypeMTOServiceItemDomesticShuttle: + shuttleService := mtoServiceItem.(*primev3messages.MTOServiceItemDomesticShuttle) + // values to get from payload + model.ReService.Code = models.ReServiceCode(*shuttleService.ReServiceCode) + model.Reason = shuttleService.Reason + model.EstimatedWeight = handlers.PoundPtrFromInt64Ptr(shuttleService.EstimatedWeight) + model.ActualWeight = handlers.PoundPtrFromInt64Ptr(shuttleService.ActualWeight) + case primev3messages.MTOServiceItemModelTypeMTOServiceItemInternationalShuttle: shuttleService := mtoServiceItem.(*primev3messages.MTOServiceItemInternationalShuttle) // values to get from payload diff --git a/pkg/handlers/primeapiv3/payloads/payload_to_model_test.go b/pkg/handlers/primeapiv3/payloads/payload_to_model_test.go index fd9430379f0..4f12b050b83 100644 --- a/pkg/handlers/primeapiv3/payloads/payload_to_model_test.go +++ b/pkg/handlers/primeapiv3/payloads/payload_to_model_test.go @@ -64,7 +64,7 @@ func (suite *PayloadsSuite) TestMTOServiceItemModel() { DCRTServiceItem.SetMoveTaskOrderID(handlers.FmtUUID(moveTaskOrderIDField)) DCRTServiceItem.SetMtoShipmentID(*mtoShipmentIDString) - DDSHUTServiceItem := &primev3messages.MTOServiceItemShuttle{ + DDSHUTServiceItem := &primev3messages.MTOServiceItemDomesticShuttle{ ReServiceCode: &ddshutCode, Reason: &reason, EstimatedWeight: &estimatedWeight, @@ -73,7 +73,7 @@ func (suite *PayloadsSuite) TestMTOServiceItemModel() { DDSHUTServiceItem.SetMoveTaskOrderID(handlers.FmtUUID(moveTaskOrderIDField)) DDSHUTServiceItem.SetMtoShipmentID(*mtoShipmentIDString) - DOSHUTServiceItem := &primev3messages.MTOServiceItemShuttle{ + DOSHUTServiceItem := &primev3messages.MTOServiceItemDomesticShuttle{ ReServiceCode: &doshutCode, Reason: &reason, EstimatedWeight: &estimatedWeight, diff --git a/pkg/handlers/supportapi/api.go b/pkg/handlers/supportapi/api.go index f9970af0553..e60ec489162 100644 --- a/pkg/handlers/supportapi/api.go +++ b/pkg/handlers/supportapi/api.go @@ -95,7 +95,7 @@ func NewSupportAPIHandler(handlerConfig handlers.HandlerConfig) http.Handler { mtoserviceitem.NewMTOServiceItemCreator(handlerConfig.HHGPlanner(), queryBuilder, moveRouter, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticPackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticShorthaulPricer(), ghcrateengine.NewDomesticOriginPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()), handlerConfig.HHGPlanner()), } - supportAPI.MtoServiceItemUpdateMTOServiceItemStatusHandler = UpdateMTOServiceItemStatusHandler{handlerConfig, mtoserviceitem.NewMTOServiceItemUpdater(handlerConfig.HHGPlanner(), queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher)} + supportAPI.MtoServiceItemUpdateMTOServiceItemStatusHandler = UpdateMTOServiceItemStatusHandler{handlerConfig, mtoserviceitem.NewMTOServiceItemUpdater(handlerConfig.HHGPlanner(), queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer())} supportAPI.WebhookReceiveWebhookNotificationHandler = ReceiveWebhookNotificationHandler{handlerConfig} // Create TAC and LOA services diff --git a/pkg/handlers/supportapi/internal/payloads/model_to_payload.go b/pkg/handlers/supportapi/internal/payloads/model_to_payload.go index a43c59663a7..83d481d125a 100644 --- a/pkg/handlers/supportapi/internal/payloads/model_to_payload.go +++ b/pkg/handlers/supportapi/internal/payloads/model_to_payload.go @@ -351,12 +351,34 @@ func MTOServiceItem(mtoServiceItem *models.MTOServiceItem) supportmessages.MTOSe StandaloneCrate: mtoServiceItem.StandaloneCrate, } case models.ReServiceCodeDDSHUT, models.ReServiceCodeDOSHUT: - payload = &supportmessages.MTOServiceItemShuttle{ + payload = &supportmessages.MTOServiceItemDomesticShuttle{ ReServiceCode: handlers.FmtString(string(mtoServiceItem.ReService.Code)), Reason: mtoServiceItem.Reason, EstimatedWeight: handlers.FmtPoundPtr(mtoServiceItem.EstimatedWeight), ActualWeight: handlers.FmtPoundPtr(mtoServiceItem.ActualWeight), } + case models.ReServiceCodeIDSHUT, models.ReServiceCodeIOSHUT: + market := models.MarketConus.FullString() + + if mtoServiceItem.ReService.Code == models.ReServiceCodeIOSHUT && mtoServiceItem.MTOShipment.PickupAddress != nil { + if *mtoServiceItem.MTOShipment.PickupAddress.IsOconus { + market = models.MarketOconus.FullString() + } + } + + if mtoServiceItem.ReService.Code == models.ReServiceCodeIDSHUT && mtoServiceItem.MTOShipment.DestinationAddress != nil { + if *mtoServiceItem.MTOShipment.DestinationAddress.IsOconus { + market = models.MarketOconus.FullString() + } + } + + payload = &supportmessages.MTOServiceItemInternationalShuttle{ + ReServiceCode: handlers.FmtString(string(mtoServiceItem.ReService.Code)), + Reason: mtoServiceItem.Reason, + EstimatedWeight: handlers.FmtPoundPtr(mtoServiceItem.EstimatedWeight), + ActualWeight: handlers.FmtPoundPtr(mtoServiceItem.ActualWeight), + Market: market, + } default: // otherwise, basic service item payload = &supportmessages.MTOServiceItemBasic{ diff --git a/pkg/handlers/supportapi/mto_service_item_test.go b/pkg/handlers/supportapi/mto_service_item_test.go index 0e3faa635cd..0837679cb04 100644 --- a/pkg/handlers/supportapi/mto_service_item_test.go +++ b/pkg/handlers/supportapi/mto_service_item_test.go @@ -22,6 +22,7 @@ import ( "github.com/transcom/mymove/pkg/models" routemocks "github.com/transcom/mymove/pkg/route/mocks" "github.com/transcom/mymove/pkg/services/address" + "github.com/transcom/mymove/pkg/services/ghcrateengine" moverouter "github.com/transcom/mymove/pkg/services/move" mtoserviceitem "github.com/transcom/mymove/pkg/services/mto_service_item" mtoshipment "github.com/transcom/mymove/pkg/services/mto_shipment" @@ -88,7 +89,7 @@ func (suite *HandlerSuite) TestUpdateMTOServiceItemStatusHandlerApproveSuccess() false, ).Return(400, nil) handler := UpdateMTOServiceItemStatusHandler{handlerConfig, - mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher), + mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()), } // CALL FUNCTION UNDER TEST @@ -146,7 +147,7 @@ func (suite *HandlerSuite) TestUpdateMTOServiceItemStatusHandlerRejectSuccess() false, ).Return(400, nil) handler := UpdateMTOServiceItemStatusHandler{handlerConfig, - mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher), + mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()), } // CALL FUNCTION UNDER TEST @@ -204,7 +205,7 @@ func (suite *HandlerSuite) TestUpdateMTOServiceItemStatusHandlerRejectionFailedN false, ).Return(400, nil) handler := UpdateMTOServiceItemStatusHandler{handlerConfig, - mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher), + mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()), } // CALL FUNCTION UNDER TEST diff --git a/pkg/iws/rbs_error_test.go b/pkg/iws/rbs_error_test.go new file mode 100644 index 00000000000..022ab8400b7 --- /dev/null +++ b/pkg/iws/rbs_error_test.go @@ -0,0 +1,15 @@ +package iws + +func (suite *iwsSuite) TestRbsError() { + data := ` + + 14030 + Problem with this argument: EMA_TX + ` + _, _, _, err := parseWkEmaResponse([]byte(data)) + suite.NotNil(err) + rbsError, ok := err.(*RbsError) + suite.True(ok) + suite.Equal(uint64(14030), rbsError.FaultCode) + suite.NotEmpty(rbsError.FaultMessage) +} diff --git a/pkg/models/move.go b/pkg/models/move.go index 236bef5af6b..b740c85c5d0 100644 --- a/pkg/models/move.go +++ b/pkg/models/move.go @@ -71,7 +71,7 @@ type Move struct { PPMType *string `db:"ppm_type"` MTOServiceItems MTOServiceItems `has_many:"mto_service_items" fk_id:"move_id"` PaymentRequests PaymentRequests `has_many:"payment_requests" fk_id:"move_id"` - MTOShipments MTOShipments `has_many:"mto_shipments" fk_id:"move_id"` + MTOShipments MTOShipments `json:"mto_shipments" has_many:"mto_shipments" fk_id:"move_id"` ReferenceID *string `db:"reference_id"` ServiceCounselingCompletedAt *time.Time `db:"service_counseling_completed_at"` PrimeCounselingCompletedAt *time.Time `db:"prime_counseling_completed_at"` @@ -98,11 +98,11 @@ type Move struct { SCAssignedID *uuid.UUID `json:"sc_assigned_id" db:"sc_assigned_id"` SCAssignedUser *OfficeUser `belongs_to:"office_users" fk_id:"sc_assigned_id"` TOOAssignedID *uuid.UUID `json:"too_assigned_id" db:"too_assigned_id"` - TOOAssignedUser *OfficeUser `belongs_to:"office_users" fk_id:"too_assigned_id"` + TOOAssignedUser *OfficeUser `json:"too_assigned" belongs_to:"office_users" fk_id:"too_assigned_id"` TIOAssignedID *uuid.UUID `json:"tio_assigned_id" db:"tio_assigned_id"` TIOAssignedUser *OfficeUser `belongs_to:"office_users" fk_id:"tio_assigned_id"` CounselingOfficeID *uuid.UUID `json:"counseling_transportation_office_id" db:"counseling_transportation_office_id"` - CounselingOffice *TransportationOffice `belongs_to:"transportation_offices" fk_id:"counseling_transportation_office_id"` + CounselingOffice *TransportationOffice `json:"counseling_transportation_office" belongs_to:"transportation_offices" fk_id:"counseling_transportation_office_id"` } type MoveWithEarliestDate struct { diff --git a/pkg/models/mto_service_items.go b/pkg/models/mto_service_items.go index 42a749626ce..7948401087d 100644 --- a/pkg/models/mto_service_items.go +++ b/pkg/models/mto_service_items.go @@ -96,7 +96,9 @@ type MTOServiceItemSingle struct { SITEntryDate *time.Time `db:"sit_entry_date"` SITDepartureDate *time.Time `db:"sit_departure_date"` SITDestinationFinalAddressID *uuid.UUID `db:"sit_destination_final_address_id"` + SITDestinationFinalAddress *Address `belongs_to:"addresses" fk_id:"sit_destination_final_address_id"` SITOriginHHGOriginalAddressID *uuid.UUID `db:"sit_origin_hhg_original_address_id"` + SITDestinationOriginalAddress *Address `belongs_to:"addresses" fk_id:"sit_destination_original_address_id"` SITOriginHHGActualAddressID *uuid.UUID `db:"sit_origin_hhg_actual_address_id"` EstimatedWeight *unit.Pound `db:"estimated_weight"` ActualWeight *unit.Pound `db:"actual_weight"` @@ -152,22 +154,26 @@ func FetchRelatedDestinationSITServiceItems(tx *pop.Connection, mtoServiceItemID } func FetchServiceItem(db *pop.Connection, serviceItemID uuid.UUID) (MTOServiceItem, error) { - var serviceItem MTOServiceItem - err := db.Eager("SITDestinationOriginalAddress", - "SITDestinationFinalAddress", - "ReService", - "CustomerContacts", - "MTOShipment.PickupAddress", - "MTOShipment.DestinationAddress").Where("id = ?", serviceItemID).First(&serviceItem) - - if err != nil { - if errors.Cause(err).Error() == RecordNotFoundErrorString { - return MTOServiceItem{}, ErrFetchNotFound + if db != nil { + var serviceItem MTOServiceItem + err := db.Eager("SITDestinationOriginalAddress", + "SITDestinationFinalAddress", + "ReService", + "CustomerContacts", + "MTOShipment.PickupAddress", + "MTOShipment.DestinationAddress", + "Dimensions").Where("id = ?", serviceItemID).First(&serviceItem) + + if err != nil { + if errors.Cause(err).Error() == RecordNotFoundErrorString { + return MTOServiceItem{}, ErrFetchNotFound + } + return MTOServiceItem{}, err } - return MTOServiceItem{}, err + return serviceItem, nil + } else { + return MTOServiceItem{}, errors.New("db connection is nil; unable to fetch service item") } - - return serviceItem, nil } func FetchRelatedDestinationSITFuelCharge(tx *pop.Connection, mtoServiceItemID uuid.UUID) (MTOServiceItem, error) { @@ -214,6 +220,7 @@ type MTOServiceItemType struct { ServiceLocation *ServiceLocationType `json:"service_location"` POELocationID *uuid.UUID `json:"poe_location_id"` PODLocationID *uuid.UUID `json:"pod_location_id"` + ExternalCrate *bool `json:"external_crate"` } func (m MTOServiceItem) GetMTOServiceItemTypeFromServiceItem() MTOServiceItemType { @@ -250,6 +257,7 @@ func (m MTOServiceItem) GetMTOServiceItemTypeFromServiceItem() MTOServiceItemTyp ServiceLocation: m.ServiceLocation, POELocationID: m.POELocationID, PODLocationID: m.PODLocationID, + ExternalCrate: m.ExternalCrate, } } @@ -283,6 +291,7 @@ func (m MTOServiceItem) Value() (driver.Value, error) { var estimatedWeight int64 var actualWeight int64 var pricingEstimate int64 + var externalCrate bool if m.ID != uuid.Nil { id = m.ID.String() @@ -376,6 +385,10 @@ func (m MTOServiceItem) Value() (driver.Value, error) { standaloneCrate = *m.StandaloneCrate } + if m.ExternalCrate != nil { + externalCrate = *m.ExternalCrate + } + if m.LockedPriceCents != nil { lockedPriceCents = m.LockedPriceCents.Int64() } @@ -400,7 +413,7 @@ func (m MTOServiceItem) Value() (driver.Value, error) { pricingEstimate = m.PricingEstimate.Int64() } - s := fmt.Sprintf("(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%d,%d,%s,%s,%s,%t,%t,%s,%d,%d,%t,%d,%s,%s,%s,%s)", + s := fmt.Sprintf("(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%d,%d,%s,%s,%s,%t,%t,%s,%d,%d,%t,%d,%s,%s,%s,%s,%t)", id, moveTaskOrderID, mtoShipmentID, @@ -435,6 +448,7 @@ func (m MTOServiceItem) Value() (driver.Value, error) { poeLocationID, podLocationID, m.ReService.Code.String(), + externalCrate, ) return []byte(s), nil } diff --git a/pkg/models/mto_service_items_test.go b/pkg/models/mto_service_items_test.go index 8d55e1c4bea..00d028e3bf9 100644 --- a/pkg/models/mto_service_items_test.go +++ b/pkg/models/mto_service_items_test.go @@ -164,3 +164,55 @@ func (suite *ModelSuite) TestGetMTOServiceItemTypeFromServiceItem() { suite.NotNil(returnedShipment) }) } + +func (suite *ModelSuite) TestFetchServiceItem() { + suite.Run("successful fetch service item", func() { + move := factory.BuildMove(suite.DB(), nil, nil) + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + }, nil) + msServiceItem := factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.ReService{ + Code: models.ReServiceCodeMS, + }, + }, + }, nil) + factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.ReService{ + Code: models.ReServiceCodeCS, + }, + }, + }, nil) + serviceItem, fetchErr := models.FetchServiceItem(suite.DB(), msServiceItem.ID) + suite.NoError(fetchErr) + suite.NotNil(serviceItem) + }) + + suite.Run("failed fetch service item - db connection is nil", func() { + serviceItem, fetchErr := models.FetchServiceItem(nil, uuid.Must(uuid.NewV4())) + suite.Error(fetchErr) + suite.EqualError(fetchErr, "db connection is nil; unable to fetch service item") + suite.Empty(serviceItem) + }) + + suite.Run("failed fetch service item - record not found", func() { + nonExistentID := uuid.Must(uuid.NewV4()) + serviceItem, fetchErr := models.FetchServiceItem(suite.DB(), nonExistentID) + suite.Error(fetchErr) + suite.Equal(fetchErr, models.ErrFetchNotFound) + suite.Empty(serviceItem) + }) +} diff --git a/pkg/models/mto_shipments.go b/pkg/models/mto_shipments.go index b3c69cb52bb..148f1130e65 100644 --- a/pkg/models/mto_shipments.go +++ b/pkg/models/mto_shipments.go @@ -118,74 +118,74 @@ const ( // MTOShipment is an object representing data for a move task order shipment type MTOShipment struct { - ID uuid.UUID `db:"id"` - MoveTaskOrder Move `belongs_to:"moves" fk_id:"move_id"` - MoveTaskOrderID uuid.UUID `db:"move_id"` - ScheduledPickupDate *time.Time `db:"scheduled_pickup_date"` - RequestedPickupDate *time.Time `db:"requested_pickup_date"` - RequestedDeliveryDate *time.Time `db:"requested_delivery_date"` - ApprovedDate *time.Time `db:"approved_date"` - FirstAvailableDeliveryDate *time.Time `db:"first_available_delivery_date"` - ActualPickupDate *time.Time `db:"actual_pickup_date"` - RequiredDeliveryDate *time.Time `db:"required_delivery_date"` - ScheduledDeliveryDate *time.Time `db:"scheduled_delivery_date"` - ActualDeliveryDate *time.Time `db:"actual_delivery_date"` - CustomerRemarks *string `db:"customer_remarks"` - CounselorRemarks *string `db:"counselor_remarks"` - PickupAddress *Address `belongs_to:"addresses" fk_id:"pickup_address_id"` - PickupAddressID *uuid.UUID `db:"pickup_address_id"` - DestinationAddress *Address `belongs_to:"addresses" fk_id:"destination_address_id"` - DestinationAddressID *uuid.UUID `db:"destination_address_id"` - DestinationType *DestinationType `db:"destination_address_type"` - MTOAgents MTOAgents `has_many:"mto_agents" fk_id:"mto_shipment_id"` - MTOServiceItems MTOServiceItems `has_many:"mto_service_items" fk_id:"mto_shipment_id"` - SecondaryPickupAddress *Address `belongs_to:"addresses" fk_id:"secondary_pickup_address_id"` - SecondaryPickupAddressID *uuid.UUID `db:"secondary_pickup_address_id"` - HasSecondaryPickupAddress *bool `db:"has_secondary_pickup_address"` - SecondaryDeliveryAddress *Address `belongs_to:"addresses" fk_id:"secondary_delivery_address_id"` - SecondaryDeliveryAddressID *uuid.UUID `db:"secondary_delivery_address_id"` - HasSecondaryDeliveryAddress *bool `db:"has_secondary_delivery_address"` - TertiaryPickupAddress *Address `belongs_to:"addresses" fk_id:"tertiary_pickup_address_id"` - TertiaryPickupAddressID *uuid.UUID `db:"tertiary_pickup_address_id"` - HasTertiaryPickupAddress *bool `db:"has_tertiary_pickup_address"` - TertiaryDeliveryAddress *Address `belongs_to:"addresses" fk_id:"tertiary_delivery_address_id"` - TertiaryDeliveryAddressID *uuid.UUID `db:"tertiary_delivery_address_id"` - HasTertiaryDeliveryAddress *bool `db:"has_tertiary_delivery_address"` - SITDaysAllowance *int `db:"sit_days_allowance"` - SITDurationUpdates SITDurationUpdates `has_many:"sit_extensions" fk_id:"mto_shipment_id"` - PrimeEstimatedWeight *unit.Pound `db:"prime_estimated_weight"` - PrimeEstimatedWeightRecordedDate *time.Time `db:"prime_estimated_weight_recorded_date"` - PrimeActualWeight *unit.Pound `db:"prime_actual_weight"` - BillableWeightCap *unit.Pound `db:"billable_weight_cap"` - BillableWeightJustification *string `db:"billable_weight_justification"` - NTSRecordedWeight *unit.Pound `db:"nts_recorded_weight"` - ShipmentType MTOShipmentType `db:"shipment_type"` - Status MTOShipmentStatus `db:"status"` - Diversion bool `db:"diversion"` - DiversionReason *string `db:"diversion_reason"` - DivertedFromShipmentID *uuid.UUID `db:"diverted_from_shipment_id"` - ActualProGearWeight *unit.Pound `db:"actual_pro_gear_weight"` - ActualSpouseProGearWeight *unit.Pound `db:"actual_spouse_pro_gear_weight"` - RejectionReason *string `db:"rejection_reason"` - Distance *unit.Miles `db:"distance"` - Reweigh *Reweigh `has_one:"reweighs" fk_id:"shipment_id"` - UsesExternalVendor bool `db:"uses_external_vendor"` - StorageFacility *StorageFacility `belongs_to:"storage_facilities" fk:"storage_facility_id"` - StorageFacilityID *uuid.UUID `db:"storage_facility_id"` - ServiceOrderNumber *string `db:"service_order_number"` - TACType *LOAType `db:"tac_type"` - SACType *LOAType `db:"sac_type"` - PPMShipment *PPMShipment `has_one:"ppm_shipment" fk_id:"shipment_id"` - BoatShipment *BoatShipment `has_one:"boat_shipment" fk_id:"shipment_id"` - DeliveryAddressUpdate *ShipmentAddressUpdate `has_one:"shipment_address_update" fk_id:"shipment_id"` - CreatedAt time.Time `db:"created_at"` - UpdatedAt time.Time `db:"updated_at"` - DeletedAt *time.Time `db:"deleted_at"` - ShipmentLocator *string `db:"shipment_locator"` - OriginSITAuthEndDate *time.Time `db:"origin_sit_auth_end_date"` - DestinationSITAuthEndDate *time.Time `db:"dest_sit_auth_end_date"` - MobileHome *MobileHome `has_one:"mobile_home" fk_id:"shipment_id"` - MarketCode MarketCode `db:"market_code"` + ID uuid.UUID `json:"id" db:"id"` + MoveTaskOrder Move `json:"move_task_order" belongs_to:"moves" fk_id:"move_id"` + MoveTaskOrderID uuid.UUID `json:"move_task_order_id" db:"move_id"` + ScheduledPickupDate *time.Time `json:"scheduled_pickup_date" db:"scheduled_pickup_date"` + RequestedPickupDate *time.Time `json:"requested_pickup_date" db:"requested_pickup_date"` + RequestedDeliveryDate *time.Time `json:"requested_delivery_date" db:"requested_delivery_date"` + ApprovedDate *time.Time `json:"approved_date" db:"approved_date"` + FirstAvailableDeliveryDate *time.Time `json:"first_available_delivery_date" db:"first_available_delivery_date"` + ActualPickupDate *time.Time `json:"actual_pickup_date" db:"actual_pickup_date"` + RequiredDeliveryDate *time.Time `json:"required_delivery_date" db:"required_delivery_date"` + ScheduledDeliveryDate *time.Time `json:"scheduled_delivery_date" db:"scheduled_delivery_date"` + ActualDeliveryDate *time.Time `json:"actual_delivery_date" db:"actual_delivery_date"` + CustomerRemarks *string `json:"customer_remarks" db:"customer_remarks"` + CounselorRemarks *string `json:"counselor_remarks" db:"counselor_remarks"` + PickupAddress *Address `json:"pickup_address" belongs_to:"addresses" fk_id:"pickup_address_id"` + PickupAddressID *uuid.UUID `json:"pickup_address_id" db:"pickup_address_id"` + DestinationAddress *Address `json:"destination_address" belongs_to:"addresses" fk_id:"destination_address_id"` + DestinationAddressID *uuid.UUID `json:"destination_address_id" db:"destination_address_id"` + DestinationType *DestinationType `json:"destination_type" db:"destination_address_type"` + MTOAgents MTOAgents `json:"mto_agents" has_many:"mto_agents" fk_id:"mto_shipment_id"` + MTOServiceItems MTOServiceItems `json:"mto_service_items" has_many:"mto_service_items" fk_id:"mto_shipment_id"` + SecondaryPickupAddress *Address `json:"secondary_pickup_address" belongs_to:"addresses" fk_id:"secondary_pickup_address_id"` + SecondaryPickupAddressID *uuid.UUID `json:"secondary_pickup_address_id" db:"secondary_pickup_address_id"` + HasSecondaryPickupAddress *bool `json:"has_secondary_pickup_address" db:"has_secondary_pickup_address"` + SecondaryDeliveryAddress *Address `json:"secondary_delivery_address" belongs_to:"addresses" fk_id:"secondary_delivery_address_id"` + SecondaryDeliveryAddressID *uuid.UUID `json:"secondary_delivery_address_id" db:"secondary_delivery_address_id"` + HasSecondaryDeliveryAddress *bool `json:"has_secondary_delivery_address" db:"has_secondary_delivery_address"` + TertiaryPickupAddress *Address `json:"tertiary_pickup_address" belongs_to:"addresses" fk_id:"tertiary_pickup_address_id"` + TertiaryPickupAddressID *uuid.UUID `json:"tertiary_pickup_address_id" db:"tertiary_pickup_address_id"` + HasTertiaryPickupAddress *bool `json:"has_tertiary_pickup_address" db:"has_tertiary_pickup_address"` + TertiaryDeliveryAddress *Address `json:"tertiary_delivery_address" belongs_to:"addresses" fk_id:"tertiary_delivery_address_id"` + TertiaryDeliveryAddressID *uuid.UUID `json:"tertiary_delivery_address_id" db:"tertiary_delivery_address_id"` + HasTertiaryDeliveryAddress *bool `json:"has_tertiary_delivery_address" db:"has_tertiary_delivery_address"` + SITDaysAllowance *int `json:"sit_days_allowance" db:"sit_days_allowance"` + SITDurationUpdates SITDurationUpdates `json:"sit_duration_updates" has_many:"sit_extensions" fk_id:"mto_shipment_id"` + PrimeEstimatedWeight *unit.Pound `json:"prime_estimated_weight" db:"prime_estimated_weight"` + PrimeEstimatedWeightRecordedDate *time.Time `json:"prime_estimated_weight_recorded_date" db:"prime_estimated_weight_recorded_date"` + PrimeActualWeight *unit.Pound `json:"prime_actual_weight" db:"prime_actual_weight"` + BillableWeightCap *unit.Pound `json:"billable_weight_cap" db:"billable_weight_cap"` + BillableWeightJustification *string `json:"billable_weight_justification" db:"billable_weight_justification"` + NTSRecordedWeight *unit.Pound `json:"nts_recorded_weight" db:"nts_recorded_weight"` + ShipmentType MTOShipmentType `json:"shipment_type" db:"shipment_type"` + Status MTOShipmentStatus `json:"status" db:"status"` + Diversion bool `json:"diversion" db:"diversion"` + DiversionReason *string `json:"diversion_reason" db:"diversion_reason"` + DivertedFromShipmentID *uuid.UUID `json:"diverted_from_shipment_id" db:"diverted_from_shipment_id"` + ActualProGearWeight *unit.Pound `json:"actual_pro_gear_weight" db:"actual_pro_gear_weight"` + ActualSpouseProGearWeight *unit.Pound `json:"actual_spouse_pro_gear_weight" db:"actual_spouse_pro_gear_weight"` + RejectionReason *string `json:"rejection_reason" db:"rejection_reason"` + Distance *unit.Miles `json:"distance" db:"distance"` + Reweigh *Reweigh `json:"reweigh" has_one:"reweighs" fk_id:"shipment_id"` + UsesExternalVendor bool `json:"uses_external_vendor" db:"uses_external_vendor"` + StorageFacility *StorageFacility `json:"storage_facility" belongs_to:"storage_facilities" fk:"storage_facility_id"` + StorageFacilityID *uuid.UUID `json:"storage_facility_id" db:"storage_facility_id"` + ServiceOrderNumber *string `json:"service_order_number" db:"service_order_number"` + TACType *LOAType `json:"tac_type" db:"tac_type"` + SACType *LOAType `json:"sac_type" db:"sac_type"` + PPMShipment *PPMShipment `json:"ppm_shipment" has_one:"ppm_shipment" fk_id:"shipment_id"` + BoatShipment *BoatShipment `json:"boat_shipment" has_one:"boat_shipment" fk_id:"shipment_id"` + DeliveryAddressUpdate *ShipmentAddressUpdate `json:"delivery_address_update" has_one:"shipment_address_update" fk_id:"shipment_id"` + CreatedAt time.Time `json:"created_at" db:"created_at"` + UpdatedAt time.Time `json:"updated_at" db:"updated_at"` + DeletedAt *time.Time `json:"deleted_at" db:"deleted_at"` + ShipmentLocator *string `json:"shipment_locator" db:"shipment_locator"` + OriginSITAuthEndDate *time.Time `json:"origin_sit_auth_end_date" db:"origin_sit_auth_end_date"` + DestinationSITAuthEndDate *time.Time `json:"destination_sit_auth_end_date" db:"dest_sit_auth_end_date"` + MobileHome *MobileHome `json:"mobile_home" has_one:"mobile_home" fk_id:"shipment_id"` + MarketCode MarketCode `json:"market_code" db:"market_code"` } // TableName overrides the table name used by Pop. diff --git a/pkg/models/order.go b/pkg/models/order.go index 2b0601f17c7..a9120c9579f 100644 --- a/pkg/models/order.go +++ b/pkg/models/order.go @@ -70,14 +70,14 @@ type Order struct { CreatedAt time.Time `json:"created_at" db:"created_at"` UpdatedAt time.Time `json:"updated_at" db:"updated_at"` ServiceMemberID uuid.UUID `json:"service_member_id" db:"service_member_id"` - ServiceMember ServiceMember `belongs_to:"service_members" fk_id:"service_member_id"` + ServiceMember ServiceMember `json:"service_member" belongs_to:"service_members" fk_id:"service_member_id"` IssueDate time.Time `json:"issue_date" db:"issue_date"` ReportByDate time.Time `json:"report_by_date" db:"report_by_date"` OrdersType internalmessages.OrdersType `json:"orders_type" db:"orders_type"` OrdersTypeDetail *internalmessages.OrdersTypeDetail `json:"orders_type_detail" db:"orders_type_detail"` HasDependents bool `json:"has_dependents" db:"has_dependents"` SpouseHasProGear bool `json:"spouse_has_pro_gear" db:"spouse_has_pro_gear"` - OriginDutyLocation *DutyLocation `belongs_to:"duty_locations" fk_id:"origin_duty_location_id"` + OriginDutyLocation *DutyLocation `json:"origin_duty_location" belongs_to:"duty_locations" fk_id:"origin_duty_location_id"` OriginDutyLocationID *uuid.UUID `json:"origin_duty_location_id" db:"origin_duty_location_id"` NewDutyLocationID uuid.UUID `json:"new_duty_location_id" db:"new_duty_location_id"` NewDutyLocation DutyLocation `belongs_to:"duty_locations" fk_id:"new_duty_location_id"` diff --git a/pkg/models/re_intl_accessorial_price.go b/pkg/models/re_intl_accessorial_price.go index b61b281b76a..431f87f845b 100644 --- a/pkg/models/re_intl_accessorial_price.go +++ b/pkg/models/re_intl_accessorial_price.go @@ -29,6 +29,17 @@ var validMarkets = []string{ string(MarketOconus), } +func (m Market) FullString() string { + switch m { + case MarketConus: + return "CONUS" + case MarketOconus: + return "OCONUS" + default: + return "" + } +} + // ReIntlAccessorialPrice model struct type ReIntlAccessorialPrice struct { ID uuid.UUID `json:"id" db:"id"` @@ -61,14 +72,3 @@ func (r *ReIntlAccessorialPrice) Validate(_ *pop.Connection) (*validate.Errors, &validators.IntIsGreaterThan{Field: r.PerUnitCents.Int(), Name: "PerUnitCents", Compared: -1}, ), nil } - -func (m Market) FullString() string { - switch m { - case MarketConus: - return "CONUS" - case MarketOconus: - return "OCONUS" - default: - return "" - } -} diff --git a/pkg/models/re_service.go b/pkg/models/re_service.go index 85136a4705a..70f78ccea2b 100644 --- a/pkg/models/re_service.go +++ b/pkg/models/re_service.go @@ -86,6 +86,8 @@ const ( ReServiceCodeIDDSIT ReServiceCode = "IDDSIT" // ReServiceCodeIDFSIT International destination 1st day SIT ReServiceCodeIDFSIT ReServiceCode = "IDFSIT" + // ReServiceCodeIDSFSC International destination SIT FSC + ReServiceCodeIDSFSC ReServiceCode = "IDSFSC" // ReServiceCodeIDSHUT International destination shuttle service ReServiceCodeIDSHUT ReServiceCode = "IDSHUT" // ReServiceCodeIHPK International HHG pack @@ -108,6 +110,8 @@ const ( ReServiceCodeIOOUB ReServiceCode = "IOOUB" // ReServiceCodeIOPSIT International origin SIT pickup ReServiceCodeIOPSIT ReServiceCode = "IOPSIT" + // ReServiceCodeIOSFSC International origin SIT FSC + ReServiceCodeIOSFSC ReServiceCode = "IOSFSC" // ReServiceCodeIOSHUT International origin shuttle service ReServiceCodeIOSHUT ReServiceCode = "IOSHUT" // ReServiceCodeIUBPK International UB pack diff --git a/pkg/models/service_item_param_key.go b/pkg/models/service_item_param_key.go index 0c637cc7d92..7c2692b7e8d 100644 --- a/pkg/models/service_item_param_key.go +++ b/pkg/models/service_item_param_key.go @@ -161,6 +161,8 @@ const ( ServiceItemParamNameUncappedRequestTotal ServiceItemParamName = "UncappedRequestTotal" // ServiceItemParamNameLockedPriceCents is the param key name LockedPriceCents ServiceItemParamNameLockedPriceCents ServiceItemParamName = "LockedPriceCents" + // ServiceItemParamNameExternalCrate is the param key name ExternalCrate + ServiceItemParamNameExternalCrate ServiceItemParamName = "ExternalCrate" ) // ServiceItemParamType is a type of service item parameter @@ -281,6 +283,7 @@ var ValidServiceItemParamNames = []ServiceItemParamName{ ServiceItemParamNameLockedPriceCents, ServiceItemParamNamePerUnitCents, ServiceItemParamNamePortZip, + ServiceItemParamNameExternalCrate, } // ValidServiceItemParamNameStrings lists all valid service item param key names @@ -357,6 +360,7 @@ var ValidServiceItemParamNameStrings = []string{ string(ServiceItemParamNameLockedPriceCents), string(ServiceItemParamNamePerUnitCents), string(ServiceItemParamNamePortZip), + string(ServiceItemParamNameExternalCrate), } // ValidServiceItemParamTypes lists all valid service item param types diff --git a/pkg/payment_request/service_param_value_lookups/cubic_feet_billed_lookup.go b/pkg/payment_request/service_param_value_lookups/cubic_feet_billed_lookup.go index 547d900cfc8..80f26837b3f 100644 --- a/pkg/payment_request/service_param_value_lookups/cubic_feet_billed_lookup.go +++ b/pkg/payment_request/service_param_value_lookups/cubic_feet_billed_lookup.go @@ -13,17 +13,21 @@ const ( // CubicFeetBilledLookup does lookup for CubicFeetBilled type CubicFeetBilledLookup struct { - Dimensions models.MTOServiceItemDimensions + Dimensions models.MTOServiceItemDimensions + ServiceItem models.MTOServiceItem } func (c CubicFeetBilledLookup) lookup(_ appcontext.AppContext, keyData *ServiceItemParamKeyData) (string, error) { + isIntlCrateUncrate := c.ServiceItem.ReService.Code == models.ReServiceCodeICRT || c.ServiceItem.ReService.Code == models.ReServiceCodeIUCRT + isExternalCrate := c.ServiceItem.ExternalCrate != nil && *c.ServiceItem.ExternalCrate + // Each service item has an array of dimensions. There is a DB constraint preventing // more than one dimension of each type for a given service item, so we just have to // look for the first crating dimension. for _, dimension := range c.Dimensions { if dimension.Type == models.DimensionTypeCrate { volume := dimension.Volume().ToCubicFeet() - if volume < minCubicFeetBilled { + if (!isIntlCrateUncrate || isExternalCrate) && volume < minCubicFeetBilled { volume = minCubicFeetBilled } return volume.String(), nil diff --git a/pkg/payment_request/service_param_value_lookups/cubic_feet_billed_lookup_test.go b/pkg/payment_request/service_param_value_lookups/cubic_feet_billed_lookup_test.go index 29965d9f03a..48374649c74 100644 --- a/pkg/payment_request/service_param_value_lookups/cubic_feet_billed_lookup_test.go +++ b/pkg/payment_request/service_param_value_lookups/cubic_feet_billed_lookup_test.go @@ -74,7 +74,7 @@ func (suite *ServiceParamValueLookupsSuite) TestCubicFeetBilledLookup() { suite.Equal("1029.33", stringValue) }) - suite.Run("When crate volume is less than minimum, billed volume should be set to minimum", func() { + suite.Run("When domestic crate volume is less than minimum, billed volume should be set to minimum", func() { testdatagen.MakeReContractYear(suite.DB(), testdatagen.Assertions{ ReContractYear: models.ReContractYear{ StartDate: time.Now().Add(-24 * time.Hour), @@ -134,6 +134,127 @@ func (suite *ServiceParamValueLookupsSuite) TestCubicFeetBilledLookup() { suite.Equal("4.00", stringValue) }) + suite.Run("When international external crate volume is less than minimum, billed volume should be set to minimum", func() { + testdatagen.MakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + StartDate: time.Now().Add(-24 * time.Hour), + EndDate: time.Now().Add(24 * time.Hour), + }, + }) + mtoServiceItem := factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: models.ReService{ + Code: models.ReServiceCodeICRT, + }, + }, + }, []factory.Trait{ + factory.GetTraitAvailableToPrimeMove, + }) + + cratingDimension := factory.BuildMTOServiceItemDimension(suite.DB(), []factory.Customization{ + { + Model: models.MTOServiceItemDimension{ + Type: models.DimensionTypeCrate, + Length: 1000, + Height: 1000, + Width: 1000, + CreatedAt: time.Time{}, + UpdatedAt: time.Time{}, + }, + }, + { + Model: mtoServiceItem, + LinkOnly: true, + }, + }, nil) + itemDimension := factory.BuildMTOServiceItemDimension(suite.DB(), []factory.Customization{ + { + Model: models.MTOServiceItemDimension{ + Type: models.DimensionTypeItem, + Length: 100, + Height: 100, + Width: 100, + CreatedAt: time.Time{}, + UpdatedAt: time.Time{}, + }, + }, + { + Model: mtoServiceItem, + LinkOnly: true, + }, + }, nil) + mtoServiceItem.Dimensions = []models.MTOServiceItemDimension{itemDimension, cratingDimension} + mtoServiceItem.ExternalCrate = models.BoolPointer(true) + suite.MustSave(&mtoServiceItem) + paramLookup, err := ServiceParamLookupInitialize(suite.AppContextForTest(), suite.planner, mtoServiceItem, uuid.Must(uuid.NewV4()), mtoServiceItem.MoveTaskOrderID, nil) + suite.FatalNoError(err) + + stringValue, err := paramLookup.ServiceParamValue(suite.AppContextForTest(), key) + suite.FatalNoError(err) + + suite.Equal("4.00", stringValue) + }) + + suite.Run("When international non-external crate volume is less than minimum, billed volume should NOT be set to minimum", func() { + testdatagen.MakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + StartDate: time.Now().Add(-24 * time.Hour), + EndDate: time.Now().Add(24 * time.Hour), + }, + }) + mtoServiceItem := factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: models.ReService{ + Code: models.ReServiceCodeICRT, + }, + }, + }, []factory.Trait{ + factory.GetTraitAvailableToPrimeMove, + }) + + cratingDimension := factory.BuildMTOServiceItemDimension(suite.DB(), []factory.Customization{ + { + Model: models.MTOServiceItemDimension{ + Type: models.DimensionTypeCrate, + Length: 12000, + Height: 12000, + Width: 12000, + CreatedAt: time.Time{}, + UpdatedAt: time.Time{}, + }, + }, + { + Model: mtoServiceItem, + LinkOnly: true, + }, + }, nil) + itemDimension := factory.BuildMTOServiceItemDimension(suite.DB(), []factory.Customization{ + { + Model: models.MTOServiceItemDimension{ + Type: models.DimensionTypeItem, + Length: 100, + Height: 100, + Width: 100, + CreatedAt: time.Time{}, + UpdatedAt: time.Time{}, + }, + }, + { + Model: mtoServiceItem, + LinkOnly: true, + }, + }, nil) + mtoServiceItem.Dimensions = []models.MTOServiceItemDimension{itemDimension, cratingDimension} + suite.MustSave(&mtoServiceItem) + paramLookup, err := ServiceParamLookupInitialize(suite.AppContextForTest(), suite.planner, mtoServiceItem, uuid.Must(uuid.NewV4()), mtoServiceItem.MoveTaskOrderID, nil) + suite.FatalNoError(err) + + stringValue, err := paramLookup.ServiceParamValue(suite.AppContextForTest(), key) + suite.FatalNoError(err) + + suite.Equal("1.00", stringValue) + }) + suite.Run("missing dimension should error", func() { testdatagen.MakeReContractYear(suite.DB(), testdatagen.Assertions{ ReContractYear: models.ReContractYear{ diff --git a/pkg/payment_request/service_param_value_lookups/external_crate_lookup.go b/pkg/payment_request/service_param_value_lookups/external_crate_lookup.go new file mode 100644 index 00000000000..64aef2da1d4 --- /dev/null +++ b/pkg/payment_request/service_param_value_lookups/external_crate_lookup.go @@ -0,0 +1,22 @@ +package serviceparamvaluelookups + +import ( + "strconv" + + "github.com/transcom/mymove/pkg/appcontext" + "github.com/transcom/mymove/pkg/models" +) + +// ExternalCrateLookup does lookup on externalCrate +type ExternalCrateLookup struct { + ServiceItem models.MTOServiceItem +} + +func (r ExternalCrateLookup) lookup(_ appcontext.AppContext, _ *ServiceItemParamKeyData) (string, error) { + externalCrate := r.ServiceItem.ExternalCrate + if externalCrate == nil { + return "false", nil + } + + return strconv.FormatBool(*externalCrate), nil +} diff --git a/pkg/payment_request/service_param_value_lookups/external_crate_lookup_test.go b/pkg/payment_request/service_param_value_lookups/external_crate_lookup_test.go new file mode 100644 index 00000000000..59606485500 --- /dev/null +++ b/pkg/payment_request/service_param_value_lookups/external_crate_lookup_test.go @@ -0,0 +1,50 @@ +package serviceparamvaluelookups + +import ( + "strconv" + + "github.com/transcom/mymove/pkg/models" +) + +func (suite *ServiceParamValueLookupsSuite) TestExternalCrateLookup() { + suite.Run("ExternalCrate is true", func() { + externalCrate := true + mtoServiceItem := models.MTOServiceItem{ + ExternalCrate: &externalCrate, + } + + paramLookup := ExternalCrateLookup{ServiceItem: mtoServiceItem} + valueStr, err := paramLookup.lookup(suite.AppContextForTest(), nil) + + suite.FatalNoError(err) + expected := strconv.FormatBool(externalCrate) + suite.Equal(expected, valueStr) + }) + + suite.Run("ExternalCrate is false", func() { + externalCrate := false + mtoServiceItem := models.MTOServiceItem{ + ExternalCrate: &externalCrate, + } + + paramLookup := ExternalCrateLookup{ServiceItem: mtoServiceItem} + valueStr, err := paramLookup.lookup(suite.AppContextForTest(), nil) + + suite.FatalNoError(err) + expected := strconv.FormatBool(externalCrate) + suite.Equal(expected, valueStr) + }) + + suite.Run("ExternalCrate is nil", func() { + mtoServiceItem := models.MTOServiceItem{ + ExternalCrate: nil, + } + + paramLookup := ExternalCrateLookup{ServiceItem: mtoServiceItem} + valueStr, err := paramLookup.lookup(suite.AppContextForTest(), nil) + + suite.FatalNoError(err) + expected := "false" + suite.Equal(expected, valueStr) + }) +} diff --git a/pkg/payment_request/service_param_value_lookups/service_param_value_lookups.go b/pkg/payment_request/service_param_value_lookups/service_param_value_lookups.go index 6815d250f28..242340c3085 100644 --- a/pkg/payment_request/service_param_value_lookups/service_param_value_lookups.go +++ b/pkg/payment_request/service_param_value_lookups/service_param_value_lookups.go @@ -91,6 +91,7 @@ var ServiceItemParamsWithLookups = []models.ServiceItemParamName{ models.ServiceItemParamNamePortZip, models.ServiceItemParamNameMarketDest, models.ServiceItemParamNameMarketOrigin, + models.ServiceItemParamNameExternalCrate, } // ServiceParamLookupInitialize initializes service parameter lookup @@ -342,7 +343,8 @@ func InitializeLookups(appCtx appcontext.AppContext, shipment models.MTOShipment lookups[models.ServiceItemParamNameContractCode] = ContractCodeLookup{} lookups[models.ServiceItemParamNameCubicFeetBilled] = CubicFeetBilledLookup{ - Dimensions: serviceItem.Dimensions, + Dimensions: serviceItem.Dimensions, + ServiceItem: serviceItem, } lookups[models.ServiceItemParamNamePSILinehaulDom] = PSILinehaulDomLookup{ @@ -455,6 +457,10 @@ func InitializeLookups(appCtx appcontext.AppContext, shipment models.MTOShipment Address: *shipment.DestinationAddress, } + lookups[models.ServiceItemParamNameExternalCrate] = ExternalCrateLookup{ + ServiceItem: serviceItem, + } + return lookups } diff --git a/pkg/payment_request/service_param_value_lookups/service_params_cache_test.go b/pkg/payment_request/service_param_value_lookups/service_params_cache_test.go index f55832cc189..999db3de94b 100644 --- a/pkg/payment_request/service_param_value_lookups/service_params_cache_test.go +++ b/pkg/payment_request/service_param_value_lookups/service_params_cache_test.go @@ -20,7 +20,7 @@ type paramsCacheSubtestData struct { mtoServiceItemMS models.MTOServiceItem mtoServiceItemCrate1 models.MTOServiceItem mtoServiceItemCrate2 models.MTOServiceItem - mtoServiceItemShuttle models.MTOServiceItem + mtoServiceItemDomesticShuttle models.MTOServiceItem paramKeyWeightEstimated models.ServiceItemParamKey paramKeyRequestedPickupDate models.ServiceItemParamKey paramKeyMTOAvailableToPrimeAt models.ServiceItemParamKey @@ -224,7 +224,7 @@ func (suite *ServiceParamValueLookupsSuite) makeSubtestData() (subtestData *para subtestData.shuttleEstimatedWeight = unit.Pound(400) subtestData.shuttleActualWeight = unit.Pound(450) - subtestData.mtoServiceItemShuttle = factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + subtestData.mtoServiceItemDomesticShuttle = factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ { Model: subtestData.move, LinkOnly: true, @@ -248,7 +248,7 @@ func (suite *ServiceParamValueLookupsSuite) makeSubtestData() (subtestData *para // DOSHUT estimated weight factory.BuildServiceParam(suite.DB(), []factory.Customization{ { - Model: subtestData.mtoServiceItemShuttle.ReService, + Model: subtestData.mtoServiceItemDomesticShuttle.ReService, LinkOnly: true, }, { @@ -462,7 +462,7 @@ func (suite *ServiceParamValueLookupsSuite) TestServiceParamCache() { expected := strconv.Itoa(subtestData.estimatedWeight.Int()) suite.Equal(expected, estimatedWeightStr) - paramLookupService2, err := ServiceParamLookupInitialize(suite.AppContextForTest(), suite.planner, subtestData.mtoServiceItemShuttle, subtestData.paymentRequest.ID, subtestData.paymentRequest.MoveTaskOrderID, ¶mCache) + paramLookupService2, err := ServiceParamLookupInitialize(suite.AppContextForTest(), suite.planner, subtestData.mtoServiceItemDomesticShuttle, subtestData.paymentRequest.ID, subtestData.paymentRequest.MoveTaskOrderID, ¶mCache) suite.NoError(err) var shuttleEstimatedWeightStr string diff --git a/pkg/services/event/notification_test.go b/pkg/services/event/notification_test.go index eea593eba9a..ec669b81daf 100644 --- a/pkg/services/event/notification_test.go +++ b/pkg/services/event/notification_test.go @@ -200,7 +200,7 @@ func (suite *EventServiceSuite) Test_MTOServiceItemPayload() { }, }, }, nil) - data := &primemessages.MTOServiceItemShuttle{} + data := &primemessages.MTOServiceItemDomesticShuttle{} payload, assemblePayloadErr := assembleMTOServiceItemPayload(suite.AppContextForTest(), mtoServiceItemDOSHUT.ID) diff --git a/pkg/services/ghc_rate_engine.go b/pkg/services/ghc_rate_engine.go index d1c7b923118..8944ad1b1bc 100644 --- a/pkg/services/ghc_rate_engine.go +++ b/pkg/services/ghc_rate_engine.go @@ -312,3 +312,19 @@ type IntlDestinationAdditionalDaySITPricer interface { Price(appCtx appcontext.AppContext, contractCode string, requestedPickupDate time.Time, numberOfDaysInSIT int, weight unit.Pound, perUnitCents int) (unit.Cents, PricingDisplayParams, error) ParamsPricer } + +// IntlCratingPricer prices the international crating service for a Move +// +//go:generate mockery --name IntlCratingPricer +type IntlCratingPricer interface { + Price(appCtx appcontext.AppContext, contractCode string, requestedPickupDate time.Time, billedCubicFeet unit.CubicFeet, standaloneCrate bool, standaloneCrateCap unit.Cents, externalCrate bool, market models.Market) (unit.Cents, PricingDisplayParams, error) + ParamsPricer +} + +// IntlUncratingPricer prices the international uncrating service for a Move +// +//go:generate mockery --name IntlUncratingPricer +type IntlUncratingPricer interface { + Price(appCtx appcontext.AppContext, contractCode string, requestedPickupDate time.Time, billedCubicFeet unit.CubicFeet, market models.Market) (unit.Cents, PricingDisplayParams, error) + ParamsPricer +} diff --git a/pkg/services/ghcrateengine/intl_crating_pricer.go b/pkg/services/ghcrateengine/intl_crating_pricer.go new file mode 100644 index 00000000000..096045a07ce --- /dev/null +++ b/pkg/services/ghcrateengine/intl_crating_pricer.go @@ -0,0 +1,66 @@ +package ghcrateengine + +import ( + "time" + + "github.com/transcom/mymove/pkg/appcontext" + "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/unit" +) + +type intlCratingPricer struct { +} + +// NewIntlCratingPricer creates a new pricer for international crating +func NewIntlCratingPricer() services.IntlCratingPricer { + return &intlCratingPricer{} +} + +// Price determines the price for international crating +func (p intlCratingPricer) Price(appCtx appcontext.AppContext, contractCode string, referenceDate time.Time, billedCubicFeet unit.CubicFeet, standaloneCrate bool, standaloneCrateCap unit.Cents, externalCrate bool, market models.Market) (unit.Cents, services.PricingDisplayParams, error) { + return priceIntlCratingUncrating(appCtx, models.ReServiceCodeICRT, contractCode, referenceDate, billedCubicFeet, standaloneCrate, standaloneCrateCap, externalCrate, market) +} + +// PriceUsingParams determines the price for international crating given PaymentServiceItemParams +func (p intlCratingPricer) PriceUsingParams(appCtx appcontext.AppContext, params models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error) { + contractCode, err := getParamString(params, models.ServiceItemParamNameContractCode) + if err != nil { + return unit.Cents(0), nil, err + } + + cubicFeetFloat, err := getParamFloat(params, models.ServiceItemParamNameCubicFeetBilled) + if err != nil { + return unit.Cents(0), nil, err + } + + cubicFeetBilled := unit.CubicFeet(cubicFeetFloat) + + referenceDate, err := getParamTime(params, models.ServiceItemParamNameReferenceDate) + if err != nil { + return unit.Cents(0), nil, err + } + + market, err := getParamMarket(params, models.ServiceItemParamNameMarketOrigin) + if err != nil { + return unit.Cents(0), nil, err + } + + standaloneCrate, err := getParamBool(params, models.ServiceItemParamNameStandaloneCrate) + if err != nil { + return unit.Cents(0), nil, err + } + + externalCrate, err := getParamBool(params, models.ServiceItemParamNameExternalCrate) + if err != nil { + return unit.Cents(0), nil, err + } + + standaloneCrateCapParam, err := getParamInt(params, models.ServiceItemParamNameStandaloneCrateCap) + if err != nil { + return unit.Cents(0), nil, err + } + standaloneCrateCap := unit.Cents(float64(standaloneCrateCapParam)) + + return p.Price(appCtx, contractCode, referenceDate, cubicFeetBilled, standaloneCrate, standaloneCrateCap, externalCrate, market) +} diff --git a/pkg/services/ghcrateengine/intl_crating_pricer_test.go b/pkg/services/ghcrateengine/intl_crating_pricer_test.go new file mode 100644 index 00000000000..d49951cb30c --- /dev/null +++ b/pkg/services/ghcrateengine/intl_crating_pricer_test.go @@ -0,0 +1,136 @@ +package ghcrateengine + +import ( + "strconv" + "time" + + "github.com/transcom/mymove/pkg/factory" + "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/testdatagen" + "github.com/transcom/mymove/pkg/unit" +) + +const ( + icrtTestMarket = models.Market("O") + icrtTestBasePriceCents = unit.Cents(2300) + icrtTestEscalationCompounded = 1.125 + icrtTestBilledCubicFeet = unit.CubicFeet(10) + icrtTestPriceCents = unit.Cents(25880) + icrtTestStandaloneCrate = false + icrtTestStandaloneCrateCap = unit.Cents(1000000) + icrtTestUncappedRequestTotal = unit.Cents(25880) + icrtTestExternalCrate = false +) + +var icrtTestRequestedPickupDate = time.Date(testdatagen.TestYear, time.June, 5, 7, 33, 11, 456, time.UTC) + +func (suite *GHCRateEngineServiceSuite) TestIntlCratingPricer() { + pricer := NewIntlCratingPricer() + + suite.Run("success using PaymentServiceItemParams", func() { + suite.setupInternationalAccessorialPrice(models.ReServiceCodeICRT, icrtTestMarket, icrtTestBasePriceCents, testdatagen.DefaultContractCode, icrtTestEscalationCompounded) + + paymentServiceItem := suite.setupIntlCratingServiceItem(icrtTestBilledCubicFeet) + priceCents, displayParams, err := pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.NoError(err) + suite.Equal(icrtTestPriceCents, priceCents) + + expectedParams := services.PricingDisplayParams{ + {Key: models.ServiceItemParamNameContractYearName, Value: testdatagen.DefaultContractCode}, + {Key: models.ServiceItemParamNameEscalationCompounded, Value: FormatEscalation(icrtTestEscalationCompounded)}, + {Key: models.ServiceItemParamNamePriceRateOrFactor, Value: FormatCents(icrtTestBasePriceCents)}, + {Key: models.ServiceItemParamNameUncappedRequestTotal, Value: FormatCents(icrtTestUncappedRequestTotal)}, + } + suite.validatePricerCreatedParams(expectedParams, displayParams) + }) + suite.Run("success with truncating cubic feet", func() { + suite.setupInternationalAccessorialPrice(models.ReServiceCodeICRT, icrtTestMarket, icrtTestBasePriceCents, testdatagen.DefaultContractCode, icrtTestEscalationCompounded) + + paymentServiceItem := suite.setupIntlCratingServiceItem(unit.CubicFeet(10.005)) + priceCents, _, err := pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.NoError(err) + suite.Equal(icrtTestPriceCents, priceCents) + }) + + suite.Run("success without PaymentServiceItemParams", func() { + suite.setupInternationalAccessorialPrice(models.ReServiceCodeICRT, icrtTestMarket, icrtTestBasePriceCents, testdatagen.DefaultContractCode, icrtTestEscalationCompounded) + + priceCents, _, err := pricer.Price(suite.AppContextForTest(), testdatagen.DefaultContractCode, icrtTestRequestedPickupDate, icrtTestBilledCubicFeet, icrtTestStandaloneCrate, icrtTestStandaloneCrateCap, icrtTestExternalCrate, icrtTestMarket) + suite.NoError(err) + suite.Equal(icrtTestPriceCents, priceCents) + }) + + suite.Run("PriceUsingParams but sending empty params", func() { + suite.setupInternationalAccessorialPrice(models.ReServiceCodeICRT, icrtTestMarket, icrtTestBasePriceCents, testdatagen.DefaultContractCode, icrtTestEscalationCompounded) + _, _, err := pricer.PriceUsingParams(suite.AppContextForTest(), models.PaymentServiceItemParams{}) + suite.Error(err) + }) + + suite.Run("invalid crating volume - external crate", func() { + suite.setupInternationalAccessorialPrice(models.ReServiceCodeICRT, icrtTestMarket, icrtTestBasePriceCents, testdatagen.DefaultContractCode, icrtTestEscalationCompounded) + badVolume := unit.CubicFeet(3.0) + _, _, err := pricer.Price(suite.AppContextForTest(), testdatagen.DefaultContractCode, icrtTestRequestedPickupDate, badVolume, icrtTestStandaloneCrate, icrtTestStandaloneCrateCap, true, icrtTestMarket) + suite.Error(err) + suite.Contains(err.Error(), "external crates must be billed for a minimum of 4.00 cubic feet") + }) + + suite.Run("not finding a rate record", func() { + suite.setupInternationalAccessorialPrice(models.ReServiceCodeICRT, icrtTestMarket, icrtTestBasePriceCents, testdatagen.DefaultContractCode, icrtTestEscalationCompounded) + _, _, err := pricer.Price(suite.AppContextForTest(), "BOGUS", icrtTestRequestedPickupDate, icrtTestBilledCubicFeet, icrtTestStandaloneCrate, icrtTestStandaloneCrateCap, icrtTestExternalCrate, icrtTestMarket) + suite.Error(err) + suite.Contains(err.Error(), "could not lookup International Accessorial Area Price") + }) + + suite.Run("not finding a contract year record", func() { + suite.setupInternationalAccessorialPrice(models.ReServiceCodeICRT, icrtTestMarket, icrtTestBasePriceCents, testdatagen.DefaultContractCode, icrtTestEscalationCompounded) + twoYearsLaterPickupDate := icrtTestRequestedPickupDate.AddDate(2, 0, 0) + _, _, err := pricer.Price(suite.AppContextForTest(), testdatagen.DefaultContractCode, twoYearsLaterPickupDate, icrtTestBilledCubicFeet, icrtTestStandaloneCrate, icrtTestStandaloneCrateCap, icrtTestExternalCrate, icrtTestMarket) + suite.Error(err) + suite.Contains(err.Error(), "could not lookup contract year") + }) +} + +func (suite *GHCRateEngineServiceSuite) setupIntlCratingServiceItem(cubicFeet unit.CubicFeet) models.PaymentServiceItem { + return factory.BuildPaymentServiceItemWithParams( + suite.DB(), + models.ReServiceCodeICRT, + []factory.CreatePaymentServiceItemParams{ + { + Key: models.ServiceItemParamNameContractCode, + KeyType: models.ServiceItemParamTypeString, + Value: factory.DefaultContractCode, + }, + { + Key: models.ServiceItemParamNameCubicFeetBilled, + KeyType: models.ServiceItemParamTypeDecimal, + Value: cubicFeet.String(), + }, + { + Key: models.ServiceItemParamNameReferenceDate, + KeyType: models.ServiceItemParamTypeDate, + Value: icrtTestRequestedPickupDate.Format(DateParamFormat), + }, + { + Key: models.ServiceItemParamNameStandaloneCrate, + KeyType: models.ServiceItemParamTypeBoolean, + Value: strconv.FormatBool(false), + }, + { + Key: models.ServiceItemParamNameStandaloneCrateCap, + KeyType: models.ServiceItemParamTypeInteger, + Value: strconv.FormatInt(100000, 10), + }, + { + Key: models.ServiceItemParamNameMarketOrigin, + KeyType: models.ServiceItemParamTypeString, + Value: icrtTestMarket.String(), + }, + { + Key: models.ServiceItemParamNameExternalCrate, + KeyType: models.ServiceItemParamTypeBoolean, + Value: strconv.FormatBool(false), + }, + }, nil, nil, + ) +} diff --git a/pkg/services/ghcrateengine/intl_uncrating_pricer.go b/pkg/services/ghcrateengine/intl_uncrating_pricer.go new file mode 100644 index 00000000000..23c3470f102 --- /dev/null +++ b/pkg/services/ghcrateengine/intl_uncrating_pricer.go @@ -0,0 +1,50 @@ +package ghcrateengine + +import ( + "time" + + "github.com/transcom/mymove/pkg/appcontext" + "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/unit" +) + +type intlUncratingPricer struct { +} + +// NewIntlUncratingPricer creates a new pricer for international uncrating +func NewIntlUncratingPricer() services.IntlUncratingPricer { + return &intlUncratingPricer{} +} + +// Price determines the price for international uncrating +func (p intlUncratingPricer) Price(appCtx appcontext.AppContext, contractCode string, referenceDate time.Time, billedCubicFeet unit.CubicFeet, market models.Market) (unit.Cents, services.PricingDisplayParams, error) { + return priceIntlCratingUncrating(appCtx, models.ReServiceCodeIUCRT, contractCode, referenceDate, billedCubicFeet, false, 0, false, market) +} + +// PriceUsingParams determines the price for international uncrating given PaymentServiceItemParams +func (p intlUncratingPricer) PriceUsingParams(appCtx appcontext.AppContext, params models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error) { + contractCode, err := getParamString(params, models.ServiceItemParamNameContractCode) + if err != nil { + return unit.Cents(0), nil, err + } + + cubicFeetFloat, err := getParamFloat(params, models.ServiceItemParamNameCubicFeetBilled) + if err != nil { + return unit.Cents(0), nil, err + } + + cubicFeetBilled := unit.CubicFeet(cubicFeetFloat) + + referenceDate, err := getParamTime(params, models.ServiceItemParamNameReferenceDate) + if err != nil { + return unit.Cents(0), nil, err + } + + market, err := getParamMarket(params, models.ServiceItemParamNameMarketDest) + if err != nil { + return unit.Cents(0), nil, err + } + + return p.Price(appCtx, contractCode, referenceDate, cubicFeetBilled, market) +} diff --git a/pkg/services/ghcrateengine/intl_uncrating_pricer_test.go b/pkg/services/ghcrateengine/intl_uncrating_pricer_test.go new file mode 100644 index 00000000000..5369172b039 --- /dev/null +++ b/pkg/services/ghcrateengine/intl_uncrating_pricer_test.go @@ -0,0 +1,102 @@ +package ghcrateengine + +import ( + "fmt" + "time" + + "github.com/transcom/mymove/pkg/factory" + "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/testdatagen" + "github.com/transcom/mymove/pkg/unit" +) + +const ( + iucrtTestMarket = models.Market("O") + iucrtTestBasePriceCents = unit.Cents(595) + iucrtTestEscalationCompounded = 1.125 + iucrtTestBilledCubicFeet = 10 + iucrtTestPriceCents = unit.Cents(6690) + iucrtTestUncappedRequestTotal = unit.Cents(6690) +) + +var iucrtTestRequestedPickupDate = time.Date(testdatagen.TestYear, time.June, 5, 7, 33, 11, 456, time.UTC) + +func (suite *GHCRateEngineServiceSuite) TestIntlUncratingPricer() { + pricer := NewIntlUncratingPricer() + + suite.Run("success using PaymentServiceItemParams", func() { + suite.setupInternationalAccessorialPrice(models.ReServiceCodeIUCRT, iucrtTestMarket, iucrtTestBasePriceCents, testdatagen.DefaultContractCode, iucrtTestEscalationCompounded) + + paymentServiceItem := suite.setupIntlUncratingServiceItem() + priceCents, displayParams, err := pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.NoError(err) + suite.Equal(iucrtTestPriceCents, priceCents) + + expectedParams := services.PricingDisplayParams{ + {Key: models.ServiceItemParamNameContractYearName, Value: testdatagen.DefaultContractCode}, + {Key: models.ServiceItemParamNameEscalationCompounded, Value: FormatEscalation(iucrtTestEscalationCompounded)}, + {Key: models.ServiceItemParamNamePriceRateOrFactor, Value: FormatCents(iucrtTestBasePriceCents)}, + {Key: models.ServiceItemParamNameUncappedRequestTotal, Value: FormatCents(iucrtTestUncappedRequestTotal)}, + } + suite.validatePricerCreatedParams(expectedParams, displayParams) + }) + + suite.Run("success without PaymentServiceItemParams", func() { + suite.setupInternationalAccessorialPrice(models.ReServiceCodeIUCRT, iucrtTestMarket, iucrtTestBasePriceCents, testdatagen.DefaultContractCode, iucrtTestEscalationCompounded) + + priceCents, _, err := pricer.Price(suite.AppContextForTest(), testdatagen.DefaultContractCode, iucrtTestRequestedPickupDate, iucrtTestBilledCubicFeet, iucrtTestMarket) + suite.NoError(err) + suite.Equal(iucrtTestPriceCents, priceCents) + }) + + suite.Run("PriceUsingParams but sending empty params", func() { + suite.setupInternationalAccessorialPrice(models.ReServiceCodeIUCRT, iucrtTestMarket, iucrtTestBasePriceCents, testdatagen.DefaultContractCode, iucrtTestEscalationCompounded) + _, _, err := pricer.PriceUsingParams(suite.AppContextForTest(), models.PaymentServiceItemParams{}) + suite.Error(err) + }) + + suite.Run("not finding a rate record", func() { + suite.setupInternationalAccessorialPrice(models.ReServiceCodeIUCRT, iucrtTestMarket, iucrtTestBasePriceCents, testdatagen.DefaultContractCode, iucrtTestEscalationCompounded) + _, _, err := pricer.Price(suite.AppContextForTest(), "BOGUS", iucrtTestRequestedPickupDate, iucrtTestBilledCubicFeet, iucrtTestMarket) + suite.Error(err) + suite.Contains(err.Error(), "could not lookup International Accessorial Area Price") + }) + + suite.Run("not finding a contract year record", func() { + suite.setupInternationalAccessorialPrice(models.ReServiceCodeIUCRT, iucrtTestMarket, iucrtTestBasePriceCents, testdatagen.DefaultContractCode, iucrtTestEscalationCompounded) + twoYearsLaterPickupDate := iucrtTestRequestedPickupDate.AddDate(2, 0, 0) + _, _, err := pricer.Price(suite.AppContextForTest(), testdatagen.DefaultContractCode, twoYearsLaterPickupDate, iucrtTestBilledCubicFeet, iucrtTestMarket) + suite.Error(err) + suite.Contains(err.Error(), "could not lookup contract year") + }) +} + +func (suite *GHCRateEngineServiceSuite) setupIntlUncratingServiceItem() models.PaymentServiceItem { + return factory.BuildPaymentServiceItemWithParams( + suite.DB(), + models.ReServiceCodeIUCRT, + []factory.CreatePaymentServiceItemParams{ + { + Key: models.ServiceItemParamNameContractCode, + KeyType: models.ServiceItemParamTypeString, + Value: factory.DefaultContractCode, + }, + { + Key: models.ServiceItemParamNameCubicFeetBilled, + KeyType: models.ServiceItemParamTypeDecimal, + Value: fmt.Sprintf("%d", int(iucrtTestBilledCubicFeet)), + }, + { + Key: models.ServiceItemParamNameReferenceDate, + KeyType: models.ServiceItemParamTypeDate, + Value: iucrtTestRequestedPickupDate.Format(DateParamFormat), + }, + { + Key: models.ServiceItemParamNameMarketDest, + KeyType: models.ServiceItemParamTypeString, + Value: iucrtTestMarket.String(), + }, + }, nil, nil, + ) +} diff --git a/pkg/services/ghcrateengine/pricer_helpers_intl.go b/pkg/services/ghcrateengine/pricer_helpers_intl.go index 9754f552b3d..195dc117b1c 100644 --- a/pkg/services/ghcrateengine/pricer_helpers_intl.go +++ b/pkg/services/ghcrateengine/pricer_helpers_intl.go @@ -222,3 +222,63 @@ func priceIntlAdditionalDaySIT(appCtx appcontext.AppContext, additionalDaySITCod return totalCost, displayParams, nil } + +func priceIntlCratingUncrating(appCtx appcontext.AppContext, cratingUncratingCode models.ReServiceCode, contractCode string, referenceDate time.Time, billedCubicFeet unit.CubicFeet, standaloneCrate bool, standaloneCrateCap unit.Cents, externalCrate bool, market models.Market) (unit.Cents, services.PricingDisplayParams, error) { + if cratingUncratingCode != models.ReServiceCodeICRT && cratingUncratingCode != models.ReServiceCodeIUCRT { + return 0, nil, fmt.Errorf("unsupported international crating/uncrating code of %s", cratingUncratingCode) + } + + // Validate parameters + if len(contractCode) == 0 { + return 0, nil, errors.New("ContractCode is required") + } + if referenceDate.IsZero() { + return 0, nil, errors.New("ReferenceDate is required") + } + if market == "" { + return 0, nil, errors.New("Market is required") + } + + if externalCrate && billedCubicFeet < minIntlExternalCrateBilledCubicFeet { + return 0, nil, fmt.Errorf("external crates must be billed for a minimum of %.2f cubic feet", minIntlExternalCrateBilledCubicFeet) + } + + internationalAccessorialPrice, err := fetchInternationalAccessorialPrice(appCtx, contractCode, cratingUncratingCode, market) + if err != nil { + return 0, nil, fmt.Errorf("could not lookup International Accessorial Area Price: %w", err) + } + + basePrice := internationalAccessorialPrice.PerUnitCents.Float64() + escalatedPrice, contractYear, err := escalatePriceForContractYear(appCtx, internationalAccessorialPrice.ContractID, referenceDate, false, basePrice) + if err != nil { + return 0, nil, fmt.Errorf("could not calculate escalated price: %w", err) + } + + escalatedPrice = escalatedPrice * float64(billedCubicFeet) + totalCost := unit.Cents(math.Round(escalatedPrice)) + + displayParams := services.PricingDisplayParams{ + { + Key: models.ServiceItemParamNamePriceRateOrFactor, + Value: FormatCents(internationalAccessorialPrice.PerUnitCents), + }, + { + Key: models.ServiceItemParamNameContractYearName, + Value: contractYear.Name, + }, + { + Key: models.ServiceItemParamNameEscalationCompounded, + Value: FormatEscalation(contractYear.EscalationCompounded), + }, + { + Key: models.ServiceItemParamNameUncappedRequestTotal, + Value: FormatCents(totalCost), + }, + } + + if (standaloneCrate) && (totalCost > standaloneCrateCap) { + totalCost = standaloneCrateCap + } + + return totalCost, displayParams, nil +} diff --git a/pkg/services/ghcrateengine/pricer_helpers_intl_test.go b/pkg/services/ghcrateengine/pricer_helpers_intl_test.go index 14e3d6c8618..a0721e001fb 100644 --- a/pkg/services/ghcrateengine/pricer_helpers_intl_test.go +++ b/pkg/services/ghcrateengine/pricer_helpers_intl_test.go @@ -205,3 +205,58 @@ func (suite *GHCRateEngineServiceSuite) TestPriceIntlAdditionalDaySIT() { suite.Contains(err.Error(), "NumberDaysSIT is required") }) } + +func (suite *GHCRateEngineServiceSuite) TestPriceIntlCratingUncrating() { + suite.Run("crating golden path", func() { + suite.setupInternationalAccessorialPrice(models.ReServiceCodeICRT, icrtTestMarket, icrtTestBasePriceCents, testdatagen.DefaultContractCode, icrtTestEscalationCompounded) + + priceCents, displayParams, err := priceIntlCratingUncrating(suite.AppContextForTest(), models.ReServiceCodeICRT, testdatagen.DefaultContractCode, icrtTestRequestedPickupDate, icrtTestBilledCubicFeet, icrtTestStandaloneCrate, icrtTestStandaloneCrateCap, icrtTestExternalCrate, icrtTestMarket) + suite.NoError(err) + suite.Equal(icrtTestPriceCents, priceCents) + + expectedParams := services.PricingDisplayParams{ + {Key: models.ServiceItemParamNameContractYearName, Value: testdatagen.DefaultContractCode}, + {Key: models.ServiceItemParamNameEscalationCompounded, Value: FormatEscalation(icrtTestEscalationCompounded)}, + {Key: models.ServiceItemParamNamePriceRateOrFactor, Value: FormatCents(icrtTestBasePriceCents)}, + {Key: models.ServiceItemParamNameUncappedRequestTotal, Value: FormatCents(dcrtTestUncappedRequestTotal)}, + } + suite.validatePricerCreatedParams(expectedParams, displayParams) + }) + + suite.Run("invalid service code", func() { + suite.setupInternationalAccessorialPrice(models.ReServiceCodeICRT, icrtTestMarket, icrtTestBasePriceCents, testdatagen.DefaultContractCode, icrtTestEscalationCompounded) + _, _, err := priceIntlCratingUncrating(suite.AppContextForTest(), models.ReServiceCodeCS, testdatagen.DefaultContractCode, icrtTestRequestedPickupDate, icrtTestBilledCubicFeet, icrtTestStandaloneCrate, icrtTestStandaloneCrateCap, icrtTestExternalCrate, icrtTestMarket) + + suite.Error(err) + suite.Contains(err.Error(), "unsupported international crating/uncrating code") + }) + + suite.Run("invalid crate size - external crate", func() { + suite.setupInternationalAccessorialPrice(models.ReServiceCodeICRT, icrtTestMarket, icrtTestBasePriceCents, testdatagen.DefaultContractCode, icrtTestEscalationCompounded) + + badSize := unit.CubicFeet(1.0) + _, _, err := priceIntlCratingUncrating(suite.AppContextForTest(), models.ReServiceCodeICRT, testdatagen.DefaultContractCode, icrtTestRequestedPickupDate, badSize, icrtTestStandaloneCrate, icrtTestStandaloneCrateCap, true, icrtTestMarket) + + suite.Error(err) + suite.Contains(err.Error(), "external crates must be billed for a minimum of 4.00 cubic feet") + }) + + suite.Run("not finding a rate record", func() { + suite.setupInternationalAccessorialPrice(models.ReServiceCodeICRT, icrtTestMarket, icrtTestBasePriceCents, testdatagen.DefaultContractCode, icrtTestEscalationCompounded) + + _, _, err := priceIntlCratingUncrating(suite.AppContextForTest(), models.ReServiceCodeICRT, "BOGUS", icrtTestRequestedPickupDate, icrtTestBilledCubicFeet, icrtTestStandaloneCrate, icrtTestStandaloneCrateCap, icrtTestExternalCrate, icrtTestMarket) + + suite.Error(err) + suite.Contains(err.Error(), "could not lookup International Accessorial Area Price") + }) + + suite.Run("not finding a contract year record", func() { + suite.setupInternationalAccessorialPrice(models.ReServiceCodeICRT, icrtTestMarket, icrtTestBasePriceCents, testdatagen.DefaultContractCode, icrtTestEscalationCompounded) + + twoYearsLaterPickupDate := ioshutTestRequestedPickupDate.AddDate(2, 0, 0) + _, _, err := priceIntlCratingUncrating(suite.AppContextForTest(), models.ReServiceCodeICRT, testdatagen.DefaultContractCode, twoYearsLaterPickupDate, icrtTestBilledCubicFeet, icrtTestStandaloneCrate, icrtTestStandaloneCrateCap, icrtTestExternalCrate, icrtTestMarket) + + suite.Error(err) + suite.Contains(err.Error(), "could not calculate escalated price: could not lookup contract year") + }) +} diff --git a/pkg/services/ghcrateengine/service_item_pricer.go b/pkg/services/ghcrateengine/service_item_pricer.go index 777ca2283bf..e48f3cdc749 100644 --- a/pkg/services/ghcrateengine/service_item_pricer.go +++ b/pkg/services/ghcrateengine/service_item_pricer.go @@ -115,6 +115,10 @@ func PricerForServiceItem(serviceCode models.ReServiceCode) (services.ParamsPric return NewIntlDestinationFirstDaySITPricer(), nil case models.ReServiceCodeIDASIT: return NewIntlDestinationAdditionalDaySITPricer(), nil + case models.ReServiceCodeICRT: + return NewIntlCratingPricer(), nil + case models.ReServiceCodeIUCRT: + return NewIntlUncratingPricer(), nil default: // TODO: We may want a different error type here after all pricers have been implemented return nil, apperror.NewNotImplementedError(fmt.Sprintf("pricer not found for code %s", serviceCode)) diff --git a/pkg/services/ghcrateengine/service_item_pricer_test.go b/pkg/services/ghcrateengine/service_item_pricer_test.go index c27652cf90d..cbaf02ba2e2 100644 --- a/pkg/services/ghcrateengine/service_item_pricer_test.go +++ b/pkg/services/ghcrateengine/service_item_pricer_test.go @@ -55,6 +55,8 @@ func (suite *GHCRateEngineServiceSuite) TestGetPricer() { {models.ReServiceCodeIOSHUT, &internationalOriginShuttlingPricer{}}, {models.ReServiceCodeDCRT, &domesticCratingPricer{}}, {models.ReServiceCodeDUCRT, &domesticUncratingPricer{}}, + {models.ReServiceCodeICRT, &intlCratingPricer{}}, + {models.ReServiceCodeIUCRT, &intlUncratingPricer{}}, {models.ReServiceCodeDPK, &domesticPackPricer{}}, {models.ReServiceCodeDNPK, &domesticNTSPackPricer{}}, {models.ReServiceCodeDUPK, &domesticUnpackPricer{}}, diff --git a/pkg/services/ghcrateengine/shared.go b/pkg/services/ghcrateengine/shared.go index 1a76f817734..44eb2100124 100644 --- a/pkg/services/ghcrateengine/shared.go +++ b/pkg/services/ghcrateengine/shared.go @@ -15,6 +15,9 @@ const minIntlWeightHHG = unit.Pound(500) // minInternationalWeight is the minimum weight used in international calculations (weights below this are upgraded to the min) const minInternationalWeight = unit.Pound(500) +// minIntlExternalCrateBilledCubicFeet is the minimum billed cubic feet used in international external crate +const minIntlExternalCrateBilledCubicFeet = 4.00 + // dateInYear represents a specific date in a year (without caring what year it is) type dateInYear struct { month time.Month diff --git a/pkg/services/mocks/IntlCratingPricer.go b/pkg/services/mocks/IntlCratingPricer.go new file mode 100644 index 00000000000..0ada84deb77 --- /dev/null +++ b/pkg/services/mocks/IntlCratingPricer.go @@ -0,0 +1,109 @@ +// Code generated by mockery. DO NOT EDIT. + +package mocks + +import ( + mock "github.com/stretchr/testify/mock" + appcontext "github.com/transcom/mymove/pkg/appcontext" + + models "github.com/transcom/mymove/pkg/models" + + services "github.com/transcom/mymove/pkg/services" + + time "time" + + unit "github.com/transcom/mymove/pkg/unit" +) + +// IntlCratingPricer is an autogenerated mock type for the IntlCratingPricer type +type IntlCratingPricer struct { + mock.Mock +} + +// Price provides a mock function with given fields: appCtx, contractCode, requestedPickupDate, billedCubicFeet, standaloneCrate, standaloneCrateCap, externalCrate, market +func (_m *IntlCratingPricer) Price(appCtx appcontext.AppContext, contractCode string, requestedPickupDate time.Time, billedCubicFeet unit.CubicFeet, standaloneCrate bool, standaloneCrateCap unit.Cents, externalCrate bool, market models.Market) (unit.Cents, services.PricingDisplayParams, error) { + ret := _m.Called(appCtx, contractCode, requestedPickupDate, billedCubicFeet, standaloneCrate, standaloneCrateCap, externalCrate, market) + + if len(ret) == 0 { + panic("no return value specified for Price") + } + + var r0 unit.Cents + var r1 services.PricingDisplayParams + var r2 error + if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, time.Time, unit.CubicFeet, bool, unit.Cents, bool, models.Market) (unit.Cents, services.PricingDisplayParams, error)); ok { + return rf(appCtx, contractCode, requestedPickupDate, billedCubicFeet, standaloneCrate, standaloneCrateCap, externalCrate, market) + } + if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, time.Time, unit.CubicFeet, bool, unit.Cents, bool, models.Market) unit.Cents); ok { + r0 = rf(appCtx, contractCode, requestedPickupDate, billedCubicFeet, standaloneCrate, standaloneCrateCap, externalCrate, market) + } else { + r0 = ret.Get(0).(unit.Cents) + } + + if rf, ok := ret.Get(1).(func(appcontext.AppContext, string, time.Time, unit.CubicFeet, bool, unit.Cents, bool, models.Market) services.PricingDisplayParams); ok { + r1 = rf(appCtx, contractCode, requestedPickupDate, billedCubicFeet, standaloneCrate, standaloneCrateCap, externalCrate, market) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(services.PricingDisplayParams) + } + } + + if rf, ok := ret.Get(2).(func(appcontext.AppContext, string, time.Time, unit.CubicFeet, bool, unit.Cents, bool, models.Market) error); ok { + r2 = rf(appCtx, contractCode, requestedPickupDate, billedCubicFeet, standaloneCrate, standaloneCrateCap, externalCrate, market) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// PriceUsingParams provides a mock function with given fields: appCtx, params +func (_m *IntlCratingPricer) PriceUsingParams(appCtx appcontext.AppContext, params models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error) { + ret := _m.Called(appCtx, params) + + if len(ret) == 0 { + panic("no return value specified for PriceUsingParams") + } + + var r0 unit.Cents + var r1 services.PricingDisplayParams + var r2 error + if rf, ok := ret.Get(0).(func(appcontext.AppContext, models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error)); ok { + return rf(appCtx, params) + } + if rf, ok := ret.Get(0).(func(appcontext.AppContext, models.PaymentServiceItemParams) unit.Cents); ok { + r0 = rf(appCtx, params) + } else { + r0 = ret.Get(0).(unit.Cents) + } + + if rf, ok := ret.Get(1).(func(appcontext.AppContext, models.PaymentServiceItemParams) services.PricingDisplayParams); ok { + r1 = rf(appCtx, params) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(services.PricingDisplayParams) + } + } + + if rf, ok := ret.Get(2).(func(appcontext.AppContext, models.PaymentServiceItemParams) error); ok { + r2 = rf(appCtx, params) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// NewIntlCratingPricer creates a new instance of IntlCratingPricer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewIntlCratingPricer(t interface { + mock.TestingT + Cleanup(func()) +}) *IntlCratingPricer { + mock := &IntlCratingPricer{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/services/mocks/IntlUncratingPricer.go b/pkg/services/mocks/IntlUncratingPricer.go new file mode 100644 index 00000000000..c54879fbce8 --- /dev/null +++ b/pkg/services/mocks/IntlUncratingPricer.go @@ -0,0 +1,109 @@ +// Code generated by mockery. DO NOT EDIT. + +package mocks + +import ( + mock "github.com/stretchr/testify/mock" + appcontext "github.com/transcom/mymove/pkg/appcontext" + + models "github.com/transcom/mymove/pkg/models" + + services "github.com/transcom/mymove/pkg/services" + + time "time" + + unit "github.com/transcom/mymove/pkg/unit" +) + +// IntlUncratingPricer is an autogenerated mock type for the IntlUncratingPricer type +type IntlUncratingPricer struct { + mock.Mock +} + +// Price provides a mock function with given fields: appCtx, contractCode, requestedPickupDate, billedCubicFeet, market +func (_m *IntlUncratingPricer) Price(appCtx appcontext.AppContext, contractCode string, requestedPickupDate time.Time, billedCubicFeet unit.CubicFeet, market models.Market) (unit.Cents, services.PricingDisplayParams, error) { + ret := _m.Called(appCtx, contractCode, requestedPickupDate, billedCubicFeet, market) + + if len(ret) == 0 { + panic("no return value specified for Price") + } + + var r0 unit.Cents + var r1 services.PricingDisplayParams + var r2 error + if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, time.Time, unit.CubicFeet, models.Market) (unit.Cents, services.PricingDisplayParams, error)); ok { + return rf(appCtx, contractCode, requestedPickupDate, billedCubicFeet, market) + } + if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, time.Time, unit.CubicFeet, models.Market) unit.Cents); ok { + r0 = rf(appCtx, contractCode, requestedPickupDate, billedCubicFeet, market) + } else { + r0 = ret.Get(0).(unit.Cents) + } + + if rf, ok := ret.Get(1).(func(appcontext.AppContext, string, time.Time, unit.CubicFeet, models.Market) services.PricingDisplayParams); ok { + r1 = rf(appCtx, contractCode, requestedPickupDate, billedCubicFeet, market) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(services.PricingDisplayParams) + } + } + + if rf, ok := ret.Get(2).(func(appcontext.AppContext, string, time.Time, unit.CubicFeet, models.Market) error); ok { + r2 = rf(appCtx, contractCode, requestedPickupDate, billedCubicFeet, market) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// PriceUsingParams provides a mock function with given fields: appCtx, params +func (_m *IntlUncratingPricer) PriceUsingParams(appCtx appcontext.AppContext, params models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error) { + ret := _m.Called(appCtx, params) + + if len(ret) == 0 { + panic("no return value specified for PriceUsingParams") + } + + var r0 unit.Cents + var r1 services.PricingDisplayParams + var r2 error + if rf, ok := ret.Get(0).(func(appcontext.AppContext, models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error)); ok { + return rf(appCtx, params) + } + if rf, ok := ret.Get(0).(func(appcontext.AppContext, models.PaymentServiceItemParams) unit.Cents); ok { + r0 = rf(appCtx, params) + } else { + r0 = ret.Get(0).(unit.Cents) + } + + if rf, ok := ret.Get(1).(func(appcontext.AppContext, models.PaymentServiceItemParams) services.PricingDisplayParams); ok { + r1 = rf(appCtx, params) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(services.PricingDisplayParams) + } + } + + if rf, ok := ret.Get(2).(func(appcontext.AppContext, models.PaymentServiceItemParams) error); ok { + r2 = rf(appCtx, params) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// NewIntlUncratingPricer creates a new instance of IntlUncratingPricer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewIntlUncratingPricer(t interface { + mock.TestingT + Cleanup(func()) +}) *IntlUncratingPricer { + mock := &IntlUncratingPricer{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/services/mocks/MTOServiceItemCreator.go b/pkg/services/mocks/MTOServiceItemCreator.go index ae6e7d230e7..ab7cd5f1deb 100644 --- a/pkg/services/mocks/MTOServiceItemCreator.go +++ b/pkg/services/mocks/MTOServiceItemCreator.go @@ -8,6 +8,8 @@ import ( models "github.com/transcom/mymove/pkg/models" + unit "github.com/transcom/mymove/pkg/unit" + validate "github.com/gobuffalo/validate/v3" ) @@ -55,6 +57,34 @@ func (_m *MTOServiceItemCreator) CreateMTOServiceItem(appCtx appcontext.AppConte return r0, r1, r2 } +// FindEstimatedPrice provides a mock function with given fields: appCtx, serviceItem, mtoShipment +func (_m *MTOServiceItemCreator) FindEstimatedPrice(appCtx appcontext.AppContext, serviceItem *models.MTOServiceItem, mtoShipment models.MTOShipment) (unit.Cents, error) { + ret := _m.Called(appCtx, serviceItem, mtoShipment) + + if len(ret) == 0 { + panic("no return value specified for FindEstimatedPrice") + } + + var r0 unit.Cents + var r1 error + if rf, ok := ret.Get(0).(func(appcontext.AppContext, *models.MTOServiceItem, models.MTOShipment) (unit.Cents, error)); ok { + return rf(appCtx, serviceItem, mtoShipment) + } + if rf, ok := ret.Get(0).(func(appcontext.AppContext, *models.MTOServiceItem, models.MTOShipment) unit.Cents); ok { + r0 = rf(appCtx, serviceItem, mtoShipment) + } else { + r0 = ret.Get(0).(unit.Cents) + } + + if rf, ok := ret.Get(1).(func(appcontext.AppContext, *models.MTOServiceItem, models.MTOShipment) error); ok { + r1 = rf(appCtx, serviceItem, mtoShipment) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // NewMTOServiceItemCreator creates a new instance of MTOServiceItemCreator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewMTOServiceItemCreator(t interface { diff --git a/pkg/services/mocks/MTOServiceItemUpdater.go b/pkg/services/mocks/MTOServiceItemUpdater.go index ed356fd7699..bbb6bd86828 100644 --- a/pkg/services/mocks/MTOServiceItemUpdater.go +++ b/pkg/services/mocks/MTOServiceItemUpdater.go @@ -138,6 +138,36 @@ func (_m *MTOServiceItemUpdater) UpdateMTOServiceItemBasic(appCtx appcontext.App return r0, r1 } +// UpdateMTOServiceItemPricingEstimate provides a mock function with given fields: appCtx, serviceItem, shipment, eTag +func (_m *MTOServiceItemUpdater) UpdateMTOServiceItemPricingEstimate(appCtx appcontext.AppContext, serviceItem *models.MTOServiceItem, shipment models.MTOShipment, eTag string) (*models.MTOServiceItem, error) { + ret := _m.Called(appCtx, serviceItem, shipment, eTag) + + if len(ret) == 0 { + panic("no return value specified for UpdateMTOServiceItemPricingEstimate") + } + + var r0 *models.MTOServiceItem + var r1 error + if rf, ok := ret.Get(0).(func(appcontext.AppContext, *models.MTOServiceItem, models.MTOShipment, string) (*models.MTOServiceItem, error)); ok { + return rf(appCtx, serviceItem, shipment, eTag) + } + if rf, ok := ret.Get(0).(func(appcontext.AppContext, *models.MTOServiceItem, models.MTOShipment, string) *models.MTOServiceItem); ok { + r0 = rf(appCtx, serviceItem, shipment, eTag) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.MTOServiceItem) + } + } + + if rf, ok := ret.Get(1).(func(appcontext.AppContext, *models.MTOServiceItem, models.MTOShipment, string) error); ok { + r1 = rf(appCtx, serviceItem, shipment, eTag) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // UpdateMTOServiceItemPrime provides a mock function with given fields: appCtx, serviceItem, planner, shipment, eTag func (_m *MTOServiceItemUpdater) UpdateMTOServiceItemPrime(appCtx appcontext.AppContext, serviceItem *models.MTOServiceItem, planner route.Planner, shipment models.MTOShipment, eTag string) (*models.MTOServiceItem, error) { ret := _m.Called(appCtx, serviceItem, planner, shipment, eTag) diff --git a/pkg/services/mocks/OrderFetcher.go b/pkg/services/mocks/OrderFetcher.go index c7eb39d01ce..012df5ccb1b 100644 --- a/pkg/services/mocks/OrderFetcher.go +++ b/pkg/services/mocks/OrderFetcher.go @@ -80,6 +80,43 @@ func (_m *OrderFetcher) ListAllOrderLocations(appCtx appcontext.AppContext, offi return r0, r1 } +// ListDestinationRequestsOrders provides a mock function with given fields: appCtx, officeUserID, role, params +func (_m *OrderFetcher) ListDestinationRequestsOrders(appCtx appcontext.AppContext, officeUserID uuid.UUID, role roles.RoleType, params *services.ListOrderParams) ([]models.Move, int, error) { + ret := _m.Called(appCtx, officeUserID, role, params) + + if len(ret) == 0 { + panic("no return value specified for ListDestinationRequestsOrders") + } + + var r0 []models.Move + var r1 int + var r2 error + if rf, ok := ret.Get(0).(func(appcontext.AppContext, uuid.UUID, roles.RoleType, *services.ListOrderParams) ([]models.Move, int, error)); ok { + return rf(appCtx, officeUserID, role, params) + } + if rf, ok := ret.Get(0).(func(appcontext.AppContext, uuid.UUID, roles.RoleType, *services.ListOrderParams) []models.Move); ok { + r0 = rf(appCtx, officeUserID, role, params) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]models.Move) + } + } + + if rf, ok := ret.Get(1).(func(appcontext.AppContext, uuid.UUID, roles.RoleType, *services.ListOrderParams) int); ok { + r1 = rf(appCtx, officeUserID, role, params) + } else { + r1 = ret.Get(1).(int) + } + + if rf, ok := ret.Get(2).(func(appcontext.AppContext, uuid.UUID, roles.RoleType, *services.ListOrderParams) error); ok { + r2 = rf(appCtx, officeUserID, role, params) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + // ListOrders provides a mock function with given fields: appCtx, officeUserID, role, params func (_m *OrderFetcher) ListOrders(appCtx appcontext.AppContext, officeUserID uuid.UUID, role roles.RoleType, params *services.ListOrderParams) ([]models.Move, int, error) { ret := _m.Called(appCtx, officeUserID, role, params) diff --git a/pkg/services/move/move_fetcher_test.go b/pkg/services/move/move_fetcher_test.go index 62b6551add5..b37cdb6c277 100644 --- a/pkg/services/move/move_fetcher_test.go +++ b/pkg/services/move/move_fetcher_test.go @@ -519,6 +519,90 @@ func (suite *MoveServiceSuite) TestMoveFetcherBulkAssignmentSC() { suite.Equal(1, len(moves)) suite.NotEqual(marinePPM.ID, moves[0].ID) }) + + suite.Run("Closeout returns non Navy/USCG/USMC ppms in needs closeout status", func() { + moveFetcher := NewMoveFetcherBulkAssignment() + transportationOffice := factory.BuildTransportationOffice(suite.DB(), nil, nil) + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CloseoutOffice, + }, + }, []roles.RoleType{roles.RoleTypeServicesCounselor}) + + submittedAt := time.Now() + + // create non USMC/USCG/NAVY ppm in need closeout status + factory.BuildMoveWithPPMShipment(suite.DB(), []factory.Customization{ + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CloseoutOffice, + }, + { + Model: models.PPMShipment{ + Status: models.PPMShipmentStatusNeedsCloseout, + SubmittedAt: &submittedAt, + }, + }, + { + Model: models.Move{ + Status: models.MoveStatusAPPROVED, + }, + }, + }, nil) + + // create non closeout needed ppm + factory.BuildMoveWithPPMShipment(suite.DB(), []factory.Customization{ + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CloseoutOffice, + }, + { + Model: models.PPMShipment{ + Status: models.PPMShipmentStatusWaitingOnCustomer, + SubmittedAt: &submittedAt, + }, + }, + { + Model: models.Move{ + Status: models.MoveStatusAPPROVED, + }, + }, + }, nil) + + marine := models.AffiliationMARINES + marinePPM := factory.BuildPPMShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusAPPROVED, + }, + }, + { + Model: models.MTOShipment{ + ShipmentType: models.MTOShipmentTypePPM, + }, + }, + { + Model: models.PPMShipment{ + Status: models.PPMShipmentStatusNeedsCloseout, + SubmittedAt: &submittedAt, + }, + }, + { + Model: models.ServiceMember{ + Affiliation: &marine, + }, + }, + }, nil) + + moves, err := moveFetcher.FetchMovesForBulkAssignmentCloseout(suite.AppContextForTest(), "KKFA", officeUser.TransportationOffice.ID) + suite.FatalNoError(err) + suite.Equal(1, len(moves)) + suite.NotEqual(marinePPM.ID, moves[0].ID) + }) } func (suite *MoveServiceSuite) TestMoveFetcherBulkAssignmentTOO() { diff --git a/pkg/services/move_history/move_history_fetcher_test.go b/pkg/services/move_history/move_history_fetcher_test.go index eac2db729fc..9900d97cd1a 100644 --- a/pkg/services/move_history/move_history_fetcher_test.go +++ b/pkg/services/move_history/move_history_fetcher_test.go @@ -379,7 +379,7 @@ func (suite *MoveHistoryServiceSuite) TestMoveHistoryFetcherScenarios() { mock.Anything, false, ).Return(400, nil) - updater := mtoserviceitem.NewMTOServiceItemUpdater(planner, builder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher) + updater := mtoserviceitem.NewMTOServiceItemUpdater(planner, builder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) move := factory.BuildApprovalsRequestedMove(suite.DB(), nil, nil) serviceItem := factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ { diff --git a/pkg/services/move_task_order/move_task_order_fetcher.go b/pkg/services/move_task_order/move_task_order_fetcher.go index b53b8351420..f93bbf51681 100644 --- a/pkg/services/move_task_order/move_task_order_fetcher.go +++ b/pkg/services/move_task_order/move_task_order_fetcher.go @@ -352,12 +352,11 @@ func (f moveTaskOrderFetcher) FetchMoveTaskOrder(appCtx appcontext.AppContext, s if loadErr != nil { return &models.Move{}, apperror.NewQueryError("CustomerContacts", loadErr, "") } - } else if serviceItem.ReService.Code == models.ReServiceCodeICRT || // use address.isOconus to get 'market' value for intl crating - serviceItem.ReService.Code == models.ReServiceCodeIUCRT { - loadErr := appCtx.DB().Load(&mto.MTOServiceItems[i], "MTOShipment.PickupAddress", "MTOShipment.DestinationAddress") - if loadErr != nil { - return &models.Move{}, apperror.NewQueryError("MTOShipment.PickupAddress, MTOShipment.DestinationAddress", loadErr, "") - } + } + + loadErr := appCtx.DB().Load(&mto.MTOServiceItems[i], "MTOShipment.PickupAddress", "MTOShipment.DestinationAddress") + if loadErr != nil { + return &models.Move{}, apperror.NewQueryError("MTOShipment", loadErr, "") } loadedServiceItems = append(loadedServiceItems, mto.MTOServiceItems[i]) diff --git a/pkg/services/mto_service_item.go b/pkg/services/mto_service_item.go index 25926ae3fca..0931fcbefc7 100644 --- a/pkg/services/mto_service_item.go +++ b/pkg/services/mto_service_item.go @@ -9,6 +9,7 @@ import ( "github.com/transcom/mymove/pkg/appcontext" "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/route" + "github.com/transcom/mymove/pkg/unit" ) // MTOServiceItemFetcher is the exported interface for fetching a mto service item @@ -23,6 +24,7 @@ type MTOServiceItemFetcher interface { //go:generate mockery --name MTOServiceItemCreator type MTOServiceItemCreator interface { CreateMTOServiceItem(appCtx appcontext.AppContext, serviceItem *models.MTOServiceItem) (*models.MTOServiceItems, *validate.Errors, error) + FindEstimatedPrice(appCtx appcontext.AppContext, serviceItem *models.MTOServiceItem, mtoShipment models.MTOShipment) (unit.Cents, error) } // MTOServiceItemUpdater is the exported interface for updating an mto service item @@ -32,6 +34,7 @@ type MTOServiceItemUpdater interface { ApproveOrRejectServiceItem(appCtx appcontext.AppContext, mtoServiceItemID uuid.UUID, status models.MTOServiceItemStatus, rejectionReason *string, eTag string) (*models.MTOServiceItem, error) UpdateMTOServiceItem(appCtx appcontext.AppContext, serviceItem *models.MTOServiceItem, eTag string, validator string) (*models.MTOServiceItem, error) UpdateMTOServiceItemBasic(appCtx appcontext.AppContext, serviceItem *models.MTOServiceItem, eTag string) (*models.MTOServiceItem, error) + UpdateMTOServiceItemPricingEstimate(appCtx appcontext.AppContext, serviceItem *models.MTOServiceItem, shipment models.MTOShipment, eTag string) (*models.MTOServiceItem, error) UpdateMTOServiceItemPrime(appCtx appcontext.AppContext, serviceItem *models.MTOServiceItem, planner route.Planner, shipment models.MTOShipment, eTag string) (*models.MTOServiceItem, error) ConvertItemToCustomerExpense(appCtx appcontext.AppContext, shipment *models.MTOShipment, customerExpenseReason *string, convertToCustomerExpense bool) (*models.MTOServiceItem, error) } diff --git a/pkg/services/mto_service_item/mto_service_item_creator.go b/pkg/services/mto_service_item/mto_service_item_creator.go index e71a4d5249d..3b46e33ea3a 100644 --- a/pkg/services/mto_service_item/mto_service_item_creator.go +++ b/pkg/services/mto_service_item/mto_service_item_creator.go @@ -39,7 +39,8 @@ type mtoServiceItemCreator struct { fuelSurchargePricer services.FuelSurchargePricer } -func (o *mtoServiceItemCreator) findEstimatedPrice(appCtx appcontext.AppContext, serviceItem *models.MTOServiceItem, mtoShipment models.MTOShipment) (unit.Cents, error) { +// FindEstimatedPrice finds the estimated price for a service item +func (o *mtoServiceItemCreator) FindEstimatedPrice(appCtx appcontext.AppContext, serviceItem *models.MTOServiceItem, mtoShipment models.MTOShipment) (unit.Cents, error) { if serviceItem.ReService.Code == models.ReServiceCodeDOP || serviceItem.ReService.Code == models.ReServiceCodeDPK || serviceItem.ReService.Code == models.ReServiceCodeDDP || @@ -55,7 +56,8 @@ func (o *mtoServiceItemCreator) findEstimatedPrice(appCtx appcontext.AppContext, requestedPickupDate := *mtoShipment.RequestedPickupDate currTime := time.Now() var distance int - primeEstimatedWeight := *mtoShipment.PrimeEstimatedWeight + + adjustedWeight := GetAdjustedWeight(*mtoShipment.PrimeEstimatedWeight, mtoShipment.ShipmentType == models.MTOShipmentTypeUnaccompaniedBaggage) contractCode, err := FetchContractCode(appCtx, currTime) if err != nil { @@ -74,7 +76,7 @@ func (o *mtoServiceItemCreator) findEstimatedPrice(appCtx appcontext.AppContext, return 0, err } - price, _, err = o.originPricer.Price(appCtx, contractCode, requestedPickupDate, *mtoShipment.PrimeEstimatedWeight, domesticServiceArea.ServiceArea, isPPM) + price, _, err = o.originPricer.Price(appCtx, contractCode, requestedPickupDate, *adjustedWeight, domesticServiceArea.ServiceArea, isPPM) if err != nil { return 0, err } @@ -87,7 +89,7 @@ func (o *mtoServiceItemCreator) findEstimatedPrice(appCtx appcontext.AppContext, servicesScheduleOrigin := domesticServiceArea.ServicesSchedule - price, _, err = o.packPricer.Price(appCtx, contractCode, requestedPickupDate, *mtoShipment.PrimeEstimatedWeight, servicesScheduleOrigin, isPPM) + price, _, err = o.packPricer.Price(appCtx, contractCode, requestedPickupDate, *adjustedWeight, servicesScheduleOrigin, isPPM) if err != nil { return 0, err } @@ -102,7 +104,7 @@ func (o *mtoServiceItemCreator) findEstimatedPrice(appCtx appcontext.AppContext, } } - price, _, err = o.destinationPricer.Price(appCtx, contractCode, requestedPickupDate, *mtoShipment.PrimeEstimatedWeight, domesticServiceArea.ServiceArea, isPPM) + price, _, err = o.destinationPricer.Price(appCtx, contractCode, requestedPickupDate, *adjustedWeight, domesticServiceArea.ServiceArea, isPPM) if err != nil { return 0, err } @@ -115,7 +117,7 @@ func (o *mtoServiceItemCreator) findEstimatedPrice(appCtx appcontext.AppContext, serviceScheduleDestination := domesticServiceArea.ServicesSchedule - price, _, err = o.unpackPricer.Price(appCtx, contractCode, requestedPickupDate, *mtoShipment.PrimeEstimatedWeight, serviceScheduleDestination, isPPM) + price, _, err = o.unpackPricer.Price(appCtx, contractCode, requestedPickupDate, *adjustedWeight, serviceScheduleDestination, isPPM) if err != nil { return 0, err } @@ -133,7 +135,7 @@ func (o *mtoServiceItemCreator) findEstimatedPrice(appCtx appcontext.AppContext, return 0, err } } - price, _, err = o.linehaulPricer.Price(appCtx, contractCode, requestedPickupDate, unit.Miles(distance), *mtoShipment.PrimeEstimatedWeight, domesticServiceArea.ServiceArea, isPPM) + price, _, err = o.linehaulPricer.Price(appCtx, contractCode, requestedPickupDate, unit.Miles(distance), *adjustedWeight, domesticServiceArea.ServiceArea, isPPM) if err != nil { return 0, err } @@ -149,7 +151,7 @@ func (o *mtoServiceItemCreator) findEstimatedPrice(appCtx appcontext.AppContext, return 0, err } } - price, _, err = o.shorthaulPricer.Price(appCtx, contractCode, requestedPickupDate, unit.Miles(distance), *mtoShipment.PrimeEstimatedWeight, domesticServiceArea.ServiceArea) + price, _, err = o.shorthaulPricer.Price(appCtx, contractCode, requestedPickupDate, unit.Miles(distance), *adjustedWeight, domesticServiceArea.ServiceArea) if err != nil { return 0, err } @@ -173,7 +175,7 @@ func (o *mtoServiceItemCreator) findEstimatedPrice(appCtx appcontext.AppContext, } } - fscWeightBasedDistanceMultiplier, err := LookupFSCWeightBasedDistanceMultiplier(appCtx, primeEstimatedWeight) + fscWeightBasedDistanceMultiplier, err := LookupFSCWeightBasedDistanceMultiplier(appCtx, *adjustedWeight) if err != nil { return 0, err } @@ -185,7 +187,7 @@ func (o *mtoServiceItemCreator) findEstimatedPrice(appCtx appcontext.AppContext, if err != nil { return 0, err } - price, _, err = o.fuelSurchargePricer.Price(appCtx, pickupDateForFSC, unit.Miles(distance), primeEstimatedWeight, fscWeightBasedDistanceMultiplierFloat, eiaFuelPrice, isPPM) + price, _, err = o.fuelSurchargePricer.Price(appCtx, pickupDateForFSC, unit.Miles(distance), *adjustedWeight, fscWeightBasedDistanceMultiplierFloat, eiaFuelPrice, isPPM) if err != nil { return 0, err } @@ -606,8 +608,8 @@ func (o *mtoServiceItemCreator) CreateMTOServiceItem(appCtx appcontext.AppContex // DLH, DPK, DOP, DDP, DUPK // NTS-release requested pickup dates are for handle out, their pricing is handled differently as their locations are based on storage facilities, not pickup locations - if mtoShipment.PrimeEstimatedWeight != nil && mtoShipment.RequestedPickupDate != nil && mtoShipment.ShipmentType != models.MTOShipmentTypeHHGOutOfNTS { - serviceItemEstimatedPrice, err := o.findEstimatedPrice(appCtx, serviceItem, mtoShipment) + if mtoShipment.PrimeEstimatedWeight != nil && mtoShipment.RequestedPickupDate != nil { + serviceItemEstimatedPrice, err := o.FindEstimatedPrice(appCtx, serviceItem, mtoShipment) if serviceItemEstimatedPrice != 0 && err == nil { serviceItem.PricingEstimate = &serviceItemEstimatedPrice } @@ -952,3 +954,22 @@ func (o *mtoServiceItemCreator) validateFirstDaySITServiceItem(appCtx appcontext return &extraServiceItems, nil } + +// Get Adjusted weight for pricing. Returns the weight at 110% or the minimum billable weight whichever is higher, unless it's 0 +func GetAdjustedWeight(incomingWeight unit.Pound, isUB bool) *unit.Pound { + // minimum weight billed by GHC is 500 lbs unless it's Unaccompanied Baggage (UB) + minimumBilledWeight := unit.Pound(500) + if isUB { + minimumBilledWeight = unit.Pound(300) + } + + // add 110% modifier to billable weight + newWeight := (int(incomingWeight.Float64() * 1.1)) + adjustedWeight := (*unit.Pound)(&newWeight) + + // if the adjusted weight is less than the minimum billable weight but is nonzero, set it to the minimum weight billed + if *adjustedWeight < minimumBilledWeight && *adjustedWeight > 0 { + *adjustedWeight = minimumBilledWeight + } + return adjustedWeight +} diff --git a/pkg/services/mto_service_item/mto_service_item_creator_test.go b/pkg/services/mto_service_item/mto_service_item_creator_test.go index d754d1aa00f..9a9146f50d8 100644 --- a/pkg/services/mto_service_item/mto_service_item_creator_test.go +++ b/pkg/services/mto_service_item/mto_service_item_creator_test.go @@ -819,7 +819,7 @@ func (suite *MTOServiceItemServiceSuite) TestCreateMTOServiceItem() { }) // If the service item we're trying to create is shuttle service and there is no estimated weight, it fails. - suite.Run("MTOServiceItemShuttle no prime weight is okay", func() { + suite.Run("MTOServiceItemDomesticShuttle no prime weight is okay", func() { // TESTCASE SCENARIO // Under test: CreateMTOServiceItem function // Set up: Create DDSHUT service item on a shipment without estimated weight @@ -1828,3 +1828,668 @@ func (suite *MTOServiceItemServiceSuite) TestCreateDestSITServiceItem() { suite.Contains(invalidInputError.ValidationErrors.Keys(), "reServiceCode") }) } + +func (suite *MTOServiceItemServiceSuite) TestPriceEstimator() { + suite.Run("Calcuating price estimated on creation for HHG ", func() { + setupTestData := func() models.MTOShipment { + // Set up data to use for all Origin SIT Service Item tests + + move := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) + estimatedPrimeWeight := unit.Pound(6000) + pickupDate := time.Date(2024, time.July, 31, 12, 0, 0, 0, time.UTC) + pickupAddress := factory.BuildAddress(suite.DB(), nil, []factory.Trait{factory.GetTraitAddress2}) + deliveryAddress := factory.BuildAddress(suite.DB(), nil, []factory.Trait{factory.GetTraitAddress3}) + + mtoShipment := factory.BuildMTOShipmentMinimal(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + { + Model: pickupAddress, + LinkOnly: true, + Type: &factory.Addresses.PickupAddress, + }, + { + Model: deliveryAddress, + LinkOnly: true, + Type: &factory.Addresses.DeliveryAddress, + }, + { + Model: models.MTOShipment{ + PrimeEstimatedWeight: &estimatedPrimeWeight, + RequestedPickupDate: &pickupDate, + }, + }, + }, nil) + + return mtoShipment + } + + reServiceCodeDOP := factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeDOP) + reServiceCodeDPK := factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeDPK) + reServiceCodeDDP := factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeDDP) + reServiceCodeDUPK := factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeDUPK) + reServiceCodeDLH := factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeDLH) + reServiceCodeDSH := factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeDSH) + reServiceCodeFSC := factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeFSC) + + startDate := time.Now().AddDate(-1, 0, 0) + endDate := startDate.AddDate(1, 1, 1) + sitEntryDate := time.Date(2020, time.October, 24, 0, 0, 0, 0, time.UTC) + sitPostalCode := "99999" + reason := "lorem ipsum" + + contract := testdatagen.FetchOrMakeReContract(suite.DB(), testdatagen.Assertions{}) + contractYear := testdatagen.MakeReContractYear(suite.DB(), + testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + Name: "Test Contract Year", + EscalationCompounded: 1.125, + StartDate: startDate, + EndDate: endDate, + }, + }) + + serviceArea := testdatagen.MakeReDomesticServiceArea(suite.DB(), + testdatagen.Assertions{ + ReDomesticServiceArea: models.ReDomesticServiceArea{ + Contract: contractYear.Contract, + ServiceArea: "945", + ServicesSchedule: 1, + }, + }) + + serviceAreaDest := testdatagen.MakeReDomesticServiceArea(suite.DB(), + testdatagen.Assertions{ + ReDomesticServiceArea: models.ReDomesticServiceArea{ + Contract: contractYear.Contract, + ServiceArea: "503", + ServicesSchedule: 1, + }, + }) + + serviceAreaPriceDOP := models.ReDomesticServiceAreaPrice{ + ContractID: contractYear.Contract.ID, + ServiceID: reServiceCodeDOP.ID, + IsPeakPeriod: true, + DomesticServiceAreaID: serviceArea.ID, + PriceCents: unit.Cents(1234), + } + + serviceAreaPriceDPK := factory.FetchOrMakeDomesticOtherPrice(suite.DB(), []factory.Customization{ + { + Model: models.ReDomesticOtherPrice{ + ContractID: contractYear.Contract.ID, + ServiceID: reServiceCodeDPK.ID, + IsPeakPeriod: true, + Schedule: 1, + PriceCents: unit.Cents(121), + }, + }, + }, nil) + + serviceAreaPriceDDP := models.ReDomesticServiceAreaPrice{ + ContractID: contractYear.Contract.ID, + ServiceID: reServiceCodeDDP.ID, + IsPeakPeriod: true, + DomesticServiceAreaID: serviceAreaDest.ID, + PriceCents: unit.Cents(482), + } + + serviceAreaPriceDUPK := factory.FetchOrMakeDomesticOtherPrice(suite.DB(), []factory.Customization{ + { + Model: models.ReDomesticOtherPrice{ + ContractID: contractYear.Contract.ID, + ServiceID: reServiceCodeDUPK.ID, + IsPeakPeriod: true, + Schedule: 1, + PriceCents: unit.Cents(945), + }, + }, + }, nil) + + serviceAreaPriceDLH := models.ReDomesticLinehaulPrice{ + ContractID: contractYear.Contract.ID, + WeightLower: 500, + WeightUpper: 10000, + MilesLower: 1, + MilesUpper: 10000, + IsPeakPeriod: true, + DomesticServiceAreaID: serviceArea.ID, + PriceMillicents: unit.Millicents(482), + } + + serviceAreaPriceDSH := models.ReDomesticServiceAreaPrice{ + ContractID: contractYear.Contract.ID, + ServiceID: reServiceCodeDSH.ID, + IsPeakPeriod: true, + DomesticServiceAreaID: serviceArea.ID, + PriceCents: unit.Cents(999), + } + + testdatagen.FetchOrMakeGHCDieselFuelPrice(suite.DB(), testdatagen.Assertions{ + GHCDieselFuelPrice: models.GHCDieselFuelPrice{ + FuelPriceInMillicents: unit.Millicents(281400), + PublicationDate: time.Date(2020, time.March, 9, 0, 0, 0, 0, time.UTC), + EffectiveDate: time.Date(2020, time.March, 10, 0, 0, 0, 0, time.UTC), + EndDate: time.Date(2025, time.March, 17, 0, 0, 0, 0, time.UTC), + }, + }) + + suite.MustSave(&serviceAreaPriceDOP) + suite.MustSave(&serviceAreaPriceDPK) + suite.MustSave(&serviceAreaPriceDDP) + suite.MustSave(&serviceAreaPriceDUPK) + suite.MustSave(&serviceAreaPriceDLH) + suite.MustSave(&serviceAreaPriceDSH) + + testdatagen.FetchOrMakeReZip3(suite.DB(), testdatagen.Assertions{ + ReZip3: models.ReZip3{ + Contract: contract, + ContractID: contract.ID, + DomesticServiceArea: serviceArea, + Zip3: "945", + }, + }) + + testdatagen.FetchOrMakeReZip3(suite.DB(), testdatagen.Assertions{ + ReZip3: models.ReZip3{ + Contract: contract, + ContractID: contract.ID, + DomesticServiceArea: serviceAreaDest, + Zip3: "503", + }, + }) + + shipment := setupTestData() + actualPickupAddress := factory.BuildAddress(suite.DB(), nil, []factory.Trait{factory.GetTraitAddress2}) + serviceItemDOP := models.MTOServiceItem{ + MoveTaskOrder: shipment.MoveTaskOrder, + MoveTaskOrderID: shipment.MoveTaskOrderID, + MTOShipment: shipment, + MTOShipmentID: &shipment.ID, + ReService: reServiceCodeDOP, + SITEntryDate: &sitEntryDate, + SITPostalCode: &sitPostalCode, + Reason: &reason, + SITOriginHHGActualAddress: &actualPickupAddress, + Status: models.MTOServiceItemStatusSubmitted, + } + + serviceItemDPK := models.MTOServiceItem{ + MoveTaskOrder: shipment.MoveTaskOrder, + MoveTaskOrderID: shipment.MoveTaskOrderID, + MTOShipment: shipment, + MTOShipmentID: &shipment.ID, + ReService: reServiceCodeDPK, + SITEntryDate: &sitEntryDate, + SITPostalCode: &sitPostalCode, + Reason: &reason, + SITOriginHHGActualAddress: &actualPickupAddress, + Status: models.MTOServiceItemStatusSubmitted, + } + + serviceItemDDP := models.MTOServiceItem{ + MoveTaskOrder: shipment.MoveTaskOrder, + MoveTaskOrderID: shipment.MoveTaskOrderID, + MTOShipment: shipment, + MTOShipmentID: &shipment.ID, + ReService: reServiceCodeDDP, + SITEntryDate: &sitEntryDate, + SITPostalCode: &sitPostalCode, + Reason: &reason, + SITOriginHHGActualAddress: &actualPickupAddress, + Status: models.MTOServiceItemStatusSubmitted, + } + + serviceItemDUPK := models.MTOServiceItem{ + MoveTaskOrder: shipment.MoveTaskOrder, + MoveTaskOrderID: shipment.MoveTaskOrderID, + MTOShipment: shipment, + MTOShipmentID: &shipment.ID, + ReService: reServiceCodeDUPK, + SITEntryDate: &sitEntryDate, + SITPostalCode: &sitPostalCode, + Reason: &reason, + SITOriginHHGActualAddress: &actualPickupAddress, + Status: models.MTOServiceItemStatusSubmitted, + } + + serviceItemDLH := models.MTOServiceItem{ + MoveTaskOrder: shipment.MoveTaskOrder, + MoveTaskOrderID: shipment.MoveTaskOrderID, + MTOShipment: shipment, + MTOShipmentID: &shipment.ID, + ReService: reServiceCodeDLH, + SITEntryDate: &sitEntryDate, + SITPostalCode: &sitPostalCode, + Reason: &reason, + SITOriginHHGActualAddress: &actualPickupAddress, + Status: models.MTOServiceItemStatusSubmitted, + } + + serviceItemDSH := models.MTOServiceItem{ + MoveTaskOrder: shipment.MoveTaskOrder, + MoveTaskOrderID: shipment.MoveTaskOrderID, + MTOShipment: shipment, + MTOShipmentID: &shipment.ID, + ReService: reServiceCodeDSH, + SITEntryDate: &sitEntryDate, + SITPostalCode: &sitPostalCode, + Reason: &reason, + SITOriginHHGActualAddress: &actualPickupAddress, + Status: models.MTOServiceItemStatusSubmitted, + } + + serviceItemFSC := models.MTOServiceItem{ + MoveTaskOrder: shipment.MoveTaskOrder, + MoveTaskOrderID: shipment.MoveTaskOrderID, + MTOShipment: shipment, + MTOShipmentID: &shipment.ID, + ReService: reServiceCodeFSC, + SITEntryDate: &sitEntryDate, + SITPostalCode: &sitPostalCode, + Reason: &reason, + SITOriginHHGActualAddress: &actualPickupAddress, + Status: models.MTOServiceItemStatusSubmitted, + } + + builder := query.NewQueryBuilder() + moveRouter := moverouter.NewMoveRouter() + planner := &mocks.Planner{} + planner.On("ZipTransitDistance", + mock.AnythingOfType("*appcontext.appContext"), + mock.Anything, + mock.Anything, + false, + false, + ).Return(400, nil) + creator := NewMTOServiceItemCreator(planner, builder, moveRouter, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticPackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticShorthaulPricer(), ghcrateengine.NewDomesticOriginPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) + + dopEstimatedPriceInCents, _ := creator.FindEstimatedPrice(suite.AppContextForTest(), &serviceItemDOP, shipment) + suite.Equal(unit.Cents(67188), dopEstimatedPriceInCents) + + dpkEstimatedPriceInCents, _ := creator.FindEstimatedPrice(suite.AppContextForTest(), &serviceItemDPK, shipment) + suite.Equal(unit.Cents(594000), dpkEstimatedPriceInCents) + + ddpEstimatedPriceInCents, _ := creator.FindEstimatedPrice(suite.AppContextForTest(), &serviceItemDDP, shipment) + suite.Equal(unit.Cents(46464), ddpEstimatedPriceInCents) + + dupkEstimatedPriceInCents, _ := creator.FindEstimatedPrice(suite.AppContextForTest(), &serviceItemDUPK, shipment) + suite.Equal(unit.Cents(48246), dupkEstimatedPriceInCents) + + dlhEstimatedPriceInCents, _ := creator.FindEstimatedPrice(suite.AppContextForTest(), &serviceItemDLH, shipment) + suite.Equal(unit.Cents(13619760), dlhEstimatedPriceInCents) + + dshEstimatedPriceInCents, _ := creator.FindEstimatedPrice(suite.AppContextForTest(), &serviceItemDSH, shipment) + suite.Equal(unit.Cents(11088000), dshEstimatedPriceInCents) + + fscEstimatedPriceInCents, _ := creator.FindEstimatedPrice(suite.AppContextForTest(), &serviceItemFSC, shipment) + suite.Equal(unit.Cents(-168), fscEstimatedPriceInCents) + }) + + suite.Run("Calcuating price estimated on creation for NTS shipment ", func() { + setupTestData := func() models.MTOShipment { + // Set up data to use for all Origin SIT Service Item tests + + move := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) + estimatedPrimeWeight := unit.Pound(6000) + pickupDate := time.Date(2024, time.July, 31, 12, 0, 0, 0, time.UTC) + pickupAddress := factory.BuildAddress(suite.DB(), nil, []factory.Trait{factory.GetTraitAddress2}) + deliveryAddress := factory.BuildAddress(suite.DB(), nil, []factory.Trait{factory.GetTraitAddress3}) + + mtoShipment := factory.BuildMTOShipmentMinimal(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + { + Model: pickupAddress, + LinkOnly: true, + Type: &factory.Addresses.PickupAddress, + }, + { + Model: deliveryAddress, + LinkOnly: true, + Type: &factory.Addresses.DeliveryAddress, + }, + { + Model: models.MTOShipment{ + PrimeEstimatedWeight: &estimatedPrimeWeight, + RequestedPickupDate: &pickupDate, + ShipmentType: models.MTOShipmentTypeHHGOutOfNTS, + }, + }, + }, nil) + + return mtoShipment + } + + reServiceCodeDOP := factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeDOP) + reServiceCodeDPK := factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeDPK) + reServiceCodeDDP := factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeDDP) + reServiceCodeDUPK := factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeDUPK) + reServiceCodeDLH := factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeDLH) + reServiceCodeDSH := factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeDSH) + reServiceCodeFSC := factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeFSC) + + startDate := time.Now().AddDate(-1, 0, 0) + endDate := startDate.AddDate(1, 1, 1) + sitEntryDate := time.Date(2020, time.October, 24, 0, 0, 0, 0, time.UTC) + sitPostalCode := "99999" + reason := "lorem ipsum" + + contract := testdatagen.FetchOrMakeReContract(suite.DB(), testdatagen.Assertions{}) + contractYear := testdatagen.FetchOrMakeReContractYear(suite.DB(), + testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + Name: "Test Contract Year", + EscalationCompounded: 1.125, + StartDate: startDate, + EndDate: endDate, + }, + }) + + serviceArea := testdatagen.FetchOrMakeReDomesticServiceArea(suite.DB(), + testdatagen.Assertions{ + ReDomesticServiceArea: models.ReDomesticServiceArea{ + Contract: contractYear.Contract, + ServiceArea: "945", + ServicesSchedule: 1, + }, + }) + + serviceAreaDest := testdatagen.MakeReDomesticServiceArea(suite.DB(), + testdatagen.Assertions{ + ReDomesticServiceArea: models.ReDomesticServiceArea{ + Contract: contractYear.Contract, + ServiceArea: "503", + ServicesSchedule: 1, + }, + }) + + serviceAreaPriceDOP := models.ReDomesticServiceAreaPrice{ + ContractID: contractYear.Contract.ID, + ServiceID: reServiceCodeDOP.ID, + IsPeakPeriod: true, + DomesticServiceAreaID: serviceArea.ID, + PriceCents: unit.Cents(1234), + } + + serviceAreaPriceDPK := factory.FetchOrMakeDomesticOtherPrice(suite.DB(), []factory.Customization{ + { + Model: models.ReDomesticOtherPrice{ + ContractID: contractYear.Contract.ID, + ServiceID: reServiceCodeDPK.ID, + IsPeakPeriod: true, + Schedule: 1, + PriceCents: unit.Cents(121), + }, + }, + }, nil) + + serviceAreaPriceDDP := models.ReDomesticServiceAreaPrice{ + ContractID: contractYear.Contract.ID, + ServiceID: reServiceCodeDDP.ID, + IsPeakPeriod: true, + DomesticServiceAreaID: serviceAreaDest.ID, + PriceCents: unit.Cents(482), + } + + serviceAreaPriceDUPK := factory.FetchOrMakeDomesticOtherPrice(suite.DB(), []factory.Customization{ + { + Model: models.ReDomesticOtherPrice{ + ContractID: contractYear.Contract.ID, + ServiceID: reServiceCodeDUPK.ID, + IsPeakPeriod: true, + Schedule: 1, + PriceCents: unit.Cents(945), + }, + }, + }, nil) + + serviceAreaPriceDLH := models.ReDomesticLinehaulPrice{ + ContractID: contractYear.Contract.ID, + WeightLower: 500, + WeightUpper: 10000, + MilesLower: 1, + MilesUpper: 10000, + IsPeakPeriod: true, + DomesticServiceAreaID: serviceArea.ID, + PriceMillicents: unit.Millicents(482), + } + + serviceAreaPriceDSH := models.ReDomesticServiceAreaPrice{ + ContractID: contractYear.Contract.ID, + ServiceID: reServiceCodeDSH.ID, + IsPeakPeriod: true, + DomesticServiceAreaID: serviceArea.ID, + PriceCents: unit.Cents(999), + } + + testdatagen.FetchOrMakeGHCDieselFuelPrice(suite.DB(), testdatagen.Assertions{ + GHCDieselFuelPrice: models.GHCDieselFuelPrice{ + FuelPriceInMillicents: unit.Millicents(281400), + PublicationDate: time.Date(2020, time.March, 9, 0, 0, 0, 0, time.UTC), + EffectiveDate: time.Date(2020, time.March, 10, 0, 0, 0, 0, time.UTC), + EndDate: time.Date(2025, time.March, 17, 0, 0, 0, 0, time.UTC), + }, + }) + + suite.MustSave(&serviceAreaPriceDOP) + suite.MustSave(&serviceAreaPriceDPK) + suite.MustSave(&serviceAreaPriceDDP) + suite.MustSave(&serviceAreaPriceDUPK) + suite.MustSave(&serviceAreaPriceDLH) + suite.MustSave(&serviceAreaPriceDSH) + + testdatagen.FetchOrMakeReZip3(suite.DB(), testdatagen.Assertions{ + ReZip3: models.ReZip3{ + Contract: contract, + ContractID: contract.ID, + DomesticServiceArea: serviceArea, + Zip3: "945", + }, + }) + + testdatagen.FetchOrMakeReZip3(suite.DB(), testdatagen.Assertions{ + ReZip3: models.ReZip3{ + Contract: contract, + ContractID: contract.ID, + DomesticServiceArea: serviceAreaDest, + Zip3: "503", + }, + }) + + shipment := setupTestData() + actualPickupAddress := factory.BuildAddress(suite.DB(), nil, []factory.Trait{factory.GetTraitAddress2}) + serviceItemDOP := models.MTOServiceItem{ + MoveTaskOrder: shipment.MoveTaskOrder, + MoveTaskOrderID: shipment.MoveTaskOrderID, + MTOShipment: shipment, + MTOShipmentID: &shipment.ID, + ReService: reServiceCodeDOP, + SITEntryDate: &sitEntryDate, + SITPostalCode: &sitPostalCode, + Reason: &reason, + SITOriginHHGActualAddress: &actualPickupAddress, + Status: models.MTOServiceItemStatusSubmitted, + } + + serviceItemDPK := models.MTOServiceItem{ + MoveTaskOrder: shipment.MoveTaskOrder, + MoveTaskOrderID: shipment.MoveTaskOrderID, + MTOShipment: shipment, + MTOShipmentID: &shipment.ID, + ReService: reServiceCodeDPK, + SITEntryDate: &sitEntryDate, + SITPostalCode: &sitPostalCode, + Reason: &reason, + SITOriginHHGActualAddress: &actualPickupAddress, + Status: models.MTOServiceItemStatusSubmitted, + } + + serviceItemDDP := models.MTOServiceItem{ + MoveTaskOrder: shipment.MoveTaskOrder, + MoveTaskOrderID: shipment.MoveTaskOrderID, + MTOShipment: shipment, + MTOShipmentID: &shipment.ID, + ReService: reServiceCodeDDP, + SITEntryDate: &sitEntryDate, + SITPostalCode: &sitPostalCode, + Reason: &reason, + SITOriginHHGActualAddress: &actualPickupAddress, + Status: models.MTOServiceItemStatusSubmitted, + } + + serviceItemDUPK := models.MTOServiceItem{ + MoveTaskOrder: shipment.MoveTaskOrder, + MoveTaskOrderID: shipment.MoveTaskOrderID, + MTOShipment: shipment, + MTOShipmentID: &shipment.ID, + ReService: reServiceCodeDUPK, + SITEntryDate: &sitEntryDate, + SITPostalCode: &sitPostalCode, + Reason: &reason, + SITOriginHHGActualAddress: &actualPickupAddress, + Status: models.MTOServiceItemStatusSubmitted, + } + + serviceItemDLH := models.MTOServiceItem{ + MoveTaskOrder: shipment.MoveTaskOrder, + MoveTaskOrderID: shipment.MoveTaskOrderID, + MTOShipment: shipment, + MTOShipmentID: &shipment.ID, + ReService: reServiceCodeDLH, + SITEntryDate: &sitEntryDate, + SITPostalCode: &sitPostalCode, + Reason: &reason, + SITOriginHHGActualAddress: &actualPickupAddress, + Status: models.MTOServiceItemStatusSubmitted, + } + + serviceItemDSH := models.MTOServiceItem{ + MoveTaskOrder: shipment.MoveTaskOrder, + MoveTaskOrderID: shipment.MoveTaskOrderID, + MTOShipment: shipment, + MTOShipmentID: &shipment.ID, + ReService: reServiceCodeDSH, + SITEntryDate: &sitEntryDate, + SITPostalCode: &sitPostalCode, + Reason: &reason, + SITOriginHHGActualAddress: &actualPickupAddress, + Status: models.MTOServiceItemStatusSubmitted, + } + + serviceItemFSC := models.MTOServiceItem{ + MoveTaskOrder: shipment.MoveTaskOrder, + MoveTaskOrderID: shipment.MoveTaskOrderID, + MTOShipment: shipment, + MTOShipmentID: &shipment.ID, + ReService: reServiceCodeFSC, + SITEntryDate: &sitEntryDate, + SITPostalCode: &sitPostalCode, + Reason: &reason, + SITOriginHHGActualAddress: &actualPickupAddress, + Status: models.MTOServiceItemStatusSubmitted, + } + + builder := query.NewQueryBuilder() + moveRouter := moverouter.NewMoveRouter() + planner := &mocks.Planner{} + planner.On("ZipTransitDistance", + mock.AnythingOfType("*appcontext.appContext"), + mock.Anything, + mock.Anything, + false, + false, + ).Return(800, nil) + creator := NewMTOServiceItemCreator(planner, builder, moveRouter, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticPackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticShorthaulPricer(), ghcrateengine.NewDomesticOriginPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) + + dopEstimatedPriceInCents, _ := creator.FindEstimatedPrice(suite.AppContextForTest(), &serviceItemDOP, shipment) + suite.Equal(unit.Cents(67188), dopEstimatedPriceInCents) + + dpkEstimatedPriceInCents, _ := creator.FindEstimatedPrice(suite.AppContextForTest(), &serviceItemDPK, shipment) + suite.Equal(unit.Cents(594000), dpkEstimatedPriceInCents) + + ddpEstimatedPriceInCents, _ := creator.FindEstimatedPrice(suite.AppContextForTest(), &serviceItemDDP, shipment) + suite.Equal(unit.Cents(46464), ddpEstimatedPriceInCents) + + dupkEstimatedPriceInCents, _ := creator.FindEstimatedPrice(suite.AppContextForTest(), &serviceItemDUPK, shipment) + suite.Equal(unit.Cents(48246), dupkEstimatedPriceInCents) + + dlhEstimatedPriceInCents, _ := creator.FindEstimatedPrice(suite.AppContextForTest(), &serviceItemDLH, shipment) + suite.Equal(unit.Cents(29990400), dlhEstimatedPriceInCents) + + dshEstimatedPriceInCents, _ := creator.FindEstimatedPrice(suite.AppContextForTest(), &serviceItemDSH, shipment) + suite.Equal(unit.Cents(22176000), dshEstimatedPriceInCents) + + fscEstimatedPriceInCents, _ := creator.FindEstimatedPrice(suite.AppContextForTest(), &serviceItemFSC, shipment) + suite.Equal(unit.Cents(-335), fscEstimatedPriceInCents) + }) + +} +func (suite *MTOServiceItemServiceSuite) TestGetAdjustedWeight() { + suite.Run("If no weight is provided", func() { + var incomingWeight unit.Pound + adjustedWeight := GetAdjustedWeight(incomingWeight, false) + suite.Equal(unit.Pound(0), *adjustedWeight) + }) + suite.Run("If a weight of 0 is provided", func() { + incomingWeight := unit.Pound(0) + adjustedWeight := GetAdjustedWeight(incomingWeight, false) + suite.Equal(unit.Pound(0), *adjustedWeight) + }) + suite.Run("If weight of 100 is provided", func() { + incomingWeight := unit.Pound(100) + adjustedWeight := GetAdjustedWeight(incomingWeight, false) + suite.Equal(unit.Pound(500), *adjustedWeight) + }) + suite.Run("If weight of 454 is provided", func() { + incomingWeight := unit.Pound(454) + adjustedWeight := GetAdjustedWeight(incomingWeight, false) + suite.Equal(unit.Pound(500), *adjustedWeight) + }) + suite.Run("If weight of 456 is provided", func() { + incomingWeight := unit.Pound(456) + adjustedWeight := GetAdjustedWeight(incomingWeight, false) + suite.Equal(unit.Pound(501), *adjustedWeight) + }) + suite.Run("If weight of 1000 is provided", func() { + incomingWeight := unit.Pound(1000) + adjustedWeight := GetAdjustedWeight(incomingWeight, false) + suite.Equal(unit.Pound(1100), *adjustedWeight) + }) + + suite.Run("If no weight is provided UB", func() { + var incomingWeight unit.Pound + adjustedWeight := GetAdjustedWeight(incomingWeight, true) + suite.Equal(unit.Pound(0), *adjustedWeight) + }) + suite.Run("If a weight of 0 is provided UB", func() { + incomingWeight := unit.Pound(0) + adjustedWeight := GetAdjustedWeight(incomingWeight, true) + suite.Equal(unit.Pound(0), *adjustedWeight) + }) + suite.Run("If weight of 100 is provided UB", func() { + incomingWeight := unit.Pound(100) + adjustedWeight := GetAdjustedWeight(incomingWeight, true) + suite.Equal(unit.Pound(300), *adjustedWeight) + }) + suite.Run("If weight of 272 is provided UB", func() { + incomingWeight := unit.Pound(272) + adjustedWeight := GetAdjustedWeight(incomingWeight, true) + suite.Equal(unit.Pound(300), *adjustedWeight) + }) + suite.Run("If weight of 274 is provided UB", func() { + incomingWeight := unit.Pound(274) + adjustedWeight := GetAdjustedWeight(incomingWeight, true) + suite.Equal(unit.Pound(301), *adjustedWeight) + }) + suite.Run("If weight of 1000 is provided UB", func() { + incomingWeight := unit.Pound(1000) + adjustedWeight := GetAdjustedWeight(incomingWeight, true) + suite.Equal(unit.Pound(1100), *adjustedWeight) + }) +} diff --git a/pkg/services/mto_service_item/mto_service_item_updater.go b/pkg/services/mto_service_item/mto_service_item_updater.go index 3c17ceff042..b5eaeae3caa 100644 --- a/pkg/services/mto_service_item/mto_service_item_updater.go +++ b/pkg/services/mto_service_item/mto_service_item_updater.go @@ -3,6 +3,7 @@ package mtoserviceitem import ( "database/sql" "fmt" + "strconv" "time" "github.com/gobuffalo/validate/v3" @@ -19,6 +20,7 @@ import ( movetaskorder "github.com/transcom/mymove/pkg/services/move_task_order" "github.com/transcom/mymove/pkg/services/query" sitstatus "github.com/transcom/mymove/pkg/services/sit_status" + "github.com/transcom/mymove/pkg/unit" ) // OriginSITLocation is the constant representing when the shipment in storage occurs at the origin @@ -43,17 +45,21 @@ type mtoServiceItemUpdater struct { moveRouter services.MoveRouter shipmentFetcher services.MTOShipmentFetcher addressCreator services.AddressCreator + unpackPricer services.DomesticUnpackPricer + linehaulPricer services.DomesticLinehaulPricer + destinationPricer services.DomesticDestinationPricer + fuelSurchargePricer services.FuelSurchargePricer portLocationFetcher services.PortLocationFetcher } // NewMTOServiceItemUpdater returns a new mto service item updater -func NewMTOServiceItemUpdater(planner route.Planner, builder mtoServiceItemQueryBuilder, moveRouter services.MoveRouter, shipmentFetcher services.MTOShipmentFetcher, addressCreator services.AddressCreator, portLocationFetcher services.PortLocationFetcher) services.MTOServiceItemUpdater { +func NewMTOServiceItemUpdater(planner route.Planner, builder mtoServiceItemQueryBuilder, moveRouter services.MoveRouter, shipmentFetcher services.MTOShipmentFetcher, addressCreator services.AddressCreator, portLocationFetcher services.PortLocationFetcher, unpackPricer services.DomesticUnpackPricer, linehaulPricer services.DomesticLinehaulPricer, destinationPricer services.DomesticDestinationPricer, fuelSurchargePricer services.FuelSurchargePricer) services.MTOServiceItemUpdater { // used inside a transaction and mocking return &mtoServiceItemUpdater{builder: builder} createNewBuilder := func() mtoServiceItemQueryBuilder { return query.NewQueryBuilder() } - return &mtoServiceItemUpdater{planner, builder, createNewBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher} + return &mtoServiceItemUpdater{planner, builder, createNewBuilder, moveRouter, shipmentFetcher, addressCreator, unpackPricer, linehaulPricer, destinationPricer, fuelSurchargePricer, portLocationFetcher} } func (p *mtoServiceItemUpdater) ApproveOrRejectServiceItem( @@ -119,6 +125,134 @@ func (p *mtoServiceItemUpdater) ConvertItemToCustomerExpense( return p.convertItemToCustomerExpense(appCtx, *mtoServiceItem, customerExpenseReason, convertToCustomerExpense, eTag, checkETag()) } +func (p *mtoServiceItemUpdater) findEstimatedPrice(appCtx appcontext.AppContext, serviceItem *models.MTOServiceItem, mtoShipment models.MTOShipment) (unit.Cents, error) { + if serviceItem.ReService.Code == models.ReServiceCodeDDP || + serviceItem.ReService.Code == models.ReServiceCodeDUPK || + serviceItem.ReService.Code == models.ReServiceCodeDLH || + serviceItem.ReService.Code == models.ReServiceCodeFSC || + serviceItem.ReService.Code == models.ReServiceCodeDDDSIT || + serviceItem.ReService.Code == models.ReServiceCodeDDSFSC { + + isPPM := false + if mtoShipment.ShipmentType == models.MTOShipmentTypePPM { + isPPM = true + } + + var pickupDate *time.Time + if mtoShipment.ActualPickupDate != nil { + pickupDate = mtoShipment.ActualPickupDate + } else { + if mtoShipment.RequestedPickupDate != nil { + pickupDate = mtoShipment.RequestedPickupDate + } + } + + currTime := time.Now() + var distance int + + var shipmentWeight unit.Pound + if mtoShipment.PrimeActualWeight != nil { + shipmentWeight = *mtoShipment.PrimeActualWeight + } else { + if mtoShipment.PrimeEstimatedWeight != nil { + shipmentWeight = *mtoShipment.PrimeEstimatedWeight + } else { + return 0, apperror.NewInvalidInputError(serviceItem.ID, nil, nil, "No estimated or actual weight exists for this service item.") + } + } + + contractCode, err := FetchContractCode(appCtx, currTime) + if err != nil && pickupDate != nil { + contractCode, err = FetchContractCode(appCtx, *pickupDate) + if err != nil { + return 0, err + } + } + + var price unit.Cents + + // destination + if serviceItem.ReService.Code == models.ReServiceCodeDDP { + var domesticServiceArea models.ReDomesticServiceArea + if mtoShipment.DestinationAddress != nil { + domesticServiceArea, err = fetchDomesticServiceArea(appCtx, contractCode, mtoShipment.DestinationAddress.PostalCode) + if err != nil { + return 0, err + } + } + + adjustedWeight := GetAdjustedWeight(shipmentWeight, mtoShipment.ShipmentType == models.MTOShipmentTypeUnaccompaniedBaggage) + price, _, err = p.destinationPricer.Price(appCtx, contractCode, *pickupDate, *adjustedWeight, domesticServiceArea.ServiceArea, isPPM) + if err != nil { + return 0, err + } + } + // linehaul + if serviceItem.ReService.Code == models.ReServiceCodeDLH { + domesticServiceArea, err := fetchDomesticServiceArea(appCtx, contractCode, mtoShipment.PickupAddress.PostalCode) + if err != nil { + return 0, err + } + if mtoShipment.PickupAddress != nil && mtoShipment.DestinationAddress != nil { + distance, err = p.planner.ZipTransitDistance(appCtx, mtoShipment.PickupAddress.PostalCode, mtoShipment.DestinationAddress.PostalCode, false) + if err != nil { + return 0, err + } + } + + adjustedWeight := GetAdjustedWeight(shipmentWeight, mtoShipment.ShipmentType == models.MTOShipmentTypeUnaccompaniedBaggage) + price, _, err = p.linehaulPricer.Price(appCtx, contractCode, *pickupDate, unit.Miles(distance), *adjustedWeight, domesticServiceArea.ServiceArea, isPPM) + if err != nil { + return 0, err + } + } + // unpacking + if serviceItem.ReService.Code == models.ReServiceCodeDUPK { + domesticServiceArea, err := fetchDomesticServiceArea(appCtx, contractCode, mtoShipment.DestinationAddress.PostalCode) + if err != nil { + return 0, err + } + + adjustedWeight := GetAdjustedWeight(shipmentWeight, mtoShipment.ShipmentType == models.MTOShipmentTypeUnaccompaniedBaggage) + price, _, err = p.unpackPricer.Price(appCtx, contractCode, *pickupDate, *adjustedWeight, domesticServiceArea.ServicesSchedule, isPPM) + if err != nil { + return 0, err + } + } + // fuel surcharge + if serviceItem.ReService.Code == models.ReServiceCodeFSC { + if mtoShipment.PickupAddress != nil && mtoShipment.DestinationAddress != nil { + distance, err = p.planner.ZipTransitDistance(appCtx, mtoShipment.PickupAddress.PostalCode, mtoShipment.DestinationAddress.PostalCode, false) + if err != nil { + return 0, err + } + } + + fscWeightBasedDistanceMultiplier, err := LookupFSCWeightBasedDistanceMultiplier(appCtx, shipmentWeight) + if err != nil { + return 0, err + } + fscWeightBasedDistanceMultiplierFloat, err := strconv.ParseFloat(fscWeightBasedDistanceMultiplier, 64) + if err != nil { + return 0, err + } + eiaFuelPrice, err := LookupEIAFuelPrice(appCtx, *pickupDate) + if err != nil { + return 0, err + } + + adjustedWeight := GetAdjustedWeight(shipmentWeight, mtoShipment.ShipmentType == models.MTOShipmentTypeUnaccompaniedBaggage) + price, _, err = p.fuelSurchargePricer.Price(appCtx, *pickupDate, unit.Miles(distance), *adjustedWeight, fscWeightBasedDistanceMultiplierFloat, eiaFuelPrice, isPPM) + if err != nil { + return 0, err + } + + } + return price, nil + } + return 0, nil +} + func (p *mtoServiceItemUpdater) findServiceItem(appCtx appcontext.AppContext, serviceItemID uuid.UUID) (*models.MTOServiceItem, error) { var serviceItem models.MTOServiceItem err := appCtx.DB().Q().EagerPreload( @@ -327,6 +461,21 @@ func (p *mtoServiceItemUpdater) convertItemToCustomerExpense( return &serviceItem, nil } +// UpdateMTOServiceItemPricingEstimate updates the MTO Service Item pricing estimate +func (p *mtoServiceItemUpdater) UpdateMTOServiceItemPricingEstimate( + appCtx appcontext.AppContext, + mtoServiceItem *models.MTOServiceItem, + shipment models.MTOShipment, + eTag string, +) (*models.MTOServiceItem, error) { + estimatedPrice, err := p.findEstimatedPrice(appCtx, mtoServiceItem, shipment) + if estimatedPrice != 0 && err == nil { + mtoServiceItem.PricingEstimate = &estimatedPrice + return p.UpdateMTOServiceItem(appCtx, mtoServiceItem, eTag, UpdateMTOServiceItemBasicValidator) + } + return mtoServiceItem, err +} + // UpdateMTOServiceItemBasic updates the MTO Service Item using base validators func (p *mtoServiceItemUpdater) UpdateMTOServiceItemBasic( appCtx appcontext.AppContext, diff --git a/pkg/services/mto_service_item/mto_service_item_updater_test.go b/pkg/services/mto_service_item/mto_service_item_updater_test.go index 95f5191681a..a1192cc39fe 100644 --- a/pkg/services/mto_service_item/mto_service_item_updater_test.go +++ b/pkg/services/mto_service_item/mto_service_item_updater_test.go @@ -24,6 +24,7 @@ import ( "github.com/transcom/mymove/pkg/models/roles" mocks "github.com/transcom/mymove/pkg/route/mocks" "github.com/transcom/mymove/pkg/services/address" + "github.com/transcom/mymove/pkg/services/ghcrateengine" moverouter "github.com/transcom/mymove/pkg/services/move" movetaskorder "github.com/transcom/mymove/pkg/services/move_task_order" mtoshipment "github.com/transcom/mymove/pkg/services/mto_shipment" @@ -52,7 +53,7 @@ func (suite *MTOServiceItemServiceSuite) TestMTOServiceItemUpdater() { mock.Anything, false, ).Return(400, nil) - updater := NewMTOServiceItemUpdater(planner, builder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher) + updater := NewMTOServiceItemUpdater(planner, builder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) setupServiceItem := func() (models.MTOServiceItem, string) { serviceItem := testdatagen.MakeDefaultMTOServiceItem(suite.DB()) @@ -2169,7 +2170,7 @@ func (suite *MTOServiceItemServiceSuite) TestUpdateMTOServiceItemStatus() { mock.Anything, false, ).Return(400, nil) - updater := NewMTOServiceItemUpdater(planner, builder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher) + updater := NewMTOServiceItemUpdater(planner, builder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) rejectionReason := models.StringPointer("") @@ -2801,6 +2802,105 @@ func (suite *MTOServiceItemServiceSuite) TestUpdateMTOServiceItemStatus() { }) } +func (suite *MTOServiceItemServiceSuite) setupServiceItemData() { + startDate := time.Date(2020, time.January, 1, 12, 0, 0, 0, time.UTC) + endDate := time.Date(2020, time.December, 31, 12, 0, 0, 0, time.UTC) + + testdatagen.FetchOrMakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + StartDate: startDate, + EndDate: endDate, + }, + }) + + originalDomesticServiceArea := testdatagen.FetchOrMakeReDomesticServiceArea(suite.DB(), testdatagen.Assertions{ + ReDomesticServiceArea: models.ReDomesticServiceArea{ + ServiceArea: "004", + ServicesSchedule: 2, + }, + ReContract: testdatagen.FetchOrMakeReContract(suite.DB(), testdatagen.Assertions{}), + }) + + testdatagen.FetchOrMakeReZip3(suite.DB(), testdatagen.Assertions{ + ReZip3: models.ReZip3{ + Contract: originalDomesticServiceArea.Contract, + ContractID: originalDomesticServiceArea.ContractID, + DomesticServiceArea: originalDomesticServiceArea, + Zip3: "902", + }, + }) + + testdatagen.FetchOrMakeReDomesticLinehaulPrice(suite.DB(), testdatagen.Assertions{ + ReDomesticLinehaulPrice: models.ReDomesticLinehaulPrice{ + Contract: originalDomesticServiceArea.Contract, + ContractID: originalDomesticServiceArea.ContractID, + DomesticServiceArea: originalDomesticServiceArea, + DomesticServiceAreaID: originalDomesticServiceArea.ID, + WeightLower: unit.Pound(500), + WeightUpper: unit.Pound(9999), + MilesLower: 250, + MilesUpper: 9999, + PriceMillicents: unit.Millicents(606800), + IsPeakPeriod: false, + }, + }) +} + +func (suite *MTOServiceItemServiceSuite) TestUpdateMTOServiceItemPricingEstimate() { + builder := query.NewQueryBuilder() + moveRouter := moverouter.NewMoveRouter() + shipmentFetcher := mtoshipment.NewMTOShipmentFetcher() + addressCreator := address.NewAddressCreator() + planner := &mocks.Planner{} + planner.On("ZipTransitDistance", + mock.AnythingOfType("*appcontext.appContext"), + mock.Anything, + mock.Anything, + ).Return(400, nil) + updater := NewMTOServiceItemUpdater(planner, builder, moveRouter, shipmentFetcher, addressCreator, portlocation.NewPortLocationFetcher(), ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) + + setupServiceItem := func() (models.MTOServiceItem, string) { + serviceItem := testdatagen.MakeDefaultMTOServiceItem(suite.DB()) + eTag := etag.GenerateEtag(serviceItem.UpdatedAt) + return serviceItem, eTag + } + + setupServiceItems := func() models.MTOServiceItems { + serviceItems := testdatagen.MakeMTOServiceItems(suite.DB()) + return serviceItems + } + + suite.Run("Validation Error", func() { + suite.setupServiceItemData() + serviceItem, eTag := setupServiceItem() + invalidServiceItem := serviceItem + invalidServiceItem.MoveTaskOrderID = serviceItem.ID // invalid Move ID + + updatedServiceItem, err := updater.UpdateMTOServiceItemPricingEstimate(suite.AppContextForTest(), &invalidServiceItem, serviceItem.MTOShipment, eTag) + + suite.Nil(updatedServiceItem) + suite.Error(err) + suite.IsType(apperror.InvalidInputError{}, err) + + invalidInputError := err.(apperror.InvalidInputError) + suite.True(invalidInputError.ValidationErrors.HasAny()) + suite.Contains(invalidInputError.ValidationErrors.Keys(), "moveTaskOrderID") + }) + + suite.Run("Returns updated service item on success wihtout error", func() { + suite.setupServiceItemData() + serviceItems := setupServiceItems() + + for _, serviceItem := range serviceItems { + eTag := etag.GenerateEtag(serviceItem.UpdatedAt) + updatedServiceItem, err := updater.UpdateMTOServiceItemPricingEstimate(suite.AppContextForTest(), &serviceItem, serviceItem.MTOShipment, eTag) + + suite.NotNil(updatedServiceItem) + suite.Nil(err) + } + }) +} + // Helper function to create a rejected service item func buildRejectedServiceItem(suite *MTOServiceItemServiceSuite, reServiceCode models.ReServiceCode, reason string, contactDatePlusGracePeriod, aMonthAgo, now, sitRequestedDelivery time.Time, requestApprovalsRequestedStatus bool) models.MTOServiceItem { return factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ diff --git a/pkg/services/mto_service_item/mto_service_item_validators.go b/pkg/services/mto_service_item/mto_service_item_validators.go index 25a6768c335..3b7d4cc7fc1 100644 --- a/pkg/services/mto_service_item/mto_service_item_validators.go +++ b/pkg/services/mto_service_item/mto_service_item_validators.go @@ -48,6 +48,8 @@ var allSITServiceItemsToCheck = []models.ReServiceCode{ var allAccessorialServiceItemsToCheck = []models.ReServiceCode{ models.ReServiceCodeIDSHUT, models.ReServiceCodeIOSHUT, + models.ReServiceCodeICRT, + models.ReServiceCodeIUCRT, } var destSITServiceItems = []models.ReServiceCode{ @@ -135,7 +137,7 @@ func (v *primeUpdateMTOServiceItemValidator) validate(appCtx appcontext.AppConte } // Checks that the SITDepartureDate - // - is not later than the authorized end date + // - is not earlier than the SIT entry date err = serviceItemData.checkSITDepartureDate(appCtx) if err != nil { return err @@ -344,13 +346,10 @@ func (v *updateMTOServiceItemData) checkOldServiceItemStatus(_ appcontext.AppCon } if slices.Contains(allAccessorialServiceItemsToCheck, serviceItemData.oldServiceItem.ReService.Code) { + invalidFieldChange := false if serviceItemData.oldServiceItem.Status == models.MTOServiceItemStatusRejected { return nil - } else if serviceItemData.oldServiceItem.Status == models.MTOServiceItemStatusApproved { - - invalidFieldChange := false - // Fields that are not allowed to change when status is approved - + } else if serviceItemData.oldServiceItem.Status == models.MTOServiceItemStatusSubmitted || serviceItemData.oldServiceItem.Status == models.MTOServiceItemStatusApproved { if serviceItemData.updatedServiceItem.ReService.Code.String() != "" && serviceItemData.updatedServiceItem.ReService.Code.String() != serviceItemData.oldServiceItem.ReService.Code.String() { invalidFieldChange = true } @@ -359,6 +358,10 @@ func (v *updateMTOServiceItemData) checkOldServiceItemStatus(_ appcontext.AppCon invalidFieldChange = true } + if serviceItemData.updatedServiceItem.EstimatedWeight != nil { + invalidFieldChange = true + } + if serviceItemData.updatedServiceItem.RequestedApprovalsRequestedStatus != nil { invalidFieldChange = true } @@ -366,10 +369,10 @@ func (v *updateMTOServiceItemData) checkOldServiceItemStatus(_ appcontext.AppCon if invalidFieldChange { return apperror.NewConflictError(serviceItemData.oldServiceItem.ID, "- one or more fields is not allowed to be updated when the shuttle service item has an approved status.") + } else { + return nil } - return apperror.NewConflictError(serviceItemData.oldServiceItem.ID, - "- unknown field or fields attempting to be updated.") } else { return apperror.NewConflictError(serviceItemData.oldServiceItem.ID, "- this shuttle service item cannot be updated because the status is not in an editable state.") @@ -503,10 +506,10 @@ func (v *updateMTOServiceItemData) checkSITEntryDateAndFADD(_ appcontext.AppCont } // checkSITDepartureDate checks that the SITDepartureDate: -// - is not later than the authorized end date +// - is not earlier than the SIT entry date func (v *updateMTOServiceItemData) checkSITDepartureDate(_ appcontext.AppContext) error { - if v.updatedServiceItem.SITDepartureDate == nil || v.updatedServiceItem.SITDepartureDate == v.oldServiceItem.SITDepartureDate { - return nil // the SITDepartureDate isn't being updated, so we're fine here + if (v.updatedServiceItem.SITDepartureDate == nil || v.updatedServiceItem.SITDepartureDate == v.oldServiceItem.SITDepartureDate) && (v.updatedServiceItem.SITEntryDate == nil || v.updatedServiceItem.SITEntryDate == v.oldServiceItem.SITEntryDate) { + return nil // the SITDepartureDate or SITEntryDate isn't being updated, so we're fine here } if v.updatedServiceItem.SITDepartureDate != nil { @@ -523,9 +526,9 @@ func (v *updateMTOServiceItemData) checkSITDepartureDate(_ appcontext.AppContext if v.updatedServiceItem.SITEntryDate != nil { SITEntryDate = v.updatedServiceItem.SITEntryDate } - // Check that departure date is not before the current entry date - if v.updatedServiceItem.SITDepartureDate.Before(*SITEntryDate) { - v.verrs.Add("SITDepartureDate", "SIT departure date cannot be set before the SIT entry date.") + // Check that departure date is not before or equal to the current entry date + if !v.updatedServiceItem.SITDepartureDate.After(*SITEntryDate) { + v.verrs.Add("SITDepartureDate", "SIT departure date cannot be set before or equal to the SIT entry date.") } } return nil @@ -686,6 +689,9 @@ func (v *updateMTOServiceItemData) setNewMTOServiceItem() *models.MTOServiceItem newMTOServiceItem.ActualWeight = services.SetOptionalPoundField( v.updatedServiceItem.ActualWeight, newMTOServiceItem.ActualWeight) + newMTOServiceItem.PricingEstimate = services.SetNoNilOptionalCentField( + v.updatedServiceItem.PricingEstimate, newMTOServiceItem.PricingEstimate) + // Set POD Location if v.updatedServiceItem.PODLocationID != nil { newMTOServiceItem.PODLocationID = v.updatedServiceItem.PODLocationID diff --git a/pkg/services/mto_service_item/mto_service_item_validators_test.go b/pkg/services/mto_service_item/mto_service_item_validators_test.go index 888c094becd..de41dc6bc9d 100644 --- a/pkg/services/mto_service_item/mto_service_item_validators_test.go +++ b/pkg/services/mto_service_item/mto_service_item_validators_test.go @@ -793,8 +793,9 @@ func (suite *MTOServiceItemServiceSuite) TestUpdateMTOServiceItemData() { suite.Run("SITDepartureDate - Does not error or update shipment auth end date when set after the authorized end date", func() { // Under test: checkSITDepartureDate checks that - // the SITDepartureDate is not later than the authorized end date - // Set up: Create an old and new DOPSIT and DDDSIT, with a date later than the + // the SITDepartureDate can be later than the authorized end date + // and that the authorized end dates is not updated when that occurs + // Set up: Create an old and new DOPSIT and DDDSIT, with a departure date later than the // shipment and try to update. // Expected outcome: No ERROR if departure date comes after the end date. // Shipment auth end date does not change @@ -827,7 +828,7 @@ func (suite *MTOServiceItemServiceSuite) TestUpdateMTOServiceItemData() { }, { Model: models.MTOServiceItem{ - SITEntryDate: &later, + SITEntryDate: &before, }, }, }, nil) @@ -842,23 +843,23 @@ func (suite *MTOServiceItemServiceSuite) TestUpdateMTOServiceItemData() { suite.NoError(err) suite.False(serviceItemData.verrs.HasAny()) - // Double check the shipment and ensure that the SITDepartureDate is in fact after the authorized end date + // Double check the shipment and ensure that the SITDepartureDate is after the authorized end date and does not alter the authorized end date var postUpdateShipment models.MTOShipment err = suite.DB().Find(&postUpdateShipment, mtoShipment.ID) suite.NoError(err) if tc.reServiceCode == models.ReServiceCodeDOPSIT { suite.True(mtoShipment.OriginSITAuthEndDate.Truncate(24 * time.Hour).Equal(postUpdateShipment.OriginSITAuthEndDate.Truncate(24 * time.Hour))) - suite.True(newSITServiceItem.SITEntryDate.Truncate(24 * time.Hour).After(postUpdateShipment.OriginSITAuthEndDate.Truncate(24 * time.Hour))) + suite.True(newSITServiceItem.SITDepartureDate.Truncate(24 * time.Hour).After(postUpdateShipment.OriginSITAuthEndDate.Truncate(24 * time.Hour))) } if tc.reServiceCode == models.ReServiceCodeDDDSIT { suite.True(mtoShipment.DestinationSITAuthEndDate.Truncate(24 * time.Hour).Equal(postUpdateShipment.DestinationSITAuthEndDate.Truncate(24 * time.Hour))) - suite.True(newSITServiceItem.SITEntryDate.Truncate(24 * time.Hour).After(postUpdateShipment.DestinationSITAuthEndDate.Truncate(24 * time.Hour))) + suite.True(newSITServiceItem.SITDepartureDate.Truncate(24 * time.Hour).After(postUpdateShipment.DestinationSITAuthEndDate.Truncate(24 * time.Hour))) } } }) - suite.Run("SITDepartureDate - errors when set before the SIT entry date", func() { + suite.Run("SITDepartureDate - errors when set before or equal the SIT entry date", func() { mtoShipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ { Model: models.MTOShipment{OriginSITAuthEndDate: &now, @@ -903,7 +904,57 @@ func (suite *MTOServiceItemServiceSuite) TestUpdateMTOServiceItemData() { suite.NoError(err) // Just verrs suite.True(serviceItemData.verrs.HasAny()) suite.Contains(serviceItemData.verrs.Keys(), "SITDepartureDate") - suite.Contains(serviceItemData.verrs.Get("SITDepartureDate"), "SIT departure date cannot be set before the SIT entry date.") + suite.Contains(serviceItemData.verrs.Get("SITDepartureDate"), "SIT departure date cannot be set before or equal to the SIT entry date.") + } + + }) + + suite.Run("SITDepartureDate - errors when set equal to the SIT entry date", func() { + mtoShipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{OriginSITAuthEndDate: &now, + DestinationSITAuthEndDate: &now}, + }, + }, nil) + testCases := []struct { + reServiceCode models.ReServiceCode + }{ + { + reServiceCode: models.ReServiceCodeDOPSIT, + }, + { + reServiceCode: models.ReServiceCodeDDDSIT, + }, + } + for _, tc := range testCases { + oldSITServiceItem := factory.BuildMTOServiceItem(nil, []factory.Customization{ + { + Model: models.ReService{ + Code: tc.reServiceCode, + }, + }, + { + Model: mtoShipment, + LinkOnly: true, + }, + { + Model: models.MTOServiceItem{ + SITEntryDate: &now, + }, + }, + }, nil) + newSITServiceItem := oldSITServiceItem + newSITServiceItem.SITDepartureDate = &now + serviceItemData := updateMTOServiceItemData{ + updatedServiceItem: newSITServiceItem, + oldServiceItem: oldSITServiceItem, + verrs: validate.NewErrors(), + } + err := serviceItemData.checkSITDepartureDate(suite.AppContextForTest()) + suite.NoError(err) // Just verrs + suite.True(serviceItemData.verrs.HasAny()) + suite.Contains(serviceItemData.verrs.Keys(), "SITDepartureDate") + suite.Contains(serviceItemData.verrs.Get("SITDepartureDate"), "SIT departure date cannot be set before or equal to the SIT entry date.") } }) diff --git a/pkg/services/mto_shipment/shipment_approver_test.go b/pkg/services/mto_shipment/shipment_approver_test.go index 492f0862bad..e84fe0fb282 100644 --- a/pkg/services/mto_shipment/shipment_approver_test.go +++ b/pkg/services/mto_shipment/shipment_approver_test.go @@ -1290,7 +1290,7 @@ func (suite *MTOShipmentServiceSuite) TestApproveShipment() { } expectedReServiceNames := []string{ "International UB price", - "International POE Fuel Surcharge", + "International POE fuel surcharge", "International UB pack", "International UB unpack", } @@ -1358,7 +1358,7 @@ func (suite *MTOShipmentServiceSuite) TestApproveShipment() { } expectedReServiceNames := []string{ "International UB price", - "International POD Fuel Surcharge", + "International POD fuel surcharge", "International UB pack", "International UB unpack", } diff --git a/pkg/services/office_user/office_user_fetcher.go b/pkg/services/office_user/office_user_fetcher.go index 99a6448f1fe..9708f4c137b 100644 --- a/pkg/services/office_user/office_user_fetcher.go +++ b/pkg/services/office_user/office_user_fetcher.go @@ -151,7 +151,8 @@ func (o *officeUserFetcherPop) FetchOfficeUsersWithWorkloadByRoleAndOffice(appCt WHERE roles.role_type = $1 AND transportation_offices.id = $2 AND office_users.active = TRUE - GROUP BY office_users.id, office_users.first_name, office_users.last_name` + GROUP BY office_users.id, office_users.first_name, office_users.last_name + ORDER BY office_users.last_name ASC, office_users.first_name ASC` err := appCtx.DB().RawQuery(query, role, officeID).All(&officeUsers) if err != nil { diff --git a/pkg/services/orchestrators/shipment/shipment_updater.go b/pkg/services/orchestrators/shipment/shipment_updater.go index 70b0406a842..d8d82af8e90 100644 --- a/pkg/services/orchestrators/shipment/shipment_updater.go +++ b/pkg/services/orchestrators/shipment/shipment_updater.go @@ -6,6 +6,7 @@ import ( "github.com/transcom/mymove/pkg/appcontext" "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/unit" ) // shipmentUpdater is the concrete struct implementing the services.ShipmentUpdater interface @@ -15,16 +16,18 @@ type shipmentUpdater struct { ppmShipmentUpdater services.PPMShipmentUpdater boatShipmentUpdater services.BoatShipmentUpdater mobileHomeShipmentUpdater services.MobileHomeShipmentUpdater + mtoServiceItemCreator services.MTOServiceItemCreator } // NewShipmentUpdater creates a new shipmentUpdater struct with the basic checks and service dependencies. -func NewShipmentUpdater(mtoShipmentUpdater services.MTOShipmentUpdater, ppmShipmentUpdater services.PPMShipmentUpdater, boatShipmentUpdater services.BoatShipmentUpdater, mobileHomeShipmentUpdater services.MobileHomeShipmentUpdater) services.ShipmentUpdater { +func NewShipmentUpdater(mtoShipmentUpdater services.MTOShipmentUpdater, ppmShipmentUpdater services.PPMShipmentUpdater, boatShipmentUpdater services.BoatShipmentUpdater, mobileHomeShipmentUpdater services.MobileHomeShipmentUpdater, mtoServiceItemCreator services.MTOServiceItemCreator) services.ShipmentUpdater { return &shipmentUpdater{ checks: basicShipmentChecks(), mtoShipmentUpdater: mtoShipmentUpdater, ppmShipmentUpdater: ppmShipmentUpdater, boatShipmentUpdater: boatShipmentUpdater, mobileHomeShipmentUpdater: mobileHomeShipmentUpdater, + mtoServiceItemCreator: mtoServiceItemCreator, } } @@ -43,6 +46,20 @@ func (s *shipmentUpdater) UpdateShipment(appCtx appcontext.AppContext, shipment return err } + if mtoShipment != nil && (mtoShipment.ShipmentType != models.MTOShipmentTypePPM) && (shipment.PrimeEstimatedWeight != nil || mtoShipment.PrimeEstimatedWeight != nil) && mtoShipment.Status == models.MTOShipmentStatusApproved { + mtoShipment, err = AddPricingEstimatesToMTOServiceItems(appCtx, *s, mtoShipment, shipment) + if err != nil { + return err + } + } + + if mtoShipment.MTOServiceItems != nil { + _, mtoErr := appCtx.DB().ValidateAndUpdate(&mtoShipment.MTOServiceItems) + if mtoErr != nil { + return mtoErr + } + } + isBoatShipment := shipment.ShipmentType == models.MTOShipmentTypeBoatHaulAway || shipment.ShipmentType == models.MTOShipmentTypeBoatTowAway if shipment.ShipmentType == models.MTOShipmentTypePPM { @@ -122,3 +139,30 @@ func (s *shipmentUpdater) UpdateShipment(appCtx appcontext.AppContext, shipment return mtoShipment, nil } + +func AddPricingEstimatesToMTOServiceItems(appCtx appcontext.AppContext, shipmentUpdater shipmentUpdater, mtoShipment *models.MTOShipment, shipmentDelta *models.MTOShipment) (*models.MTOShipment, error) { + mtoShipmentCopy := mtoShipment + + for index, serviceItem := range mtoShipmentCopy.MTOServiceItems { + var estimatedWeightToUse unit.Pound + if shipmentDelta.PrimeEstimatedWeight != nil { + estimatedWeightToUse = *shipmentDelta.PrimeEstimatedWeight + } else { + estimatedWeightToUse = *mtoShipmentCopy.PrimeEstimatedWeight + } + + serviceItemEstimatedPrice, err := shipmentUpdater.mtoServiceItemCreator.FindEstimatedPrice(appCtx, &serviceItem, *mtoShipment) + + // store actual captured weight + mtoShipmentCopy.MTOServiceItems[index].EstimatedWeight = &estimatedWeightToUse + mtoShipmentCopy.PrimeEstimatedWeight = &estimatedWeightToUse + + if serviceItemEstimatedPrice != 0 && err == nil { + mtoShipmentCopy.MTOServiceItems[index].PricingEstimate = &serviceItemEstimatedPrice + } + if err != nil { + return mtoShipmentCopy, err + } + } + return mtoShipmentCopy, nil +} diff --git a/pkg/services/orchestrators/shipment/shipment_updater_test.go b/pkg/services/orchestrators/shipment/shipment_updater_test.go index 3e46c05d001..3b9fec24cac 100644 --- a/pkg/services/orchestrators/shipment/shipment_updater_test.go +++ b/pkg/services/orchestrators/shipment/shipment_updater_test.go @@ -2,6 +2,7 @@ package shipment import ( "fmt" + "time" "github.com/gofrs/uuid" "github.com/stretchr/testify/mock" @@ -11,8 +12,14 @@ import ( "github.com/transcom/mymove/pkg/etag" "github.com/transcom/mymove/pkg/factory" "github.com/transcom/mymove/pkg/models" + routemocks "github.com/transcom/mymove/pkg/route/mocks" "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/services/ghcrateengine" "github.com/transcom/mymove/pkg/services/mocks" + moveservices "github.com/transcom/mymove/pkg/services/move" + mtoserviceitem "github.com/transcom/mymove/pkg/services/mto_service_item" + "github.com/transcom/mymove/pkg/services/query" + "github.com/transcom/mymove/pkg/testdatagen" "github.com/transcom/mymove/pkg/unit" ) @@ -28,6 +35,7 @@ func (suite *ShipmentSuite) TestUpdateShipment() { type subtestDataObjects struct { mockMTOShipmentUpdater *mocks.MTOShipmentUpdater + mockMtoServiceItemCreator *mocks.MTOServiceItemCreator mockPPMShipmentUpdater *mocks.PPMShipmentUpdater mockBoatShipmentUpdater *mocks.BoatShipmentUpdater mockMobileHomeShipmentUpdater *mocks.MobileHomeShipmentUpdater @@ -36,10 +44,18 @@ func (suite *ShipmentSuite) TestUpdateShipment() { fakeError error } + planner := &routemocks.Planner{} + moveRouter := moveservices.NewMoveRouter() + builder := query.NewQueryBuilder() + mtoServiceItemCreator := mtoserviceitem.NewMTOServiceItemCreator(planner, builder, moveRouter, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticPackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticShorthaulPricer(), ghcrateengine.NewDomesticOriginPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) + makeSubtestData := func(returnErrorForMTOShipment bool, returnErrorForPPMShipment bool, returnErrorForBoatShipment bool, returnErrorForMobileHomeShipment bool) (subtestData subtestDataObjects) { mockMTOShipmentUpdater := mocks.MTOShipmentUpdater{} subtestData.mockMTOShipmentUpdater = &mockMTOShipmentUpdater + mockMtoServiceItemCreator := mocks.MTOServiceItemCreator{} + subtestData.mockMtoServiceItemCreator = &mockMtoServiceItemCreator + mockPPMShipmentUpdater := mocks.PPMShipmentUpdater{} subtestData.mockPPMShipmentUpdater = &mockPPMShipmentUpdater @@ -49,9 +65,8 @@ func (suite *ShipmentSuite) TestUpdateShipment() { mockMobileHomeShipmentUpdater := mocks.MobileHomeShipmentUpdater{} subtestData.mockMobileHomeShipmentUpdater = &mockMobileHomeShipmentUpdater - subtestData.shipmentUpdaterOrchestrator = NewShipmentUpdater(subtestData.mockMTOShipmentUpdater, subtestData.mockPPMShipmentUpdater, subtestData.mockBoatShipmentUpdater, subtestData.mockMobileHomeShipmentUpdater) - - subtestData.shipmentUpdaterOrchestrator = NewShipmentUpdater(subtestData.mockMTOShipmentUpdater, subtestData.mockPPMShipmentUpdater, subtestData.mockBoatShipmentUpdater, subtestData.mockMobileHomeShipmentUpdater) + subtestData.shipmentUpdaterOrchestrator = NewShipmentUpdater(subtestData.mockMTOShipmentUpdater, subtestData.mockPPMShipmentUpdater, subtestData.mockBoatShipmentUpdater, subtestData.mockMobileHomeShipmentUpdater, mtoServiceItemCreator) + subtestData.shipmentUpdaterOrchestrator = NewShipmentUpdater(subtestData.mockMTOShipmentUpdater, subtestData.mockPPMShipmentUpdater, subtestData.mockBoatShipmentUpdater, subtestData.mockMobileHomeShipmentUpdater, mtoServiceItemCreator) if returnErrorForMTOShipment { subtestData.fakeError = apperror.NewInvalidInputError(uuid.Nil, nil, nil, "Pickup date missing") @@ -177,6 +192,26 @@ func (suite *ShipmentSuite) TestUpdateShipment() { return subtestData } + makeServiceItemSubtestData := func() (subtestData subtestDataObjects) { + mockMTOShipmentUpdater := mocks.MTOShipmentUpdater{} + subtestData.mockMTOShipmentUpdater = &mockMTOShipmentUpdater + + mockMtoServiceItemCreator := mocks.MTOServiceItemCreator{} + subtestData.mockMtoServiceItemCreator = &mockMtoServiceItemCreator + + mockPPMShipmentUpdater := mocks.PPMShipmentUpdater{} + subtestData.mockPPMShipmentUpdater = &mockPPMShipmentUpdater + + mockBoatShipmentUpdater := mocks.BoatShipmentUpdater{} + subtestData.mockBoatShipmentUpdater = &mockBoatShipmentUpdater + + mockMobileHomeShipmentUpdater := mocks.MobileHomeShipmentUpdater{} + subtestData.mockMobileHomeShipmentUpdater = &mockMobileHomeShipmentUpdater + + subtestData.shipmentUpdaterOrchestrator = NewShipmentUpdater(subtestData.mockMTOShipmentUpdater, subtestData.mockPPMShipmentUpdater, subtestData.mockBoatShipmentUpdater, subtestData.mockMobileHomeShipmentUpdater, subtestData.mockMtoServiceItemCreator) + + return subtestData + } suite.Run("Returns an InvalidInputError if there is an error with the shipment info that was input", func() { appCtx := suite.AppContextForTest() @@ -465,4 +500,133 @@ func (suite *ShipmentSuite) TestUpdateShipment() { mock.AnythingOfType("uuid.UUID"), ) }) + + suite.Run("Updating weight will update the estimated price of service items", func() { + appCtx := suite.AppContextForTest() + + subtestData := makeServiceItemSubtestData() + + estimatedWeight := unit.Pound(2000) + pickupAddress := factory.BuildAddress(suite.DB(), nil, []factory.Trait{factory.GetTraitAddress2}) + deliveryAddress := factory.BuildAddress(suite.DB(), nil, []factory.Trait{factory.GetTraitAddress3}) + + shipment := factory.BuildMTOShipment(appCtx.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + ID: uuid.Must(uuid.FromString("a5e95c1d-97c3-4f79-8097-c12dd2557ac7")), + Status: models.MTOShipmentStatusApproved, + ShipmentType: models.MTOShipmentTypeHHG, + PrimeEstimatedWeight: &estimatedWeight, + }, + }, + { + Model: pickupAddress, + LinkOnly: true, + Type: &factory.Addresses.PickupAddress, + }, + { + Model: deliveryAddress, + LinkOnly: true, + Type: &factory.Addresses.DeliveryAddress, + }, + }, nil) + + reServiceCodeFSC := factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeFSC) + + startDate := time.Now().AddDate(-1, 0, 0) + endDate := startDate.AddDate(1, 1, 1) + reason := "lorem ipsum" + + testdatagen.FetchOrMakeReContract(suite.DB(), testdatagen.Assertions{}) + testdatagen.FetchOrMakeReContractYear(suite.DB(), + testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + Name: "Test Contract Year", + EscalationCompounded: 1.125, + StartDate: startDate, + EndDate: endDate, + }, + }) + + testdatagen.FetchOrMakeGHCDieselFuelPrice(suite.DB(), testdatagen.Assertions{ + GHCDieselFuelPrice: models.GHCDieselFuelPrice{ + FuelPriceInMillicents: unit.Millicents(281400), + PublicationDate: time.Date(2020, time.March, 9, 0, 0, 0, 0, time.UTC), + EffectiveDate: time.Date(2020, time.March, 10, 0, 0, 0, 0, time.UTC), + EndDate: time.Date(2025, time.March, 17, 0, 0, 0, 0, time.UTC), + }, + }) + + actualPickupAddress := factory.BuildAddress(suite.DB(), nil, []factory.Trait{factory.GetTraitAddress2}) + requestedPickupDate := time.Date(2020, time.October, 24, 0, 0, 0, 0, time.UTC) + + serviceItemFSC := models.MTOServiceItem{ + MoveTaskOrder: shipment.MoveTaskOrder, + MoveTaskOrderID: shipment.MoveTaskOrderID, + MTOShipment: shipment, + MTOShipmentID: &shipment.ID, + ReService: reServiceCodeFSC, + Reason: &reason, + SITOriginHHGActualAddress: &actualPickupAddress, + Status: models.MTOServiceItemStatusSubmitted, + } + + shipment.MTOServiceItems = append(shipment.MTOServiceItems, serviceItemFSC) + suite.MustSave(&shipment) + + eTag := etag.GenerateEtag(shipment.UpdatedAt) + + subtestData.mockMtoServiceItemCreator.On("ZipTransitDistance", + mock.AnythingOfType("*appcontext.appContext"), + mock.Anything, + mock.Anything, + false, + false, + ).Return(800, nil) + + returnCents := unit.Cents(123) + + subtestData.mockMtoServiceItemCreator.On("FindEstimatedPrice", + mock.AnythingOfType("*appcontext.appContext"), + mock.Anything, + mock.Anything, + mock.Anything, + ).Return(returnCents, nil) + + subtestData.mockMTOShipmentUpdater. + On( + updateMTOShipmentMethodName, + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("*models.MTOShipment"), + mock.AnythingOfType("string"), + mock.AnythingOfType("string")). + Return( + &models.MTOShipment{ + ID: uuid.Must(uuid.FromString("a5e95c1d-97c3-4f79-8097-c12dd2557ac7")), + Status: models.MTOShipmentStatusApproved, + ShipmentType: models.MTOShipmentTypeHHG, + PrimeEstimatedWeight: &estimatedWeight, + RequestedPickupDate: &requestedPickupDate, + MTOServiceItems: models.MTOServiceItems{serviceItemFSC}, + PickupAddress: &pickupAddress, + DestinationAddress: &deliveryAddress, + }, nil) + + // Need to start a transaction so we can assert the call with the correct appCtx + err := appCtx.NewTransaction(func(txAppCtx appcontext.AppContext) error { + mtoShipment, err := subtestData.shipmentUpdaterOrchestrator.UpdateShipment(txAppCtx, &shipment, eTag, "test") + + suite.NoError(err) + suite.NotNil(mtoShipment) + + expectedPrice := unit.Cents(123) + expectedWeight := unit.Pound(2000) + suite.Equal(expectedWeight, *mtoShipment.MTOServiceItems[0].EstimatedWeight) + suite.Equal(expectedPrice, *mtoShipment.MTOServiceItems[0].PricingEstimate) + + return nil + }) + + suite.NoError(err) // just making golangci-lint happy + }) } diff --git a/pkg/services/order.go b/pkg/services/order.go index c1bd25d1b84..217f3e069f2 100644 --- a/pkg/services/order.go +++ b/pkg/services/order.go @@ -20,6 +20,7 @@ import ( type OrderFetcher interface { FetchOrder(appCtx appcontext.AppContext, orderID uuid.UUID) (*models.Order, error) ListOrders(appCtx appcontext.AppContext, officeUserID uuid.UUID, role roles.RoleType, params *ListOrderParams) ([]models.Move, int, error) + ListDestinationRequestsOrders(appCtx appcontext.AppContext, officeUserID uuid.UUID, role roles.RoleType, params *ListOrderParams) ([]models.Move, int, error) ListAllOrderLocations(appCtx appcontext.AppContext, officeUserID uuid.UUID, params *ListOrderParams) ([]models.Move, error) } diff --git a/pkg/services/order/order_fetcher.go b/pkg/services/order/order_fetcher.go index 923a4dfe82b..5761af41a01 100644 --- a/pkg/services/order/order_fetcher.go +++ b/pkg/services/order/order_fetcher.go @@ -2,6 +2,7 @@ package order import ( "database/sql" + "encoding/json" "fmt" "regexp" "strings" @@ -9,6 +10,8 @@ import ( "github.com/gobuffalo/pop/v6" "github.com/gofrs/uuid" + "github.com/jinzhu/copier" + "github.com/lib/pq" "go.uber.org/zap" "github.com/transcom/mymove/pkg/appcontext" @@ -122,8 +125,9 @@ func (f orderFetcher) ListOrders(appCtx appcontext.AppContext, officeUserID uuid tooAssignedUserQuery := tooAssignedUserFilter(params.TOOAssignedUser) sortOrderQuery := sortOrder(params.Sort, params.Order, ppmCloseoutGblocs) counselingQuery := counselingOfficeFilter(params.CounselingOffice) + tooDestinationRequestsQuery := tooQueueOriginRequestsFilter(role) // Adding to an array so we can iterate over them and apply the filters after the query structure is set below - options := [20]QueryOption{branchQuery, locatorQuery, dodIDQuery, emplidQuery, customerNameQuery, originDutyLocationQuery, destinationDutyLocationQuery, moveStatusQuery, gblocQuery, submittedAtQuery, appearedInTOOAtQuery, requestedMoveDateQuery, ppmTypeQuery, closeoutInitiatedQuery, closeoutLocationQuery, ppmStatusQuery, sortOrderQuery, scAssignedUserQuery, tooAssignedUserQuery, counselingQuery} + options := [21]QueryOption{branchQuery, locatorQuery, dodIDQuery, emplidQuery, customerNameQuery, originDutyLocationQuery, destinationDutyLocationQuery, moveStatusQuery, gblocQuery, submittedAtQuery, appearedInTOOAtQuery, requestedMoveDateQuery, ppmTypeQuery, closeoutInitiatedQuery, closeoutLocationQuery, ppmStatusQuery, sortOrderQuery, scAssignedUserQuery, tooAssignedUserQuery, counselingQuery, tooDestinationRequestsQuery} var query *pop.Query if ppmCloseoutGblocs { @@ -307,7 +311,118 @@ func (f orderFetcher) ListOrders(appCtx appcontext.AppContext, officeUserID uuid return moves, count, nil } -// TODO: Update query to select distinct duty locations +// this is a custom/temporary struct used in the below service object to get destination queue moves +type MoveWithCount struct { + models.Move + OrdersRaw json.RawMessage `json:"orders" db:"orders"` + Orders *models.Order `json:"-"` + MTOShipmentsRaw json.RawMessage `json:"mto_shipments" db:"mto_shipments"` + MTOShipments *models.MTOShipments `json:"-"` + CounselingOfficeRaw json.RawMessage `json:"counseling_transportation_office" db:"counseling_transportation_office"` + CounselingOffice *models.TransportationOffice `json:"-"` + TOOAssignedRaw json.RawMessage `json:"too_assigned" db:"too_assigned"` + TOOAssignedUser *models.OfficeUser `json:"-"` + TotalCount int64 `json:"total_count" db:"total_count"` +} + +type JSONB []byte + +func (j *JSONB) UnmarshalJSON(data []byte) error { + *j = data + return nil +} + +func (f orderFetcher) ListDestinationRequestsOrders(appCtx appcontext.AppContext, officeUserID uuid.UUID, role roles.RoleType, params *services.ListOrderParams) ([]models.Move, int, error) { + var moves []models.Move + var movesWithCount []MoveWithCount + + // getting the office user's GBLOC + gblocFetcher := officeuser.NewOfficeUserGblocFetcher() + officeUserGbloc, gblocErr := gblocFetcher.FetchGblocForOfficeUser(appCtx, officeUserID) + if gblocErr != nil { + return []models.Move{}, 0, gblocErr + } + + // calling the database function with all passed in parameters + err := appCtx.DB().RawQuery("SELECT * FROM get_destination_queue($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16)", + officeUserGbloc, + params.CustomerName, + params.Edipi, + params.Emplid, + pq.Array(params.Status), + params.Locator, + params.RequestedMoveDate, + params.AppearedInTOOAt, + params.Branch, + strings.Join(params.OriginDutyLocation, " "), + params.CounselingOffice, + params.TOOAssignedUser, + params.Page, + params.PerPage, + params.Sort, + params.Order). + All(&movesWithCount) + + if err != nil { + return []models.Move{}, 0, err + } + + // each row is sent back with the total count from the db func, so we will take the value from the first one + var count int64 + if len(movesWithCount) > 0 { + count = movesWithCount[0].TotalCount + } else { + count = 0 + } + + // we have to manually loop through each move and populate the nested objects that the queue uses/needs + for i := range movesWithCount { + // populating Move.Orders struct + var order models.Order + if err := json.Unmarshal(movesWithCount[i].OrdersRaw, &order); err != nil { + return nil, 0, fmt.Errorf("error unmarshaling orders JSON: %w", err) + } + movesWithCount[i].OrdersRaw = nil + movesWithCount[i].Orders = &order + + // populating Move.MTOShipments array + var shipments models.MTOShipments + if err := json.Unmarshal(movesWithCount[i].MTOShipmentsRaw, &shipments); err != nil { + return nil, 0, fmt.Errorf("error unmarshaling shipments JSON: %w", err) + } + movesWithCount[i].MTOShipmentsRaw = nil + movesWithCount[i].MTOShipments = &shipments + + // populating Moves.CounselingOffice struct + var counselingTransportationOffice models.TransportationOffice + if err := json.Unmarshal(movesWithCount[i].CounselingOfficeRaw, &counselingTransportationOffice); err != nil { + return nil, 0, fmt.Errorf("error unmarshaling counseling_transportation_office JSON: %w", err) + } + movesWithCount[i].CounselingOfficeRaw = nil + movesWithCount[i].CounselingOffice = &counselingTransportationOffice + + // populating Moves.TOOAssigned struct + var tooAssigned models.OfficeUser + if err := json.Unmarshal(movesWithCount[i].TOOAssignedRaw, &tooAssigned); err != nil { + return nil, 0, fmt.Errorf("error unmarshaling too_assigned JSON: %w", err) + } + movesWithCount[i].TOOAssignedRaw = nil + movesWithCount[i].TOOAssignedUser = &tooAssigned + } + + // the handler consumes a Move object and NOT the MoveWithCount struct used in this func + // so we have to copy our custom struct into the Move struct + for _, moveWithCount := range movesWithCount { + var move models.Move + if err := copier.Copy(&move, &moveWithCount); err != nil { + return nil, 0, fmt.Errorf("error copying movesWithCount into Moves struct: %w", err) + } + moves = append(moves, move) + } + + return moves, int(count), nil +} + func (f orderFetcher) ListAllOrderLocations(appCtx appcontext.AppContext, officeUserID uuid.UUID, params *services.ListOrderParams) ([]models.Move, error) { var moves []models.Move var err error @@ -781,3 +896,95 @@ func sortOrder(sort *string, order *string, ppmCloseoutGblocs bool) QueryOption } } } + +// We want to filter out any moves that have ONLY destination type requests to them, such as destination SIT, shuttle, out of the +// task order queue. If the moves have origin SIT, excess weight risks, or sit extensions with origin SIT service items, they +// should still appear in the task order queue, which is what this query looks for +func tooQueueOriginRequestsFilter(role roles.RoleType) QueryOption { + return func(query *pop.Query) { + if role == roles.RoleTypeTOO { + baseQuery := ` + -- check for moves with destination requests and NOT origin requests, then return the inverse for the TOO queue with the NOT wrapped around the query + NOT ( + -- check for moves with destination requests + ( + -- moves with submitted destination SIT or shuttle submitted service items + EXISTS ( + SELECT 1 + FROM mto_service_items msi + JOIN re_services rs ON msi.re_service_id = rs.id + WHERE msi.mto_shipment_id = mto_shipments.id + AND msi.status = 'SUBMITTED' + AND rs.code IN ('DDFSIT', 'DDASIT', 'DDDSIT', 'DDSHUT', 'DDSFSC', + 'IDFSIT', 'IDASIT', 'IDDSIT', 'IDSHUT', 'IDSFSC') + ) + -- requested shipment address update + OR EXISTS ( + SELECT 1 + FROM shipment_address_updates sau + WHERE sau.shipment_id = mto_shipments.id + AND sau.status = 'REQUESTED' + ) + -- Moves with SIT extensions and ONLY destination SIT service items we filter out of TOO queue + OR ( + EXISTS ( + SELECT 1 + FROM sit_extensions se + JOIN mto_service_items msi ON se.mto_shipment_id = msi.mto_shipment_id + JOIN re_services rs ON msi.re_service_id = rs.id + WHERE se.mto_shipment_id = mto_shipments.id + AND se.status = 'PENDING' + AND rs.code IN ('DDFSIT', 'DDASIT', 'DDDSIT', 'DDSHUT', 'DDSFSC', + 'IDFSIT', 'IDASIT', 'IDDSIT', 'IDSHUT', 'IDSFSC') + ) + -- make sure there are NO origin SIT service items (otherwise, it should be in both queues) + AND NOT EXISTS ( + SELECT 1 + FROM mto_service_items msi + JOIN re_services rs ON msi.re_service_id = rs.id + WHERE msi.mto_shipment_id = mto_shipments.id + AND msi.status = 'SUBMITTED' + AND rs.code IN ('ICRT', 'IUBPK', 'IOFSIT', 'IOASIT', 'IOPSIT', 'IOSHUT', + 'IHUPK', 'IUCRT', 'DCRT', 'MS', 'CS', 'DOFSIT', 'DOASIT', + 'DOPSIT', 'DOSFSC', 'IOSFSC', 'DUPK', 'DUCRT', 'DOSHUT', + 'FSC', 'DMHF', 'DBTF', 'DBHF', 'IBTF', 'IBHF', 'DCRTSA', + 'DLH', 'DOP', 'DPK', 'DSH', 'DNPK', 'INPK', 'UBP', + 'ISLH', 'POEFSC', 'PODFSC', 'IHPK') + ) + ) + ) + -- check for moves with origin requests or conditions where move should appear in TOO queue + AND NOT ( + -- keep moves in the TOO queue with origin submitted service items + EXISTS ( + SELECT 1 + FROM mto_service_items msi + JOIN re_services rs ON msi.re_service_id = rs.id + WHERE msi.mto_shipment_id = mto_shipments.id + AND msi.status = 'SUBMITTED' + AND rs.code IN ('ICRT', 'IUBPK', 'IOFSIT', 'IOASIT', 'IOPSIT', 'IOSHUT', + 'IHUPK', 'IUCRT', 'DCRT', 'MS', 'CS', 'DOFSIT', 'DOASIT', + 'DOPSIT', 'DOSFSC', 'IOSFSC', 'DUPK', 'DUCRT', 'DOSHUT', + 'FSC', 'DMHF', 'DBTF', 'DBHF', 'IBTF', 'IBHF', 'DCRTSA', + 'DLH', 'DOP', 'DPK', 'DSH', 'DNPK', 'INPK', 'UBP', + 'ISLH', 'POEFSC', 'PODFSC', 'IHPK') + ) + -- keep moves in the TOO queue if they have an unacknowledged excess weight risk + OR ( + ( + moves.excess_weight_qualified_at IS NOT NULL + AND moves.excess_weight_acknowledged_at IS NULL + ) + OR ( + moves.excess_unaccompanied_baggage_weight_qualified_at IS NOT NULL + AND moves.excess_unaccompanied_baggage_weight_acknowledged_at IS NULL + ) + ) + ) + ) + + ` + query.Where(baseQuery) + } + } +} diff --git a/pkg/services/order/order_fetcher_test.go b/pkg/services/order/order_fetcher_test.go index 57e85401246..9742e038187 100644 --- a/pkg/services/order/order_fetcher_test.go +++ b/pkg/services/order/order_fetcher_test.go @@ -5,6 +5,7 @@ import ( "time" "github.com/gofrs/uuid" + "github.com/stretchr/testify/mock" "github.com/transcom/mymove/pkg/auth" "github.com/transcom/mymove/pkg/factory" @@ -12,6 +13,7 @@ import ( "github.com/transcom/mymove/pkg/models/roles" "github.com/transcom/mymove/pkg/services" "github.com/transcom/mymove/pkg/services/entitlements" + "github.com/transcom/mymove/pkg/services/mocks" moveservice "github.com/transcom/mymove/pkg/services/move" officeuserservice "github.com/transcom/mymove/pkg/services/office_user" "github.com/transcom/mymove/pkg/testdatagen" @@ -580,74 +582,59 @@ func (suite *OrderServiceSuite) TestListOrders() { suite.Equal(1, len(moves)) suite.Equal(createdPPM.Shipment.MoveTaskOrder.Locator, moves[0].Locator) }) -} -func (suite *OrderServiceSuite) TestListOrderWithAssignedUserSingle() { - // Under test: ListOrders - // Set up: Make a move, assign one to an SC office user - // Expected outcome: Only the one move with the assigned user should be returned - assignedOfficeUserUpdater := moveservice.NewAssignedOfficeUserUpdater(moveservice.NewMoveFetcher()) - scUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeServicesCounselor}) - var orderFetcherTest orderFetcher - session := auth.Session{ - ApplicationName: auth.OfficeApp, - Roles: scUser.User.Roles, - OfficeUserID: scUser.ID, - IDToken: "fake_token", - AccessToken: "fakeAccessToken", - } - - appCtx := suite.AppContextWithSessionForTest(&session) - - createdMove := factory.BuildMoveWithShipment(suite.DB(), nil, nil) - createdMove.SCAssignedID = &scUser.ID - createdMove.SCAssignedUser = &scUser - _, updateError := assignedOfficeUserUpdater.UpdateAssignedOfficeUser(appCtx, createdMove.ID, &scUser, roles.RoleTypeServicesCounselor) - - moves, _, err := orderFetcherTest.ListOrders(suite.AppContextWithSessionForTest(&session), scUser.ID, roles.RoleTypeServicesCounselor, &services.ListOrderParams{ - SCAssignedUser: &scUser.LastName, - }) - suite.FatalNoError(err) - suite.FatalNoError(updateError) - suite.Equal(1, len(moves)) - suite.Equal(moves[0].SCAssignedID, createdMove.SCAssignedID) - suite.Equal(createdMove.SCAssignedUser.ID, moves[0].SCAssignedUser.ID) - suite.Equal(createdMove.SCAssignedUser.FirstName, moves[0].SCAssignedUser.FirstName) - suite.Equal(createdMove.SCAssignedUser.LastName, moves[0].SCAssignedUser.LastName) -} -func (suite *OrderServiceSuite) TestListOrdersUSMCGBLOC() { - waf := entitlements.NewWeightAllotmentFetcher() - orderFetcher := NewOrderFetcher(waf) + suite.Run("task order queue does not return move with ONLY a destination address update request", func() { + officeUser, _, session := setupTestData() + move := factory.BuildMove(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + Show: models.BoolPointer(true), + }, + }}, nil) - suite.Run("returns USMC order for USMC office user", func() { - marines := models.AffiliationMARINES - // It doesn't matter what the Origin GBLOC is for the move. Only the Marines - // affiliation matters for office users who are tied to the USMC GBLOC. - factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + testUUID := uuid.UUID{} + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ { - Model: models.ServiceMember{ - Affiliation: &marines, + Model: move, + LinkOnly: true, + }, + { + Model: models.MTOShipment{ + DestinationAddressID: &testUUID, }, }, }, nil) - // Create move where service member has the default ARMY affiliation - factory.BuildMoveWithShipment(suite.DB(), nil, nil) - tioRole := roles.Role{RoleType: roles.RoleTypeTIO} - tooRole := roles.Role{RoleType: roles.RoleTypeTOO} - officeUserOooRah := factory.BuildOfficeUser(suite.DB(), []factory.Customization{ + suite.NotNil(shipment) + + shipmentAddressUpdate := factory.BuildShipmentAddressUpdate(suite.DB(), []factory.Customization{ { - Model: models.TransportationOffice{ - Gbloc: "USMC", - }, + Model: shipment, + LinkOnly: true, }, { - Model: models.User{ - Roles: []roles.Role{tioRole, tooRole}, + Model: move, + LinkOnly: true, + }, + { + Model: models.ShipmentAddressUpdate{ + NewAddressID: testUUID, }, }, - }, nil) - // Create office user tied to the default KKFA GBLOC + }, []factory.Trait{factory.GetTraitShipmentAddressUpdateRequested}) + suite.NotNil(shipmentAddressUpdate) + + moves, moveCount, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{}) + + suite.FatalNoError(err) + // even though 2 moves were created, one by setupTestData(), only one will be returned from the call to List Orders since we filter out + // the one with only a shipment address update to be routed to the destination requests queue + suite.Equal(1, moveCount) + suite.Equal(1, len(moves)) + }) + + suite.Run("task order queue returns a move with origin service items and destination address update request", func() { officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) session := auth.Session{ ApplicationName: auth.OfficeApp, @@ -657,340 +644,430 @@ func (suite *OrderServiceSuite) TestListOrdersUSMCGBLOC() { AccessToken: "fakeAccessToken", } - params := services.ListOrderParams{PerPage: models.Int64Pointer(2), Page: models.Int64Pointer(1)} - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUserOooRah.ID, roles.RoleTypeServicesCounselor, ¶ms) - - suite.FatalNoError(err) - suite.Equal(1, len(moves)) - suite.Equal(models.AffiliationMARINES, *moves[0].Orders.ServiceMember.Affiliation) - - params = services.ListOrderParams{PerPage: models.Int64Pointer(2), Page: models.Int64Pointer(1)} - moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeServicesCounselor, ¶ms) - - suite.FatalNoError(err) - suite.Equal(1, len(moves)) - suite.Equal(models.AffiliationARMY, *moves[0].Orders.ServiceMember.Affiliation) - }) -} + // build a move with only destination shuttle service item + move := factory.BuildMove(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + Show: models.BoolPointer(true), + }, + }}, nil) -func getMoveNeedsServiceCounseling(suite *OrderServiceSuite, showMove bool, affiliation models.ServiceMemberAffiliation) models.Move { - nonCloseoutMove := factory.BuildMove(suite.DB(), []factory.Customization{ - { - Model: models.Move{ - Status: models.MoveStatusNeedsServiceCounseling, - Show: &showMove, - }, - }, - { - Model: models.ServiceMember{ - Affiliation: &affiliation, + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, }, - }, - }, nil) - - return nonCloseoutMove -} - -func getSubmittedMove(suite *OrderServiceSuite, showMove bool, affiliation models.ServiceMemberAffiliation) models.Move { - move := factory.BuildMove(suite.DB(), []factory.Customization{ - { - Model: models.Move{ - Status: models.MoveStatusSUBMITTED, - Show: &showMove, + }, nil) + suite.NotNil(shipment) + originSITServiceItem := factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: shipment, + LinkOnly: true, }, - }, - { - Model: models.ServiceMember{ - Affiliation: &affiliation, + { + Model: models.ReService{ + Code: models.ReServiceCodeDOASIT, + }, }, - }, - }, nil) - return move -} + }, nil) + suite.NotNil(originSITServiceItem) -func buildPPMShipmentNeedsCloseout(suite *OrderServiceSuite, move models.Move) models.PPMShipment { - ppm := factory.BuildMinimalPPMShipment(suite.DB(), []factory.Customization{ - { - Model: models.PPMShipment{ - Status: models.PPMShipmentStatusNeedsCloseout, + testUUID := uuid.UUID{} + shipmentAddressUpdate := factory.BuildShipmentAddressUpdate(suite.DB(), []factory.Customization{ + { + Model: shipment, + LinkOnly: true, }, - }, - { - Model: move, - LinkOnly: true, - }, - }, nil) - return ppm -} - -func buildPPMShipmentDraft(suite *OrderServiceSuite, move models.Move) models.PPMShipment { - ppm := factory.BuildMinimalPPMShipment(suite.DB(), []factory.Customization{ - { - Model: models.PPMShipment{ - Status: models.PPMShipmentStatusDraft, + { + Model: move, + LinkOnly: true, }, - }, - { - Model: move, - LinkOnly: true, - }, - }, nil) - return ppm -} - -func buildPPMShipmentCloseoutComplete(suite *OrderServiceSuite, move models.Move) models.PPMShipment { - ppm := factory.BuildMinimalPPMShipment(suite.DB(), []factory.Customization{ - { - Model: models.PPMShipment{ - Status: models.PPMShipmentStatusCloseoutComplete, + { + Model: models.ShipmentAddressUpdate{ + NewAddressID: testUUID, + }, }, - }, - { - Model: move, - LinkOnly: true, - }, - }, nil) - return ppm -} -func (suite *OrderServiceSuite) TestListOrdersPPMCloseoutForArmyAirforce() { - waf := entitlements.NewWeightAllotmentFetcher() - orderFetcher := NewOrderFetcher(waf) + }, []factory.Trait{factory.GetTraitShipmentAddressUpdateRequested}) + suite.NotNil(shipmentAddressUpdate) - var session auth.Session + moves, moveCount, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{}) - suite.Run("office user in normal GBLOC should only see non-Navy/Marines/CoastGuard moves that need closeout in closeout tab", func() { - officeUserSC := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeServicesCounselor}) + suite.FatalNoError(err) + suite.Equal(1, moveCount) + suite.Equal(1, len(moves)) + }) - session = auth.Session{ + suite.Run("task order queue does not return move with ONLY requested destination SIT service items", func() { + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) + session := auth.Session{ ApplicationName: auth.OfficeApp, - Roles: officeUserSC.User.Roles, - OfficeUserID: officeUserSC.ID, + Roles: officeUser.User.Roles, + OfficeUserID: officeUser.ID, IDToken: "fake_token", AccessToken: "fakeAccessToken", } - move := getMoveNeedsServiceCounseling(suite, true, models.AffiliationARMY) - buildPPMShipmentNeedsCloseout(suite, move) + // build a move with only origin service items + move := factory.BuildMove(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + Show: models.BoolPointer(true), + }, + }}, nil) - afMove := getMoveNeedsServiceCounseling(suite, true, models.AffiliationAIRFORCE) - buildPPMShipmentDraft(suite, afMove) + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + }, nil) + suite.NotNil(shipment) + originSITServiceItem := factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.ReService{ + Code: models.ReServiceCodeDOFSIT, + }, + }, + }, nil) + suite.NotNil(originSITServiceItem) - cgMove := getMoveNeedsServiceCounseling(suite, true, models.AffiliationCOASTGUARD) - buildPPMShipmentNeedsCloseout(suite, cgMove) + // build a move with destination service items + move2 := factory.BuildMove(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + Show: models.BoolPointer(true), + }, + }}, nil) + shipment2 := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: move2, + LinkOnly: true, + }, + }, nil) - params := services.ListOrderParams{PerPage: models.Int64Pointer(9), Page: models.Int64Pointer(1), NeedsPPMCloseout: models.BoolPointer(true), Status: []string{string(models.MoveStatusNeedsServiceCounseling)}} - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUserSC.ID, roles.RoleTypeServicesCounselor, ¶ms) + destinationSITServiceItem := factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: shipment2, + LinkOnly: true, + }, + { + Model: models.ReService{ + Code: models.ReServiceCodeDDFSIT, + }, + }, + }, nil) + + moves, moveCount, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{}) + + suite.Equal(models.MTOServiceItemStatusSubmitted, destinationSITServiceItem.Status) suite.FatalNoError(err) + // even though 2 moves were created, only one will be returned from the call to List Orders since we filter out + // the one with only destination service items + suite.Equal(1, moveCount) suite.Equal(1, len(moves)) - suite.Equal(move.Locator, moves[0].Locator) }) - suite.Run("office user in normal GBLOC should not see moves that require closeout in counseling tab", func() { - officeUserSC := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeServicesCounselor}) - - session = auth.Session{ + suite.Run("task order queue returns a move with origin requested SIT service items", func() { + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) + session := auth.Session{ ApplicationName: auth.OfficeApp, - Roles: officeUserSC.User.Roles, - OfficeUserID: officeUserSC.ID, + Roles: officeUser.User.Roles, + OfficeUserID: officeUser.ID, IDToken: "fake_token", AccessToken: "fakeAccessToken", } - closeoutMove := getMoveNeedsServiceCounseling(suite, true, models.AffiliationARMY) - buildPPMShipmentCloseoutComplete(suite, closeoutMove) + // build a move with only origin service items + move := factory.BuildMove(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + Show: models.BoolPointer(true), + }, + }}, nil) - // PPM moves that are not in one of the closeout statuses - nonCloseoutMove := getMoveNeedsServiceCounseling(suite, true, models.AffiliationAIRFORCE) - buildPPMShipmentDraft(suite, nonCloseoutMove) - - params := services.ListOrderParams{PerPage: models.Int64Pointer(9), Page: models.Int64Pointer(1), NeedsPPMCloseout: models.BoolPointer(false), Status: []string{string(models.MoveStatusNeedsServiceCounseling)}} + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + }, nil) + suite.NotNil(shipment) + originSITServiceItem := factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.ReService{ + Code: models.ReServiceCodeDOFSIT, + }, + }, + }, nil) + suite.NotNil(originSITServiceItem) - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUserSC.ID, roles.RoleTypeServicesCounselor, ¶ms) + moves, moveCount, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{}) suite.FatalNoError(err) + suite.Equal(1, moveCount) suite.Equal(1, len(moves)) - suite.Equal(nonCloseoutMove.Locator, moves[0].Locator) }) -} - -func (suite *OrderServiceSuite) TestListOrdersPPMCloseoutForNavyCoastGuardAndMarines() { - waf := entitlements.NewWeightAllotmentFetcher() - orderFetcher := NewOrderFetcher(waf) - - suite.Run("returns Navy order for NAVY office user when there's a ppm shipment in closeout", func() { - // It doesn't matter what the Origin GBLOC is for the move. Only the navy - // affiliation matters for SC who are tied to the NAVY GBLOC. - move := getSubmittedMove(suite, true, models.AffiliationNAVY) - buildPPMShipmentNeedsCloseout(suite, move) - - cgMove := getSubmittedMove(suite, true, models.AffiliationCOASTGUARD) - buildPPMShipmentNeedsCloseout(suite, cgMove) - - officeUserSC := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ - { - Model: models.TransportationOffice{ - Gbloc: "NAVY", - }, - }, - }, []roles.RoleType{roles.RoleTypeServicesCounselor}) + suite.Run("task order queue returns a move with both origin and destination requested SIT service items", func() { + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) session := auth.Session{ ApplicationName: auth.OfficeApp, - Roles: officeUserSC.User.Roles, - OfficeUserID: officeUserSC.ID, + Roles: officeUser.User.Roles, + OfficeUserID: officeUser.ID, IDToken: "fake_token", AccessToken: "fakeAccessToken", } - params := services.ListOrderParams{PerPage: models.Int64Pointer(9), Page: models.Int64Pointer(1)} - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUserSC.ID, roles.RoleTypeServicesCounselor, ¶ms) - - suite.FatalNoError(err) - suite.Equal(1, len(moves)) - suite.Equal(models.AffiliationNAVY, *moves[0].Orders.ServiceMember.Affiliation) - - }) - - suite.Run("returns TVCB order for TVCB office user when there's a ppm shipment in closeout", func() { - // It doesn't matter what the Origin GBLOC is for the move. Only the marines - // affiliation matters for SC who are tied to the TVCB GBLOC. - move := getSubmittedMove(suite, true, models.AffiliationMARINES) - buildPPMShipmentNeedsCloseout(suite, move) - - nonMarineMove := getSubmittedMove(suite, true, models.AffiliationARMY) - buildPPMShipmentNeedsCloseout(suite, nonMarineMove) + // build a move with only origin service items + move := factory.BuildMove(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + Show: models.BoolPointer(true), + }, + }}, nil) - officeUserSC := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ { - Model: models.TransportationOffice{ - Gbloc: "TVCB", + Model: move, + LinkOnly: true, + }, + }, nil) + suite.NotNil(shipment) + originSITServiceItem := factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.ReService{ + Code: models.ReServiceCodeDOFSIT, }, }, - }, []roles.RoleType{roles.RoleTypeServicesCounselor}) + }, nil) + suite.NotNil(originSITServiceItem) - session := auth.Session{ - ApplicationName: auth.OfficeApp, - Roles: officeUserSC.User.Roles, - OfficeUserID: officeUserSC.ID, - IDToken: "fake_token", - AccessToken: "fakeAccessToken", - } + destinationSITServiceItem := factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.ReService{ + Code: models.ReServiceCodeDDFSIT, + }, + }, + }, nil) - params := services.ListOrderParams{PerPage: models.Int64Pointer(2), Page: models.Int64Pointer(1)} - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUserSC.ID, roles.RoleTypeServicesCounselor, ¶ms) + moves, moveCount, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{}) + suite.Equal(models.MTOServiceItemStatusSubmitted, destinationSITServiceItem.Status) suite.FatalNoError(err) + suite.Equal(1, moveCount) suite.Equal(1, len(moves)) - suite.Equal(models.AffiliationMARINES, *moves[0].Orders.ServiceMember.Affiliation) - }) - suite.Run("returns coast guard order for USCG office user when there's a ppm shipment in closeout and filters out non coast guard moves", func() { - // It doesn't matter what the Origin GBLOC is for the move. Only the coast guard - // affiliation matters for SC who are tied to the USCG GBLOC. - move := getSubmittedMove(suite, true, models.AffiliationCOASTGUARD) - buildPPMShipmentNeedsCloseout(suite, move) - - armyMove := getSubmittedMove(suite, true, models.AffiliationARMY) - buildPPMShipmentNeedsCloseout(suite, armyMove) - - officeUserSC := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ - { - Model: models.TransportationOffice{ - Gbloc: "USCG", - }, - }, - }, []roles.RoleType{roles.RoleTypeServicesCounselor}) - + suite.Run("task order queue returns a move with origin shuttle service item", func() { + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) session := auth.Session{ ApplicationName: auth.OfficeApp, - Roles: officeUserSC.User.Roles, - OfficeUserID: officeUserSC.ID, + Roles: officeUser.User.Roles, + OfficeUserID: officeUser.ID, IDToken: "fake_token", AccessToken: "fakeAccessToken", } - params := services.ListOrderParams{PerPage: models.Int64Pointer(2), Page: models.Int64Pointer(1)} - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUserSC.ID, roles.RoleTypeServicesCounselor, ¶ms) + // build a move with only origin shuttle service item + move := factory.BuildMove(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + Show: models.BoolPointer(true), + }, + }}, nil) + + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + }, nil) + suite.NotNil(shipment) + internationalOriginShuttleServiceItem := factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.ReService{ + Code: models.ReServiceCodeIOSHUT, + }, + }, + }, nil) + suite.NotNil(internationalOriginShuttleServiceItem) + + moves, moveCount, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{}) suite.FatalNoError(err) + suite.Equal(1, moveCount) suite.Equal(1, len(moves)) - suite.Equal(models.AffiliationCOASTGUARD, *moves[0].Orders.ServiceMember.Affiliation) }) - suite.Run("Filters out moves with PPM shipments not in the status of NeedsApproval", func() { + suite.Run("task order queue does not return a move with ONLY destination shuttle service item", func() { + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) + session := auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: officeUser.User.Roles, + OfficeUserID: officeUser.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } - cgMoveInWrongStatus := getSubmittedMove(suite, true, models.AffiliationCOASTGUARD) - buildPPMShipmentCloseoutComplete(suite, cgMoveInWrongStatus) + // build a move with only destination shuttle service item + move := factory.BuildMove(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + Show: models.BoolPointer(true), + }, + }}, nil) - officeUserSC := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ { - Model: models.TransportationOffice{ - Gbloc: "USCG", + Model: move, + LinkOnly: true, + }, + }, nil) + suite.NotNil(shipment) + domesticDestinationShuttleServiceItem := factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.ReService{ + Code: models.ReServiceCodeDDSHUT, }, }, - }, []roles.RoleType{roles.RoleTypeServicesCounselor}) - var session auth.Session - params := services.ListOrderParams{PerPage: models.Int64Pointer(2), Page: models.Int64Pointer(1)} - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUserSC.ID, roles.RoleTypeServicesCounselor, ¶ms) + }, nil) + suite.NotNil(domesticDestinationShuttleServiceItem) + + moves, moveCount, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{}) suite.FatalNoError(err) + suite.Equal(0, moveCount) suite.Equal(0, len(moves)) }) - suite.Run("Filters out moves with no PPM shipment", func() { + suite.Run("task order queue returns a move with both origin and destination shuttle service item", func() { + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) + session := auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: officeUser.User.Roles, + OfficeUserID: officeUser.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } - moveWithHHG := getSubmittedMove(suite, true, models.AffiliationCOASTGUARD) - factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + // build a move with only destination shuttle service item + move := factory.BuildMove(suite.DB(), []factory.Customization{ { - Model: models.MTOShipment{ - ShipmentType: models.MTOShipmentTypeHHG, + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + Show: models.BoolPointer(true), }, + }}, nil) + + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, }, + }, nil) + suite.NotNil(shipment) + domesticOriginShuttleServiceItem := factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ { - Model: moveWithHHG, + Model: shipment, LinkOnly: true, }, + { + Model: models.ReService{ + Code: models.ReServiceCodeDOSHUT, + }, + }, }, nil) + suite.NotNil(domesticOriginShuttleServiceItem) - officeUserSC := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + internationalDestinationShuttleServiceItem := factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ { - Model: models.TransportationOffice{ - Gbloc: "USCG", + Model: shipment, + LinkOnly: true, + }, + { + Model: models.ReService{ + Code: models.ReServiceCodeIDSHUT, }, }, - }, []roles.RoleType{roles.RoleTypeServicesCounselor}) + }, nil) + suite.NotNil(internationalDestinationShuttleServiceItem) + + moves, moveCount, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{}) + + suite.FatalNoError(err) + suite.Equal(1, moveCount) + suite.Equal(1, len(moves)) + }) + suite.Run("task order queue returns a move with excess weight flagged for review", func() { + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) session := auth.Session{ ApplicationName: auth.OfficeApp, - Roles: officeUserSC.User.Roles, - OfficeUserID: officeUserSC.ID, + Roles: officeUser.User.Roles, + OfficeUserID: officeUser.ID, IDToken: "fake_token", AccessToken: "fakeAccessToken", } - params := services.ListOrderParams{PerPage: models.Int64Pointer(2), Page: models.Int64Pointer(1)} - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUserSC.ID, roles.RoleTypeServicesCounselor, ¶ms) - - suite.FatalNoError(err) - suite.Equal(0, len(moves)) - }) -} - -func (suite *OrderServiceSuite) TestListOrdersMarines() { - waf := entitlements.NewWeightAllotmentFetcher() - suite.Run("does not return moves where the service member affiliation is Marines for non-USMC office user", func() { - - orderFetcher := NewOrderFetcher(waf) - marines := models.AffiliationMARINES - factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + now := time.Now() + // build a move with a ExcessWeightQualifiedAt value + move := factory.BuildMove(suite.DB(), []factory.Customization{ { - Model: models.ServiceMember{ - Affiliation: &marines, + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + Show: models.BoolPointer(true), + ExcessWeightQualifiedAt: &now, }, + }}, nil) + + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, }, }, nil) + suite.NotNil(shipment) + + moves, moveCount, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{}) + + suite.FatalNoError(err) + suite.Equal(1, moveCount) + suite.Equal(1, len(moves)) + }) + + suite.Run("task order queue returns a move with unaccompanied baggage excess weight flagged for review", func() { officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) session := auth.Session{ ApplicationName: auth.OfficeApp, @@ -1000,159 +1077,97 @@ func (suite *OrderServiceSuite) TestListOrdersMarines() { AccessToken: "fakeAccessToken", } - params := services.ListOrderParams{PerPage: models.Int64Pointer(2), Page: models.Int64Pointer(1)} - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) - - suite.FatalNoError(err) - suite.Equal(0, len(moves)) - }) -} + now := time.Now() + // build a move with a ExcessUnaccompaniedBaggageWeightQualifiedAt value + move := factory.BuildMove(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + Show: models.BoolPointer(true), + ExcessUnaccompaniedBaggageWeightQualifiedAt: &now, + }, + }}, nil) -func (suite *OrderServiceSuite) TestListOrdersWithEmptyFields() { - expectedOrder := factory.BuildOrder(suite.DB(), nil, nil) - waf := entitlements.NewWeightAllotmentFetcher() - expectedOrder.Entitlement = nil - expectedOrder.EntitlementID = nil - expectedOrder.Grade = nil - expectedOrder.OriginDutyLocation = nil - expectedOrder.OriginDutyLocationID = nil - suite.MustSave(&expectedOrder) - - move := factory.BuildMove(suite.DB(), []factory.Customization{ - { - Model: expectedOrder, - LinkOnly: true, - }, - }, nil) - // Only orders with shipments are returned, so we need to add a shipment - // to the move we just created - factory.BuildMTOShipment(suite.DB(), []factory.Customization{ - { - Model: move, - LinkOnly: true, - }, - { - Model: models.MTOShipment{ - Status: models.MTOShipmentStatusSubmitted, - }, - }, - }, nil) - // Add a second shipment to make sure we only return 1 order even if its - // move has more than one shipment - factory.BuildMTOShipment(suite.DB(), []factory.Customization{ - { - Model: move, - LinkOnly: true, - }, - { - Model: models.MTOShipment{ - Status: models.MTOShipmentStatusSubmitted, + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, }, - }, - }, nil) - - officeUser := factory.BuildOfficeUser(suite.DB(), nil, nil) - session := auth.Session{ - ApplicationName: auth.OfficeApp, - Roles: officeUser.User.Roles, - OfficeUserID: officeUser.ID, - IDToken: "fake_token", - AccessToken: "fakeAccessToken", - } - - orderFetcher := NewOrderFetcher(waf) - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{PerPage: models.Int64Pointer(1), Page: models.Int64Pointer(1)}) - - suite.FatalNoError(err) - suite.Nil(moves) - -} - -func (suite *OrderServiceSuite) TestListOrdersWithPagination() { - officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) - waf := entitlements.NewWeightAllotmentFetcher() - session := auth.Session{ - ApplicationName: auth.OfficeApp, - Roles: officeUser.User.Roles, - OfficeUserID: officeUser.ID, - IDToken: "fake_token", - AccessToken: "fakeAccessToken", - } - - for i := 0; i < 2; i++ { - factory.BuildMoveWithShipment(suite.DB(), nil, nil) - } - - orderFetcher := NewOrderFetcher(waf) - params := services.ListOrderParams{Page: models.Int64Pointer(1), PerPage: models.Int64Pointer(1)} - moves, count, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) - - suite.NoError(err) - suite.Equal(1, len(moves)) - suite.Equal(2, count) - -} + }, nil) + suite.NotNil(shipment) -func (suite *OrderServiceSuite) TestListOrdersWithSortOrder() { + moves, moveCount, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{}) - // SET UP: Service Members for sorting by Service Member Last Name and Branch - // - We'll need two other service members to test the last name sort, Lea Spacemen and Leo Zephyer - serviceMemberFirstName := "Lea" - serviceMemberLastName := "Zephyer" - affiliation := models.AffiliationNAVY - edipi := "9999999999" - var officeUser models.OfficeUser - waf := entitlements.NewWeightAllotmentFetcher() - // SET UP: Dates for sorting by Requested Move Date - // - We want dates 2 and 3 to sandwich requestedMoveDate1 so we can test that the min() query is working - requestedMoveDate1 := time.Date(testdatagen.GHCTestYear, 02, 20, 0, 0, 0, 0, time.UTC) - requestedMoveDate2 := time.Date(testdatagen.GHCTestYear, 03, 03, 0, 0, 0, 0, time.UTC) - requestedMoveDate3 := time.Date(testdatagen.GHCTestYear, 01, 15, 0, 0, 0, 0, time.UTC) + suite.FatalNoError(err) + suite.Equal(1, moveCount) + suite.Equal(1, len(moves)) + }) - setupTestData := func() (models.Move, models.Move, auth.Session) { + suite.Run("task order queue returns a move with a pending SIT extension and origin service items to review", func() { + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) + session := auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: officeUser.User.Roles, + OfficeUserID: officeUser.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } - // CREATE EXPECTED MOVES - expectedMove1 := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ - { // Default New Duty Location name is Fort Eisenhower + // build a move with origin service items + move := factory.BuildMove(suite.DB(), []factory.Customization{ + { Model: models.Move{ - Status: models.MoveStatusAPPROVED, - Locator: "AA1234", + Status: models.MoveStatusAPPROVALSREQUESTED, + Show: models.BoolPointer(true), }, - }, + }}, nil) + + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ { - Model: models.MTOShipment{ - RequestedPickupDate: &requestedMoveDate1, - }, + Model: move, + LinkOnly: true, }, }, nil) - expectedMove2 := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + suite.NotNil(shipment) + originSITServiceItem := factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ { - Model: models.Move{ - Locator: "TTZ123", + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusApproved, }, }, { - Model: models.ServiceMember{ - Affiliation: &affiliation, - FirstName: &serviceMemberFirstName, - Edipi: &edipi, - }, + Model: shipment, + LinkOnly: true, }, { - Model: models.MTOShipment{ - RequestedPickupDate: &requestedMoveDate2, + Model: models.ReService{ + Code: models.ReServiceCodeDOFSIT, }, }, }, nil) - // Create a second shipment so we can test min() sort - factory.BuildMTOShipmentWithMove(&expectedMove2, suite.DB(), []factory.Customization{ + suite.NotNil(originSITServiceItem) + sitExtension := factory.BuildSITDurationUpdate(suite.DB(), []factory.Customization{ { - Model: models.MTOShipment{ - RequestedPickupDate: &requestedMoveDate3, + Model: shipment, + LinkOnly: true, + }, + { + Model: models.SITDurationUpdate{ + Status: models.SITExtensionStatusPending, }, }, }, nil) - officeUser = factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) + suite.NotNil(sitExtension) + + moves, moveCount, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{}) + + suite.FatalNoError(err) + suite.Equal(1, moveCount) + suite.Equal(1, len(moves)) + }) + + suite.Run("task order queue does NOT return a move with a pending SIT extension and only destination service items to review", func() { + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) session := auth.Session{ ApplicationName: auth.OfficeApp, Roles: officeUser.User.Roles, @@ -1161,86 +1176,61 @@ func (suite *OrderServiceSuite) TestListOrdersWithSortOrder() { AccessToken: "fakeAccessToken", } - return expectedMove1, expectedMove2, session - } - - orderFetcher := NewOrderFetcher(waf) - - suite.Run("Sort by locator code", func() { - expectedMove1, expectedMove2, session := setupTestData() - params := services.ListOrderParams{Sort: models.StringPointer("locator"), Order: models.StringPointer("asc")} - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) - suite.NoError(err) - suite.Equal(2, len(moves)) - suite.Equal(expectedMove1.Locator, moves[0].Locator) - suite.Equal(expectedMove2.Locator, moves[1].Locator) - - params = services.ListOrderParams{Sort: models.StringPointer("locator"), Order: models.StringPointer("desc")} - moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) - suite.NoError(err) - suite.Equal(2, len(moves)) - suite.Equal(expectedMove2.Locator, moves[0].Locator) - suite.Equal(expectedMove1.Locator, moves[1].Locator) - }) - - suite.Run("Sort by move status", func() { - expectedMove1, expectedMove2, session := setupTestData() - params := services.ListOrderParams{Sort: models.StringPointer("status"), Order: models.StringPointer("asc")} - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) - suite.NoError(err) - suite.Equal(2, len(moves)) - suite.Equal(expectedMove1.Status, moves[0].Status) - suite.Equal(expectedMove2.Status, moves[1].Status) - - params = services.ListOrderParams{Sort: models.StringPointer("status"), Order: models.StringPointer("desc")} - moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) - suite.NoError(err) - suite.Equal(2, len(moves)) - suite.Equal(expectedMove2.Status, moves[0].Status) - suite.Equal(expectedMove1.Status, moves[1].Status) - }) - - suite.Run("Sort by service member affiliations", func() { - expectedMove1, expectedMove2, session := setupTestData() - params := services.ListOrderParams{Sort: models.StringPointer("branch"), Order: models.StringPointer("asc")} - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) - suite.NoError(err) - suite.Equal(2, len(moves)) - suite.Equal(*expectedMove1.Orders.ServiceMember.Affiliation, *moves[0].Orders.ServiceMember.Affiliation) - suite.Equal(*expectedMove2.Orders.ServiceMember.Affiliation, *moves[1].Orders.ServiceMember.Affiliation) + // build a move with destination service items + move := factory.BuildMove(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + Show: models.BoolPointer(true), + }, + }}, nil) - params = services.ListOrderParams{Sort: models.StringPointer("branch"), Order: models.StringPointer("desc")} - moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) - suite.NoError(err) - suite.Equal(2, len(moves)) - suite.Equal(*expectedMove2.Orders.ServiceMember.Affiliation, *moves[0].Orders.ServiceMember.Affiliation) - suite.Equal(*expectedMove1.Orders.ServiceMember.Affiliation, *moves[1].Orders.ServiceMember.Affiliation) - }) + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + }, nil) + suite.NotNil(shipment) + destinationSITServiceItem := factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusApproved, + }, + }, + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.ReService{ + Code: models.ReServiceCodeDDFSIT, + }, + }, + }, nil) + suite.NotNil(destinationSITServiceItem) + sitExtension := factory.BuildSITDurationUpdate(suite.DB(), []factory.Customization{ + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.SITDurationUpdate{ + Status: models.SITExtensionStatusPending, + }, + }, + }, nil) + suite.NotNil(sitExtension) - suite.Run("Sort by request move date", func() { - _, _, session := setupTestData() - params := services.ListOrderParams{Sort: models.StringPointer("requestedMoveDate"), Order: models.StringPointer("asc")} - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) - suite.NoError(err) - suite.Equal(2, len(moves)) - suite.Equal(2, len(moves[0].MTOShipments)) // the move with two shipments has the earlier date - suite.Equal(1, len(moves[1].MTOShipments)) - // NOTE: You have to use Jan 02, 2006 as the example for date/time formatting in Go - suite.Equal(requestedMoveDate1.Format("2006/01/02"), moves[1].MTOShipments[0].RequestedPickupDate.Format("2006/01/02")) + moves, moveCount, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{}) - params = services.ListOrderParams{Sort: models.StringPointer("requestedMoveDate"), Order: models.StringPointer("desc")} - moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) - suite.NoError(err) - suite.Equal(2, len(moves)) - suite.Equal(1, len(moves[0].MTOShipments)) // the move with one shipment should be first - suite.Equal(2, len(moves[1].MTOShipments)) - suite.Equal(requestedMoveDate1.Format("2006/01/02"), moves[0].MTOShipments[0].RequestedPickupDate.Format("2006/01/02")) + suite.FatalNoError(err) + suite.Equal(0, moveCount) + suite.Equal(0, len(moves)) }) - suite.Run("Sort by submitted date (appearedInTooAt) in TOO queue ", func() { - // Scenario: In order to sort the moves the submitted_at, service_counseling_completed_at, and approvals_requested_at are checked to which are the minimum - // Expected: The moves appear in the order they are created below - officeUser = factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) + suite.Run("task order queue returns a move with a pending SIT extension and BOTH origin and destination service items to review", func() { + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) session := auth.Session{ ApplicationName: auth.OfficeApp, Roles: officeUser.User.Roles, @@ -1248,376 +1238,1820 @@ func (suite *OrderServiceSuite) TestListOrdersWithSortOrder() { IDToken: "fake_token", AccessToken: "fakeAccessToken", } - now := time.Now() - oneWeekAgo := now.AddDate(0, 0, -7) - move1 := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + + // build a move with only origin service items + move := factory.BuildMove(suite.DB(), []factory.Customization{ { Model: models.Move{ - SubmittedAt: &oneWeekAgo, + Status: models.MoveStatusAPPROVALSREQUESTED, + Show: models.BoolPointer(true), }, + }}, nil) + + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, }, }, nil) - move2 := factory.BuildApprovalsRequestedMove(suite.DB(), nil, nil) - factory.BuildMTOShipmentWithMove(&move2, suite.DB(), nil, nil) - move3 := factory.BuildServiceCounselingCompletedMove(suite.DB(), nil, nil) - factory.BuildMTOShipmentWithMove(&move3, suite.DB(), nil, nil) - - params := services.ListOrderParams{Sort: models.StringPointer("appearedInTooAt"), Order: models.StringPointer("asc")} - - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) - suite.NoError(err) - suite.Equal(3, len(moves)) - suite.Equal(moves[0].ID, move1.ID) - suite.Equal(moves[1].ID, move2.ID) - suite.Equal(moves[2].ID, move3.ID) - }) - - // MUST BE LAST, ADDS EXTRA MOVE - suite.Run("Sort by service member last name", func() { - _, _, session := setupTestData() + suite.NotNil(shipment) + originSITServiceItem := factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.ReService{ + Code: models.ReServiceCodeDOFSIT, + }, + }, + }, nil) + suite.NotNil(originSITServiceItem) - // Last name sort is the only one that needs 3 moves for a complete test, so add that here at the end - factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + destinationSITServiceItem := factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ { - Model: models.ServiceMember{ // Leo Zephyer - LastName: &serviceMemberLastName, + Model: shipment, + LinkOnly: true, + }, + { + Model: models.ReService{ + Code: models.ReServiceCodeDDFSIT, }, }, }, nil) - params := services.ListOrderParams{Sort: models.StringPointer("customerName"), Order: models.StringPointer("asc")} - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) - suite.NoError(err) - suite.Equal(3, len(moves)) - suite.Equal("Spacemen, Lea", *moves[0].Orders.ServiceMember.LastName+", "+*moves[0].Orders.ServiceMember.FirstName) - suite.Equal("Spacemen, Leo", *moves[1].Orders.ServiceMember.LastName+", "+*moves[1].Orders.ServiceMember.FirstName) - suite.Equal("Zephyer, Leo", *moves[2].Orders.ServiceMember.LastName+", "+*moves[2].Orders.ServiceMember.FirstName) + sitExtension := factory.BuildSITDurationUpdate(suite.DB(), []factory.Customization{ + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.SITDurationUpdate{ + Status: models.SITExtensionStatusPending, + }, + }, + }, nil) + suite.NotNil(sitExtension) - params = services.ListOrderParams{Sort: models.StringPointer("customerName"), Order: models.StringPointer("desc")} - moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) + moves, moveCount, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{}) - suite.NoError(err) - suite.Equal(3, len(moves)) - suite.Equal("Zephyer, Leo", *moves[0].Orders.ServiceMember.LastName+", "+*moves[0].Orders.ServiceMember.FirstName) - suite.Equal("Spacemen, Leo", *moves[1].Orders.ServiceMember.LastName+", "+*moves[1].Orders.ServiceMember.FirstName) - suite.Equal("Spacemen, Lea", *moves[2].Orders.ServiceMember.LastName+", "+*moves[2].Orders.ServiceMember.FirstName) + suite.Equal(models.MTOServiceItemStatusSubmitted, destinationSITServiceItem.Status) + suite.FatalNoError(err) + suite.Equal(1, moveCount) + suite.Equal(1, len(moves)) }) } +func (suite *OrderServiceSuite) TestListOrderWithAssignedUserSingle() { + // Under test: ListOrders + // Set up: Make a move, assign one to an SC office user + // Expected outcome: Only the one move with the assigned user should be returned + assignedOfficeUserUpdater := moveservice.NewAssignedOfficeUserUpdater(moveservice.NewMoveFetcher()) + scUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeServicesCounselor}) + var orderFetcherTest orderFetcher + session := auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: scUser.User.Roles, + OfficeUserID: scUser.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } -func getTransportationOffice(suite *OrderServiceSuite, name string) models.TransportationOffice { - trasportationOffice := factory.BuildTransportationOffice(suite.DB(), []factory.Customization{ - { - Model: models.TransportationOffice{ - Name: name, - }, - }}, nil) - return trasportationOffice -} + appCtx := suite.AppContextWithSessionForTest(&session) -func getPPMShipmentWithCloseoutOfficeNeedsCloseout(suite *OrderServiceSuite, closeoutOffice models.TransportationOffice) models.PPMShipment { - ppm := factory.BuildPPMShipmentThatNeedsCloseout(suite.DB(), nil, []factory.Customization{ - { - Model: closeoutOffice, - LinkOnly: true, - Type: &factory.TransportationOffices.CloseoutOffice, - }, + createdMove := factory.BuildMoveWithShipment(suite.DB(), nil, nil) + createdMove.SCAssignedID = &scUser.ID + createdMove.SCAssignedUser = &scUser + _, updateError := assignedOfficeUserUpdater.UpdateAssignedOfficeUser(appCtx, createdMove.ID, &scUser, roles.RoleTypeServicesCounselor) + + moves, _, err := orderFetcherTest.ListOrders(suite.AppContextWithSessionForTest(&session), scUser.ID, roles.RoleTypeServicesCounselor, &services.ListOrderParams{ + SCAssignedUser: &scUser.LastName, }) - return ppm -} -func (suite *OrderServiceSuite) TestListOrdersNeedingServicesCounselingWithPPMCloseoutColumnsSort() { - defaultShipmentPickupPostalCode := "90210" + suite.FatalNoError(err) + suite.FatalNoError(updateError) + suite.Equal(1, len(moves)) + suite.Equal(moves[0].SCAssignedID, createdMove.SCAssignedID) + suite.Equal(createdMove.SCAssignedUser.ID, moves[0].SCAssignedUser.ID) + suite.Equal(createdMove.SCAssignedUser.FirstName, moves[0].SCAssignedUser.FirstName) + suite.Equal(createdMove.SCAssignedUser.LastName, moves[0].SCAssignedUser.LastName) +} +func (suite *OrderServiceSuite) TestListOrdersUSMCGBLOC() { waf := entitlements.NewWeightAllotmentFetcher() - setupTestData := func() models.OfficeUser { - // Make an office user → GBLOC X - officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) - factory.FetchOrBuildPostalCodeToGBLOC(suite.DB(), "50309", officeUser.TransportationOffice.Gbloc) - - // Ensure there's an entry connecting the default shipment pickup postal code with the office user's gbloc - factory.FetchOrBuildPostalCodeToGBLOC(suite.DB(), - defaultShipmentPickupPostalCode, - officeUser.TransportationOffice.Gbloc) - - return officeUser - } orderFetcher := NewOrderFetcher(waf) - var session auth.Session - - suite.Run("Sort by PPM closeout initiated", func() { - officeUser := setupTestData() - // Create a PPM submitted on April 1st - closeoutInitiatedDate1 := time.Date(2022, 04, 01, 0, 0, 0, 0, time.UTC) - closeoutOffice := factory.BuildTransportationOffice(suite.DB(), []factory.Customization{ + suite.Run("returns USMC order for USMC office user", func() { + marines := models.AffiliationMARINES + // It doesn't matter what the Origin GBLOC is for the move. Only the Marines + // affiliation matters for office users who are tied to the USMC GBLOC. + factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ { - Model: models.TransportationOffice{Gbloc: "KKFA"}, + Model: models.ServiceMember{ + Affiliation: &marines, + }, }, }, nil) + // Create move where service member has the default ARMY affiliation + factory.BuildMoveWithShipment(suite.DB(), nil, nil) - ppm1 := factory.BuildPPMShipmentThatNeedsCloseout(suite.DB(), nil, []factory.Customization{ + tioRole := roles.Role{RoleType: roles.RoleTypeTIO} + tooRole := roles.Role{RoleType: roles.RoleTypeTOO} + officeUserOooRah := factory.BuildOfficeUser(suite.DB(), []factory.Customization{ { - Model: models.PPMShipment{ - SubmittedAt: &closeoutInitiatedDate1, + Model: models.TransportationOffice{ + Gbloc: "USMC", }, }, { - Model: closeoutOffice, - LinkOnly: true, - Type: &factory.TransportationOffices.CloseoutOffice, - }, - }) - - // Create a PPM submitted on April 2nd - closeoutInitiatedDate2 := time.Date(2022, 04, 02, 0, 0, 0, 0, time.UTC) - ppm2 := factory.BuildPPMShipmentThatNeedsCloseout(suite.DB(), nil, []factory.Customization{ - { - Model: models.PPMShipment{ - SubmittedAt: &closeoutInitiatedDate2, + Model: models.User{ + Roles: []roles.Role{tioRole, tooRole}, }, }, - { - Model: closeoutOffice, - LinkOnly: true, - Type: &factory.TransportationOffices.CloseoutOffice, - }, - }) + }, nil) + // Create office user tied to the default KKFA GBLOC + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) + session := auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: officeUser.User.Roles, + OfficeUserID: officeUser.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } - // Sort by closeout initiated date (ascending) - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{ - NeedsPPMCloseout: models.BoolPointer(true), - Sort: models.StringPointer("closeoutInitiated"), - Order: models.StringPointer("asc"), - }) + params := services.ListOrderParams{PerPage: models.Int64Pointer(2), Page: models.Int64Pointer(1)} + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUserOooRah.ID, roles.RoleTypeServicesCounselor, ¶ms) suite.FatalNoError(err) - suite.Equal(2, len(moves)) - suite.Equal(ppm1.Shipment.MoveTaskOrder.Locator, moves[0].Locator) - suite.Equal(ppm2.Shipment.MoveTaskOrder.Locator, moves[1].Locator) + suite.Equal(1, len(moves)) + suite.Equal(models.AffiliationMARINES, *moves[0].Orders.ServiceMember.Affiliation) - // Sort by closeout initiated date (descending) - moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{ - NeedsPPMCloseout: models.BoolPointer(true), - Sort: models.StringPointer("closeoutInitiated"), - Order: models.StringPointer("desc"), - }) + params = services.ListOrderParams{PerPage: models.Int64Pointer(2), Page: models.Int64Pointer(1)} + moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeServicesCounselor, ¶ms) suite.FatalNoError(err) - suite.Equal(2, len(moves)) - suite.Equal(ppm2.Shipment.MoveTaskOrder.Locator, moves[0].Locator) - suite.Equal(ppm1.Shipment.MoveTaskOrder.Locator, moves[1].Locator) + suite.Equal(1, len(moves)) + suite.Equal(models.AffiliationARMY, *moves[0].Orders.ServiceMember.Affiliation) }) +} - suite.Run("Sort by PPM closeout location", func() { - officeUser := setupTestData() - - locationA := getTransportationOffice(suite, "A") - ppmShipmentA := getPPMShipmentWithCloseoutOfficeNeedsCloseout(suite, locationA) +func getMoveNeedsServiceCounseling(suite *OrderServiceSuite, showMove bool, affiliation models.ServiceMemberAffiliation) models.Move { + nonCloseoutMove := factory.BuildMove(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusNeedsServiceCounseling, + Show: &showMove, + }, + }, + { + Model: models.ServiceMember{ + Affiliation: &affiliation, + }, + }, + }, nil) - locationB := getTransportationOffice(suite, "B") - ppmShipmentB := getPPMShipmentWithCloseoutOfficeNeedsCloseout(suite, locationB) + return nonCloseoutMove +} - // Sort by closeout location (ascending) - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{ - NeedsPPMCloseout: models.BoolPointer(true), - Sort: models.StringPointer("closeoutLocation"), - Order: models.StringPointer("asc"), - }) +func getSubmittedMove(suite *OrderServiceSuite, showMove bool, affiliation models.ServiceMemberAffiliation) models.Move { + move := factory.BuildMove(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusSUBMITTED, + Show: &showMove, + }, + }, + { + Model: models.ServiceMember{ + Affiliation: &affiliation, + }, + }, + }, nil) + return move +} + +func buildPPMShipmentNeedsCloseout(suite *OrderServiceSuite, move models.Move) models.PPMShipment { + ppm := factory.BuildMinimalPPMShipment(suite.DB(), []factory.Customization{ + { + Model: models.PPMShipment{ + Status: models.PPMShipmentStatusNeedsCloseout, + }, + }, + { + Model: move, + LinkOnly: true, + }, + }, nil) + return ppm +} + +func buildPPMShipmentDraft(suite *OrderServiceSuite, move models.Move) models.PPMShipment { + ppm := factory.BuildMinimalPPMShipment(suite.DB(), []factory.Customization{ + { + Model: models.PPMShipment{ + Status: models.PPMShipmentStatusDraft, + }, + }, + { + Model: move, + LinkOnly: true, + }, + }, nil) + return ppm +} + +func buildPPMShipmentCloseoutComplete(suite *OrderServiceSuite, move models.Move) models.PPMShipment { + ppm := factory.BuildMinimalPPMShipment(suite.DB(), []factory.Customization{ + { + Model: models.PPMShipment{ + Status: models.PPMShipmentStatusCloseoutComplete, + }, + }, + { + Model: move, + LinkOnly: true, + }, + }, nil) + return ppm +} +func (suite *OrderServiceSuite) TestListOrdersPPMCloseoutForArmyAirforce() { + waf := entitlements.NewWeightAllotmentFetcher() + orderFetcher := NewOrderFetcher(waf) + + var session auth.Session + + suite.Run("office user in normal GBLOC should only see non-Navy/Marines/CoastGuard moves that need closeout in closeout tab", func() { + officeUserSC := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeServicesCounselor}) + + session = auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: officeUserSC.User.Roles, + OfficeUserID: officeUserSC.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } + + move := getMoveNeedsServiceCounseling(suite, true, models.AffiliationARMY) + buildPPMShipmentNeedsCloseout(suite, move) + + afMove := getMoveNeedsServiceCounseling(suite, true, models.AffiliationAIRFORCE) + buildPPMShipmentDraft(suite, afMove) + + cgMove := getMoveNeedsServiceCounseling(suite, true, models.AffiliationCOASTGUARD) + buildPPMShipmentNeedsCloseout(suite, cgMove) + + params := services.ListOrderParams{PerPage: models.Int64Pointer(9), Page: models.Int64Pointer(1), NeedsPPMCloseout: models.BoolPointer(true), Status: []string{string(models.MoveStatusNeedsServiceCounseling)}} + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUserSC.ID, roles.RoleTypeServicesCounselor, ¶ms) suite.FatalNoError(err) - suite.Equal(2, len(moves)) - suite.Equal(ppmShipmentA.Shipment.MoveTaskOrder.Locator, moves[0].Locator) - suite.Equal(ppmShipmentB.Shipment.MoveTaskOrder.Locator, moves[1].Locator) + suite.Equal(1, len(moves)) + suite.Equal(move.Locator, moves[0].Locator) + }) - // Sort by closeout location (descending) - moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{ - NeedsPPMCloseout: models.BoolPointer(true), - Sort: models.StringPointer("closeoutLocation"), - Order: models.StringPointer("desc"), - }) + suite.Run("office user in normal GBLOC should not see moves that require closeout in counseling tab", func() { + officeUserSC := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeServicesCounselor}) + + session = auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: officeUserSC.User.Roles, + OfficeUserID: officeUserSC.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } + + closeoutMove := getMoveNeedsServiceCounseling(suite, true, models.AffiliationARMY) + buildPPMShipmentCloseoutComplete(suite, closeoutMove) + + // PPM moves that are not in one of the closeout statuses + nonCloseoutMove := getMoveNeedsServiceCounseling(suite, true, models.AffiliationAIRFORCE) + buildPPMShipmentDraft(suite, nonCloseoutMove) + + params := services.ListOrderParams{PerPage: models.Int64Pointer(9), Page: models.Int64Pointer(1), NeedsPPMCloseout: models.BoolPointer(false), Status: []string{string(models.MoveStatusNeedsServiceCounseling)}} + + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUserSC.ID, roles.RoleTypeServicesCounselor, ¶ms) suite.FatalNoError(err) - suite.Equal(2, len(moves)) - suite.Equal(ppmShipmentB.Shipment.MoveTaskOrder.Locator, moves[0].Locator) - suite.Equal(ppmShipmentA.Shipment.MoveTaskOrder.Locator, moves[1].Locator) + suite.Equal(1, len(moves)) + suite.Equal(nonCloseoutMove.Locator, moves[0].Locator) }) +} - suite.Run("Sort by destination duty location", func() { - officeUser := setupTestData() +func (suite *OrderServiceSuite) TestListOrdersPPMCloseoutForNavyCoastGuardAndMarines() { + waf := entitlements.NewWeightAllotmentFetcher() + orderFetcher := NewOrderFetcher(waf) - dutyLocationA := factory.BuildDutyLocation(suite.DB(), []factory.Customization{ + suite.Run("returns Navy order for NAVY office user when there's a ppm shipment in closeout", func() { + // It doesn't matter what the Origin GBLOC is for the move. Only the navy + // affiliation matters for SC who are tied to the NAVY GBLOC. + move := getSubmittedMove(suite, true, models.AffiliationNAVY) + buildPPMShipmentNeedsCloseout(suite, move) + + cgMove := getSubmittedMove(suite, true, models.AffiliationCOASTGUARD) + buildPPMShipmentNeedsCloseout(suite, cgMove) + + officeUserSC := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ { - Model: models.DutyLocation{ - Name: "A", + Model: models.TransportationOffice{ + Gbloc: "NAVY", }, }, - }, nil) - closeoutOffice := factory.BuildTransportationOffice(suite.DB(), []factory.Customization{ + }, []roles.RoleType{roles.RoleTypeServicesCounselor}) + + session := auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: officeUserSC.User.Roles, + OfficeUserID: officeUserSC.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } + + params := services.ListOrderParams{PerPage: models.Int64Pointer(9), Page: models.Int64Pointer(1)} + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUserSC.ID, roles.RoleTypeServicesCounselor, ¶ms) + + suite.FatalNoError(err) + suite.Equal(1, len(moves)) + suite.Equal(models.AffiliationNAVY, *moves[0].Orders.ServiceMember.Affiliation) + + }) + + suite.Run("returns TVCB order for TVCB office user when there's a ppm shipment in closeout", func() { + // It doesn't matter what the Origin GBLOC is for the move. Only the marines + // affiliation matters for SC who are tied to the TVCB GBLOC. + move := getSubmittedMove(suite, true, models.AffiliationMARINES) + buildPPMShipmentNeedsCloseout(suite, move) + + nonMarineMove := getSubmittedMove(suite, true, models.AffiliationARMY) + buildPPMShipmentNeedsCloseout(suite, nonMarineMove) + + officeUserSC := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ { - Model: models.TransportationOffice{Gbloc: "KKFA"}, + Model: models.TransportationOffice{ + Gbloc: "TVCB", + }, }, - }, nil) + }, []roles.RoleType{roles.RoleTypeServicesCounselor}) - ppmShipmentA := factory.BuildPPMShipmentThatNeedsCloseout(suite.DB(), nil, []factory.Customization{ + session := auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: officeUserSC.User.Roles, + OfficeUserID: officeUserSC.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } + + params := services.ListOrderParams{PerPage: models.Int64Pointer(2), Page: models.Int64Pointer(1)} + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUserSC.ID, roles.RoleTypeServicesCounselor, ¶ms) + + suite.FatalNoError(err) + suite.Equal(1, len(moves)) + suite.Equal(models.AffiliationMARINES, *moves[0].Orders.ServiceMember.Affiliation) + + }) + + suite.Run("returns coast guard order for USCG office user when there's a ppm shipment in closeout and filters out non coast guard moves", func() { + // It doesn't matter what the Origin GBLOC is for the move. Only the coast guard + // affiliation matters for SC who are tied to the USCG GBLOC. + move := getSubmittedMove(suite, true, models.AffiliationCOASTGUARD) + buildPPMShipmentNeedsCloseout(suite, move) + + armyMove := getSubmittedMove(suite, true, models.AffiliationARMY) + buildPPMShipmentNeedsCloseout(suite, armyMove) + + officeUserSC := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ { - Model: dutyLocationA, - LinkOnly: true, - Type: &factory.DutyLocations.NewDutyLocation, + Model: models.TransportationOffice{ + Gbloc: "USCG", + }, + }, + }, []roles.RoleType{roles.RoleTypeServicesCounselor}) + + session := auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: officeUserSC.User.Roles, + OfficeUserID: officeUserSC.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } + + params := services.ListOrderParams{PerPage: models.Int64Pointer(2), Page: models.Int64Pointer(1)} + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUserSC.ID, roles.RoleTypeServicesCounselor, ¶ms) + + suite.FatalNoError(err) + suite.Equal(1, len(moves)) + suite.Equal(models.AffiliationCOASTGUARD, *moves[0].Orders.ServiceMember.Affiliation) + }) + + suite.Run("Filters out moves with PPM shipments not in the status of NeedsApproval", func() { + + cgMoveInWrongStatus := getSubmittedMove(suite, true, models.AffiliationCOASTGUARD) + buildPPMShipmentCloseoutComplete(suite, cgMoveInWrongStatus) + + officeUserSC := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + { + Model: models.TransportationOffice{ + Gbloc: "USCG", + }, + }, + }, []roles.RoleType{roles.RoleTypeServicesCounselor}) + var session auth.Session + params := services.ListOrderParams{PerPage: models.Int64Pointer(2), Page: models.Int64Pointer(1)} + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUserSC.ID, roles.RoleTypeServicesCounselor, ¶ms) + + suite.FatalNoError(err) + suite.Equal(0, len(moves)) + }) + + suite.Run("Filters out moves with no PPM shipment", func() { + + moveWithHHG := getSubmittedMove(suite, true, models.AffiliationCOASTGUARD) + factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + ShipmentType: models.MTOShipmentTypeHHG, + }, }, { - Model: closeoutOffice, + Model: moveWithHHG, LinkOnly: true, - Type: &factory.TransportationOffices.CloseoutOffice, }, - }) - dutyLocationB := factory.BuildDutyLocation(suite.DB(), []factory.Customization{ + }, nil) + + officeUserSC := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + { + Model: models.TransportationOffice{ + Gbloc: "USCG", + }, + }, + }, []roles.RoleType{roles.RoleTypeServicesCounselor}) + + session := auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: officeUserSC.User.Roles, + OfficeUserID: officeUserSC.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } + + params := services.ListOrderParams{PerPage: models.Int64Pointer(2), Page: models.Int64Pointer(1)} + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUserSC.ID, roles.RoleTypeServicesCounselor, ¶ms) + + suite.FatalNoError(err) + suite.Equal(0, len(moves)) + }) +} + +func (suite *OrderServiceSuite) TestListOrdersMarines() { + waf := entitlements.NewWeightAllotmentFetcher() + suite.Run("does not return moves where the service member affiliation is Marines for non-USMC office user", func() { + + orderFetcher := NewOrderFetcher(waf) + marines := models.AffiliationMARINES + factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.ServiceMember{ + Affiliation: &marines, + }, + }, + }, nil) + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) + session := auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: officeUser.User.Roles, + OfficeUserID: officeUser.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } + + params := services.ListOrderParams{PerPage: models.Int64Pointer(2), Page: models.Int64Pointer(1)} + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) + + suite.FatalNoError(err) + suite.Equal(0, len(moves)) + }) +} + +func (suite *OrderServiceSuite) TestListOrdersWithEmptyFields() { + expectedOrder := factory.BuildOrder(suite.DB(), nil, nil) + waf := entitlements.NewWeightAllotmentFetcher() + expectedOrder.Entitlement = nil + expectedOrder.EntitlementID = nil + expectedOrder.Grade = nil + expectedOrder.OriginDutyLocation = nil + expectedOrder.OriginDutyLocationID = nil + suite.MustSave(&expectedOrder) + + move := factory.BuildMove(suite.DB(), []factory.Customization{ + { + Model: expectedOrder, + LinkOnly: true, + }, + }, nil) + // Only orders with shipments are returned, so we need to add a shipment + // to the move we just created + factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + { + Model: models.MTOShipment{ + Status: models.MTOShipmentStatusSubmitted, + }, + }, + }, nil) + // Add a second shipment to make sure we only return 1 order even if its + // move has more than one shipment + factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + { + Model: models.MTOShipment{ + Status: models.MTOShipmentStatusSubmitted, + }, + }, + }, nil) + + officeUser := factory.BuildOfficeUser(suite.DB(), nil, nil) + session := auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: officeUser.User.Roles, + OfficeUserID: officeUser.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } + + orderFetcher := NewOrderFetcher(waf) + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{PerPage: models.Int64Pointer(1), Page: models.Int64Pointer(1)}) + + suite.FatalNoError(err) + suite.Nil(moves) + +} + +func (suite *OrderServiceSuite) TestListOrdersWithPagination() { + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) + waf := entitlements.NewWeightAllotmentFetcher() + session := auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: officeUser.User.Roles, + OfficeUserID: officeUser.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } + + for i := 0; i < 2; i++ { + factory.BuildMoveWithShipment(suite.DB(), nil, nil) + } + + orderFetcher := NewOrderFetcher(waf) + params := services.ListOrderParams{Page: models.Int64Pointer(1), PerPage: models.Int64Pointer(1)} + moves, count, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) + + suite.NoError(err) + suite.Equal(1, len(moves)) + suite.Equal(2, count) + +} + +func (suite *OrderServiceSuite) TestListOrdersWithSortOrder() { + + // SET UP: Service Members for sorting by Service Member Last Name and Branch + // - We'll need two other service members to test the last name sort, Lea Spacemen and Leo Zephyer + serviceMemberFirstName := "Lea" + serviceMemberLastName := "Zephyer" + affiliation := models.AffiliationNAVY + edipi := "9999999999" + var officeUser models.OfficeUser + waf := entitlements.NewWeightAllotmentFetcher() + // SET UP: Dates for sorting by Requested Move Date + // - We want dates 2 and 3 to sandwich requestedMoveDate1 so we can test that the min() query is working + requestedMoveDate1 := time.Date(testdatagen.GHCTestYear, 02, 20, 0, 0, 0, 0, time.UTC) + requestedMoveDate2 := time.Date(testdatagen.GHCTestYear, 03, 03, 0, 0, 0, 0, time.UTC) + requestedMoveDate3 := time.Date(testdatagen.GHCTestYear, 01, 15, 0, 0, 0, 0, time.UTC) + + setupTestData := func() (models.Move, models.Move, auth.Session) { + + // CREATE EXPECTED MOVES + expectedMove1 := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { // Default New Duty Location name is Fort Eisenhower + Model: models.Move{ + Status: models.MoveStatusAPPROVED, + Locator: "AA1234", + }, + }, + { + Model: models.MTOShipment{ + RequestedPickupDate: &requestedMoveDate1, + }, + }, + }, nil) + expectedMove2 := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Locator: "TTZ123", + }, + }, + { + Model: models.ServiceMember{ + Affiliation: &affiliation, + FirstName: &serviceMemberFirstName, + Edipi: &edipi, + }, + }, + { + Model: models.MTOShipment{ + RequestedPickupDate: &requestedMoveDate2, + }, + }, + }, nil) + // Create a second shipment so we can test min() sort + factory.BuildMTOShipmentWithMove(&expectedMove2, suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + RequestedPickupDate: &requestedMoveDate3, + }, + }, + }, nil) + officeUser = factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) + session := auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: officeUser.User.Roles, + OfficeUserID: officeUser.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } + + return expectedMove1, expectedMove2, session + } + + orderFetcher := NewOrderFetcher(waf) + + suite.Run("Sort by locator code", func() { + expectedMove1, expectedMove2, session := setupTestData() + params := services.ListOrderParams{Sort: models.StringPointer("locator"), Order: models.StringPointer("asc")} + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) + suite.NoError(err) + suite.Equal(2, len(moves)) + suite.Equal(expectedMove1.Locator, moves[0].Locator) + suite.Equal(expectedMove2.Locator, moves[1].Locator) + + params = services.ListOrderParams{Sort: models.StringPointer("locator"), Order: models.StringPointer("desc")} + moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) + suite.NoError(err) + suite.Equal(2, len(moves)) + suite.Equal(expectedMove2.Locator, moves[0].Locator) + suite.Equal(expectedMove1.Locator, moves[1].Locator) + }) + + suite.Run("Sort by move status", func() { + expectedMove1, expectedMove2, session := setupTestData() + params := services.ListOrderParams{Sort: models.StringPointer("status"), Order: models.StringPointer("asc")} + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) + suite.NoError(err) + suite.Equal(2, len(moves)) + suite.Equal(expectedMove1.Status, moves[0].Status) + suite.Equal(expectedMove2.Status, moves[1].Status) + + params = services.ListOrderParams{Sort: models.StringPointer("status"), Order: models.StringPointer("desc")} + moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) + suite.NoError(err) + suite.Equal(2, len(moves)) + suite.Equal(expectedMove2.Status, moves[0].Status) + suite.Equal(expectedMove1.Status, moves[1].Status) + }) + + suite.Run("Sort by service member affiliations", func() { + expectedMove1, expectedMove2, session := setupTestData() + params := services.ListOrderParams{Sort: models.StringPointer("branch"), Order: models.StringPointer("asc")} + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) + suite.NoError(err) + suite.Equal(2, len(moves)) + suite.Equal(*expectedMove1.Orders.ServiceMember.Affiliation, *moves[0].Orders.ServiceMember.Affiliation) + suite.Equal(*expectedMove2.Orders.ServiceMember.Affiliation, *moves[1].Orders.ServiceMember.Affiliation) + + params = services.ListOrderParams{Sort: models.StringPointer("branch"), Order: models.StringPointer("desc")} + moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) + suite.NoError(err) + suite.Equal(2, len(moves)) + suite.Equal(*expectedMove2.Orders.ServiceMember.Affiliation, *moves[0].Orders.ServiceMember.Affiliation) + suite.Equal(*expectedMove1.Orders.ServiceMember.Affiliation, *moves[1].Orders.ServiceMember.Affiliation) + }) + + suite.Run("Sort by request move date", func() { + _, _, session := setupTestData() + params := services.ListOrderParams{Sort: models.StringPointer("requestedMoveDate"), Order: models.StringPointer("asc")} + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) + suite.NoError(err) + suite.Equal(2, len(moves)) + suite.Equal(2, len(moves[0].MTOShipments)) // the move with two shipments has the earlier date + suite.Equal(1, len(moves[1].MTOShipments)) + // NOTE: You have to use Jan 02, 2006 as the example for date/time formatting in Go + suite.Equal(requestedMoveDate1.Format("2006/01/02"), moves[1].MTOShipments[0].RequestedPickupDate.Format("2006/01/02")) + + params = services.ListOrderParams{Sort: models.StringPointer("requestedMoveDate"), Order: models.StringPointer("desc")} + moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) + suite.NoError(err) + suite.Equal(2, len(moves)) + suite.Equal(1, len(moves[0].MTOShipments)) // the move with one shipment should be first + suite.Equal(2, len(moves[1].MTOShipments)) + suite.Equal(requestedMoveDate1.Format("2006/01/02"), moves[0].MTOShipments[0].RequestedPickupDate.Format("2006/01/02")) + }) + + suite.Run("Sort by submitted date (appearedInTooAt) in TOO queue ", func() { + // Scenario: In order to sort the moves the submitted_at, service_counseling_completed_at, and approvals_requested_at are checked to which are the minimum + // Expected: The moves appear in the order they are created below + officeUser = factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) + session := auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: officeUser.User.Roles, + OfficeUserID: officeUser.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } + now := time.Now() + oneWeekAgo := now.AddDate(0, 0, -7) + move1 := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + SubmittedAt: &oneWeekAgo, + }, + }, + }, nil) + move2 := factory.BuildApprovalsRequestedMove(suite.DB(), nil, nil) + factory.BuildMTOShipmentWithMove(&move2, suite.DB(), nil, nil) + move3 := factory.BuildServiceCounselingCompletedMove(suite.DB(), nil, nil) + factory.BuildMTOShipmentWithMove(&move3, suite.DB(), nil, nil) + + params := services.ListOrderParams{Sort: models.StringPointer("appearedInTooAt"), Order: models.StringPointer("asc")} + + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) + suite.NoError(err) + suite.Equal(3, len(moves)) + suite.Equal(moves[0].ID, move1.ID) + suite.Equal(moves[1].ID, move2.ID) + suite.Equal(moves[2].ID, move3.ID) + }) + + // MUST BE LAST, ADDS EXTRA MOVE + suite.Run("Sort by service member last name", func() { + _, _, session := setupTestData() + + // Last name sort is the only one that needs 3 moves for a complete test, so add that here at the end + factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.ServiceMember{ // Leo Zephyer + LastName: &serviceMemberLastName, + }, + }, + }, nil) + params := services.ListOrderParams{Sort: models.StringPointer("customerName"), Order: models.StringPointer("asc")} + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) + + suite.NoError(err) + suite.Equal(3, len(moves)) + suite.Equal("Spacemen, Lea", *moves[0].Orders.ServiceMember.LastName+", "+*moves[0].Orders.ServiceMember.FirstName) + suite.Equal("Spacemen, Leo", *moves[1].Orders.ServiceMember.LastName+", "+*moves[1].Orders.ServiceMember.FirstName) + suite.Equal("Zephyer, Leo", *moves[2].Orders.ServiceMember.LastName+", "+*moves[2].Orders.ServiceMember.FirstName) + + params = services.ListOrderParams{Sort: models.StringPointer("customerName"), Order: models.StringPointer("desc")} + moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) + + suite.NoError(err) + suite.Equal(3, len(moves)) + suite.Equal("Zephyer, Leo", *moves[0].Orders.ServiceMember.LastName+", "+*moves[0].Orders.ServiceMember.FirstName) + suite.Equal("Spacemen, Leo", *moves[1].Orders.ServiceMember.LastName+", "+*moves[1].Orders.ServiceMember.FirstName) + suite.Equal("Spacemen, Lea", *moves[2].Orders.ServiceMember.LastName+", "+*moves[2].Orders.ServiceMember.FirstName) + }) +} + +func getTransportationOffice(suite *OrderServiceSuite, name string) models.TransportationOffice { + trasportationOffice := factory.BuildTransportationOffice(suite.DB(), []factory.Customization{ + { + Model: models.TransportationOffice{ + Name: name, + }, + }}, nil) + return trasportationOffice +} + +func getPPMShipmentWithCloseoutOfficeNeedsCloseout(suite *OrderServiceSuite, closeoutOffice models.TransportationOffice) models.PPMShipment { + ppm := factory.BuildPPMShipmentThatNeedsCloseout(suite.DB(), nil, []factory.Customization{ + { + Model: closeoutOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CloseoutOffice, + }, + }) + return ppm +} + +func (suite *OrderServiceSuite) TestListOrdersNeedingServicesCounselingWithPPMCloseoutColumnsSort() { + defaultShipmentPickupPostalCode := "90210" + waf := entitlements.NewWeightAllotmentFetcher() + setupTestData := func() models.OfficeUser { + // Make an office user → GBLOC X + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) + factory.FetchOrBuildPostalCodeToGBLOC(suite.DB(), "50309", officeUser.TransportationOffice.Gbloc) + + // Ensure there's an entry connecting the default shipment pickup postal code with the office user's gbloc + factory.FetchOrBuildPostalCodeToGBLOC(suite.DB(), + defaultShipmentPickupPostalCode, + officeUser.TransportationOffice.Gbloc) + + return officeUser + } + orderFetcher := NewOrderFetcher(waf) + + var session auth.Session + + suite.Run("Sort by PPM closeout initiated", func() { + officeUser := setupTestData() + // Create a PPM submitted on April 1st + closeoutInitiatedDate1 := time.Date(2022, 04, 01, 0, 0, 0, 0, time.UTC) + closeoutOffice := factory.BuildTransportationOffice(suite.DB(), []factory.Customization{ + { + Model: models.TransportationOffice{Gbloc: "KKFA"}, + }, + }, nil) + + ppm1 := factory.BuildPPMShipmentThatNeedsCloseout(suite.DB(), nil, []factory.Customization{ + { + Model: models.PPMShipment{ + SubmittedAt: &closeoutInitiatedDate1, + }, + }, + { + Model: closeoutOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CloseoutOffice, + }, + }) + + // Create a PPM submitted on April 2nd + closeoutInitiatedDate2 := time.Date(2022, 04, 02, 0, 0, 0, 0, time.UTC) + ppm2 := factory.BuildPPMShipmentThatNeedsCloseout(suite.DB(), nil, []factory.Customization{ + { + Model: models.PPMShipment{ + SubmittedAt: &closeoutInitiatedDate2, + }, + }, + { + Model: closeoutOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CloseoutOffice, + }, + }) + + // Sort by closeout initiated date (ascending) + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{ + NeedsPPMCloseout: models.BoolPointer(true), + Sort: models.StringPointer("closeoutInitiated"), + Order: models.StringPointer("asc"), + }) + + suite.FatalNoError(err) + suite.Equal(2, len(moves)) + suite.Equal(ppm1.Shipment.MoveTaskOrder.Locator, moves[0].Locator) + suite.Equal(ppm2.Shipment.MoveTaskOrder.Locator, moves[1].Locator) + + // Sort by closeout initiated date (descending) + moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{ + NeedsPPMCloseout: models.BoolPointer(true), + Sort: models.StringPointer("closeoutInitiated"), + Order: models.StringPointer("desc"), + }) + + suite.FatalNoError(err) + suite.Equal(2, len(moves)) + suite.Equal(ppm2.Shipment.MoveTaskOrder.Locator, moves[0].Locator) + suite.Equal(ppm1.Shipment.MoveTaskOrder.Locator, moves[1].Locator) + }) + + suite.Run("Sort by PPM closeout location", func() { + officeUser := setupTestData() + + locationA := getTransportationOffice(suite, "A") + ppmShipmentA := getPPMShipmentWithCloseoutOfficeNeedsCloseout(suite, locationA) + + locationB := getTransportationOffice(suite, "B") + ppmShipmentB := getPPMShipmentWithCloseoutOfficeNeedsCloseout(suite, locationB) + + // Sort by closeout location (ascending) + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{ + NeedsPPMCloseout: models.BoolPointer(true), + Sort: models.StringPointer("closeoutLocation"), + Order: models.StringPointer("asc"), + }) + + suite.FatalNoError(err) + suite.Equal(2, len(moves)) + suite.Equal(ppmShipmentA.Shipment.MoveTaskOrder.Locator, moves[0].Locator) + suite.Equal(ppmShipmentB.Shipment.MoveTaskOrder.Locator, moves[1].Locator) + + // Sort by closeout location (descending) + moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{ + NeedsPPMCloseout: models.BoolPointer(true), + Sort: models.StringPointer("closeoutLocation"), + Order: models.StringPointer("desc"), + }) + + suite.FatalNoError(err) + suite.Equal(2, len(moves)) + suite.Equal(ppmShipmentB.Shipment.MoveTaskOrder.Locator, moves[0].Locator) + suite.Equal(ppmShipmentA.Shipment.MoveTaskOrder.Locator, moves[1].Locator) + }) + + suite.Run("Sort by destination duty location", func() { + officeUser := setupTestData() + + dutyLocationA := factory.BuildDutyLocation(suite.DB(), []factory.Customization{ + { + Model: models.DutyLocation{ + Name: "A", + }, + }, + }, nil) + closeoutOffice := factory.BuildTransportationOffice(suite.DB(), []factory.Customization{ + { + Model: models.TransportationOffice{Gbloc: "KKFA"}, + }, + }, nil) + + ppmShipmentA := factory.BuildPPMShipmentThatNeedsCloseout(suite.DB(), nil, []factory.Customization{ + { + Model: dutyLocationA, + LinkOnly: true, + Type: &factory.DutyLocations.NewDutyLocation, + }, + { + Model: closeoutOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CloseoutOffice, + }, + }) + dutyLocationB := factory.BuildDutyLocation(suite.DB(), []factory.Customization{ + { + Model: models.DutyLocation{ + Name: "B", + }, + }, + }, nil) + ppmShipmentB := factory.BuildPPMShipmentThatNeedsCloseout(suite.DB(), nil, []factory.Customization{ + { + Model: dutyLocationB, + LinkOnly: true, + Type: &factory.DutyLocations.NewDutyLocation, + }, + { + Model: closeoutOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CloseoutOffice, + }, + }) + + // Sort by destination duty location (ascending) + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{ + NeedsPPMCloseout: models.BoolPointer(true), + Sort: models.StringPointer("destinationDutyLocation"), + Order: models.StringPointer("asc"), + }) + + suite.FatalNoError(err) + suite.Equal(2, len(moves)) + suite.Equal(ppmShipmentA.Shipment.MoveTaskOrder.Locator, moves[0].Locator) + suite.Equal(ppmShipmentB.Shipment.MoveTaskOrder.Locator, moves[1].Locator) + + // Sort by destination duty location (descending) + moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{ + NeedsPPMCloseout: models.BoolPointer(true), + Sort: models.StringPointer("destinationDutyLocation"), + Order: models.StringPointer("desc"), + }) + + suite.FatalNoError(err) + suite.Equal(2, len(moves)) + suite.Equal(ppmShipmentB.Shipment.MoveTaskOrder.Locator, moves[0].Locator) + suite.Equal(ppmShipmentA.Shipment.MoveTaskOrder.Locator, moves[1].Locator) + }) + + suite.Run("Sort by PPM type (full or partial)", func() { + officeUser := setupTestData() + closeoutOffice := factory.BuildTransportationOffice(suite.DB(), []factory.Customization{ + { + Model: models.TransportationOffice{Gbloc: "KKFA"}, + }, + }, nil) + ppmShipmentPartial := factory.BuildPPMShipmentThatNeedsCloseout(suite.DB(), nil, []factory.Customization{ + { + Model: models.Move{ + PPMType: models.StringPointer("Partial"), + }, + }, + { + Model: closeoutOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CloseoutOffice, + }, + }) + ppmShipmentFull := factory.BuildPPMShipmentThatNeedsCloseout(suite.DB(), nil, []factory.Customization{ + { + Model: models.Move{ + PPMType: models.StringPointer("FULL"), + }, + }, + { + Model: closeoutOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CloseoutOffice, + }, + }) + + // Sort by PPM type (ascending) + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{ + NeedsPPMCloseout: models.BoolPointer(true), + Sort: models.StringPointer("ppmType"), + Order: models.StringPointer("asc"), + }) + + suite.FatalNoError(err) + suite.Equal(2, len(moves)) + suite.Equal(ppmShipmentFull.Shipment.MoveTaskOrder.Locator, moves[0].Locator) + suite.Equal(ppmShipmentPartial.Shipment.MoveTaskOrder.Locator, moves[1].Locator) + + // Sort by PPM type (descending) + moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{ + NeedsPPMCloseout: models.BoolPointer(true), + Sort: models.StringPointer("ppmType"), + Order: models.StringPointer("desc"), + }) + + suite.FatalNoError(err) + suite.Equal(2, len(moves)) + suite.Equal(ppmShipmentPartial.Shipment.MoveTaskOrder.Locator, moves[0].Locator) + suite.Equal(ppmShipmentFull.Shipment.MoveTaskOrder.Locator, moves[1].Locator) + }) + suite.Run("Sort by PPM status", func() { + officeUser := setupTestData() + closeoutOffice := factory.BuildTransportationOffice(suite.DB(), []factory.Customization{ + { + Model: models.TransportationOffice{Gbloc: "KKFA"}, + }, + }, nil) + ppmShipmentNeedsCloseout := getPPMShipmentWithCloseoutOfficeNeedsCloseout(suite, closeoutOffice) + + // Sort by PPM type (ascending) + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{ + NeedsPPMCloseout: models.BoolPointer(true), + Sort: models.StringPointer("ppmStatus"), + Order: models.StringPointer("asc"), + }) + + suite.FatalNoError(err) + suite.Equal(1, len(moves)) + suite.Equal(ppmShipmentNeedsCloseout.Status, moves[0].MTOShipments[0].PPMShipment.Status) + + // Sort by PPM type (descending) + moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{ + NeedsPPMCloseout: models.BoolPointer(true), + Sort: models.StringPointer("ppmStatus"), + Order: models.StringPointer("desc"), + }) + + suite.FatalNoError(err) + suite.Equal(1, len(moves)) + suite.Equal(ppmShipmentNeedsCloseout.Status, moves[0].MTOShipments[0].PPMShipment.Status) + }) +} + +func (suite *OrderServiceSuite) TestListOrdersNeedingServicesCounselingWithGBLOCSortFilter() { + waf := entitlements.NewWeightAllotmentFetcher() + suite.Run("Filter by origin GBLOC", func() { + + // TESTCASE SCENARIO + // Under test: OrderFetcher.ListOrders function + // Mocked: None + // Set up: We create 2 moves with different GBLOCs, KKFA and ZANY. Both moves require service counseling + // We create an office user with the GBLOC KKFA + // Then we request a list of moves sorted by GBLOC, ascending for service counseling + // Expected outcome: + // We expect only the move that matches the counselors GBLOC - aka the KKFA move. + + // Create a services counselor (default GBLOC is KKFA) + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeServicesCounselor}) + session := auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: officeUser.User.Roles, + OfficeUserID: officeUser.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } + + // Create a move with Origin KKFA, needs service couseling + kkfaMove := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusNeedsServiceCounseling, + }, + }, + }, nil) + // Create data for a second Origin ZANY + dutyLocationAddress2 := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "Anchor 1212", + City: "Fort Eisenhower", + State: "GA", + PostalCode: "89898", + }, + }, + }, nil) + + factory.FetchOrBuildPostalCodeToGBLOC(suite.DB(), dutyLocationAddress2.PostalCode, "ZANY") + originDutyLocation2 := factory.BuildDutyLocation(suite.DB(), []factory.Customization{ + { + Model: models.DutyLocation{ + Name: "Fort Sam Snap", + }, + }, + { + Model: dutyLocationAddress2, + LinkOnly: true, + }, + }, nil) + + // Create a second move from the ZANY gbloc + factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusNeedsServiceCounseling, + Locator: "ZZ1234", + }, + }, + { + Model: originDutyLocation2, + LinkOnly: true, + Type: &factory.DutyLocations.OriginDutyLocation, + }, + }, nil) + // Setup and run the function under test requesting status NEEDS SERVICE COUNSELING + orderFetcher := NewOrderFetcher(waf) + statuses := []string{"NEEDS SERVICE COUNSELING"} + // Sort by origin GBLOC, filter by status + params := services.ListOrderParams{Sort: models.StringPointer("originGBLOC"), Order: models.StringPointer("asc"), Status: statuses} + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeServicesCounselor, ¶ms) + + // Expect only LKNQ move to be returned + suite.NoError(err) + suite.Equal(1, len(moves)) + suite.Equal(kkfaMove.ID, moves[0].ID) + }) +} + +func (suite *OrderServiceSuite) TestListOrdersForTOOWithNTSRelease() { + // Make an NTS-Release shipment (and a move). Should not have a pickup address. + factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + ShipmentType: models.MTOShipmentTypeHHGOutOfNTS, + }, + }, + }, nil) + waf := entitlements.NewWeightAllotmentFetcher() + // Make a TOO user and the postal code to GBLOC link. + tooOfficeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) + session := auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: tooOfficeUser.User.Roles, + OfficeUserID: tooOfficeUser.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } + + orderFetcher := NewOrderFetcher(waf) + moves, moveCount, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), tooOfficeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{}) + + suite.FatalNoError(err) + suite.Equal(1, moveCount) + suite.Len(moves, 1) +} + +func (suite *OrderServiceSuite) TestListOrdersForTOOWithPPM() { + postalCode := "50309" + partialPPMType := models.MovePPMTypePARTIAL + waf := entitlements.NewWeightAllotmentFetcher() + ppmShipment := factory.BuildPPMShipment(suite.DB(), []factory.Customization{ + { + Model: models.Order{ + ID: uuid.UUID{uuid.V4}, + }, + }, + { + Model: models.Move{ + Status: models.MoveStatusAPPROVED, + PPMType: &partialPPMType, + }, + }, + { + Model: models.Address{ + PostalCode: postalCode, + }, + Type: &factory.Addresses.PickupAddress, + }, + }, nil) + // Make a TOO user. + tooOfficeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) + session := auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: tooOfficeUser.User.Roles, + OfficeUserID: tooOfficeUser.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } + + // GBLOC for the below doesn't really matter, it just means the query for the moves passes the inner join in ListOrders + factory.FetchOrBuildPostalCodeToGBLOC(suite.DB(), ppmShipment.PickupAddress.PostalCode, tooOfficeUser.TransportationOffice.Gbloc) + + orderFetcher := NewOrderFetcher(waf) + moves, moveCount, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), tooOfficeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{}) + suite.FatalNoError(err) + suite.Equal(1, moveCount) + suite.Len(moves, 1) +} + +func (suite *OrderServiceSuite) TestListOrdersWithViewAsGBLOCParam() { + var hqOfficeUser models.OfficeUser + var hqOfficeUserAGFM models.OfficeUser + waf := entitlements.NewWeightAllotmentFetcher() + requestedMoveDate1 := time.Date(testdatagen.GHCTestYear, 02, 20, 0, 0, 0, 0, time.UTC) + requestedMoveDate2 := time.Date(testdatagen.GHCTestYear, 03, 03, 0, 0, 0, 0, time.UTC) + + setupTestData := func() (models.Move, models.Move, models.MTOShipment, auth.Session, auth.Session) { + // CREATE EXPECTED MOVES + expectedMove1 := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { // Default New Duty Location name is Fort Eisenhower + Model: models.Move{ + Status: models.MoveStatusAPPROVED, + Locator: "AA1234", + }, + }, + { + Model: models.MTOShipment{ + RequestedPickupDate: &requestedMoveDate1, + }, + }, + }, nil) + expectedMove2 := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Locator: "TTZ123", + }, + }, + { + Model: models.MTOShipment{ + RequestedPickupDate: &requestedMoveDate2, + }, + }, + }, nil) + + factory.FetchOrBuildPostalCodeToGBLOC(suite.DB(), "06001", "AGFM") + + expectedShipment3 := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: models.TransportationOffice{ + Name: "Fort Punxsutawney", + Gbloc: "AGFM", + }, + }, + { + Model: models.MTOShipment{ + Status: models.MTOShipmentStatusSubmitted, + }, + }, + { + Model: models.Address{ + PostalCode: "06001", + }, + Type: &factory.Addresses.PickupAddress, + }, + }, nil) + + hqOfficeUser = factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeHQ}) + hqSession := auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: hqOfficeUser.User.Roles, + OfficeUserID: hqOfficeUser.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } + + hqOfficeUserAGFM = factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + { + Model: models.TransportationOffice{ + Name: "Scott AFB", + Gbloc: "AGFM", + }, + }, + }, []roles.RoleType{roles.RoleTypeHQ}) + hqSessionAGFM := auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: hqOfficeUserAGFM.User.Roles, + OfficeUserID: hqOfficeUserAGFM.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } + + return expectedMove1, expectedMove2, expectedShipment3, hqSession, hqSessionAGFM + } + + orderFetcher := NewOrderFetcher(waf) + + suite.Run("Sort by locator code", func() { + expectedMove1, expectedMove2, expectedShipment3, hqSession, hqSessionAGFM := setupTestData() + + // Request as an HQ user with their default GBLOC, KKFA + params := services.ListOrderParams{Sort: models.StringPointer("locator"), Order: models.StringPointer("asc")} + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&hqSession), hqOfficeUser.ID, roles.RoleTypeTOO, ¶ms) + suite.NoError(err) + suite.Equal(2, len(moves)) + suite.Equal(expectedMove1.Locator, moves[0].Locator) + suite.Equal(expectedMove2.Locator, moves[1].Locator) + + // Expect the same results with a ViewAsGBLOC that equals the user's default GBLOC, KKFA + params = services.ListOrderParams{Sort: models.StringPointer("locator"), Order: models.StringPointer("asc"), ViewAsGBLOC: models.StringPointer("KKFA")} + moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&hqSession), hqOfficeUser.ID, roles.RoleTypeTOO, ¶ms) + suite.NoError(err) + suite.Equal(2, len(moves)) + suite.Equal(expectedMove1.Locator, moves[0].Locator) + suite.Equal(expectedMove2.Locator, moves[1].Locator) + + // Expect the AGFM move when using the ViewAsGBLOC param set to AGFM + params = services.ListOrderParams{ViewAsGBLOC: models.StringPointer("AGFM")} + moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&hqSession), hqOfficeUser.ID, roles.RoleTypeTOO, ¶ms) + suite.NoError(err) + suite.Equal(1, len(moves)) + suite.Equal(expectedShipment3.ID, moves[0].MTOShipments[0].ID) + + // Expect the same results without a ViewAsGBLOC for a user whose default GBLOC is AGFM + params = services.ListOrderParams{} + moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&hqSessionAGFM), hqOfficeUserAGFM.ID, roles.RoleTypeTOO, ¶ms) + suite.NoError(err) + suite.Equal(1, len(moves)) + suite.Equal(expectedShipment3.ID, moves[0].MTOShipments[0].ID) + }) +} + +func (suite *OrderServiceSuite) TestListOrdersForTOOWithPPMWithDeletedShipment() { + postalCode := "50309" + deletedAt := time.Now() + waf := entitlements.NewWeightAllotmentFetcher() + move := factory.BuildMove(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusSUBMITTED, + }, + }, + }, nil) + ppmShipment := factory.BuildPPMShipment(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + PostalCode: postalCode, + }, + Type: &factory.Addresses.PickupAddress, + }, + }, nil) + factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + { + Model: models.MTOShipment{ + Status: models.MTOShipmentStatusSubmitted, + DeletedAt: &deletedAt, + }, + }, + { + Model: ppmShipment, + LinkOnly: true, + }, + }, nil) + + // Make a TOO user. + tooOfficeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) + session := auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: tooOfficeUser.User.Roles, + OfficeUserID: tooOfficeUser.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } + + orderFetcher := NewOrderFetcher(waf) + moves, moveCount, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), tooOfficeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{Status: []string{string(models.MoveStatusSUBMITTED)}}) + suite.FatalNoError(err) + suite.Equal(0, moveCount) + suite.Len(moves, 0) +} + +func (suite *OrderServiceSuite) TestListOrdersForTOOWithPPMWithOneDeletedShipmentButOtherExists() { + postalCode := "50309" + deletedAt := time.Now() + waf := entitlements.NewWeightAllotmentFetcher() + move := factory.BuildMove(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusAPPROVED, + }, + }, + }, nil) + // This shipment is created first, but later deleted + ppmShipment1 := factory.BuildPPMShipment(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + { + Model: models.PPMShipment{ + CreatedAt: time.Now(), + }, + }, + { + Model: models.Address{ + PostalCode: postalCode, + }, + Type: &factory.Addresses.PickupAddress, + }, + }, nil) + // This shipment is created after the first one, but not deleted + factory.BuildPPMShipment(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + { + Model: models.PPMShipment{ + CreatedAt: time.Now().Add(time.Minute * time.Duration(1)), + }, + }, + { + Model: models.Address{ + PostalCode: postalCode, + }, + Type: &factory.Addresses.PickupAddress, + }, + }, nil) + factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + { + Model: models.MTOShipment{ + Status: models.MTOShipmentStatusSubmitted, + DeletedAt: &deletedAt, + }, + }, + { + Model: ppmShipment1, + LinkOnly: true, + }, + }, nil) + + // Make a TOO user and the postal code to GBLOC link. + tooOfficeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) + session := auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: tooOfficeUser.User.Roles, + OfficeUserID: tooOfficeUser.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } + + orderFetcher := NewOrderFetcher(waf) + moves, moveCount, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), tooOfficeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{}) + suite.FatalNoError(err) + suite.Equal(1, moveCount) + suite.Len(moves, 1) +} + +func (suite *OrderServiceSuite) TestListAllOrderLocations() { + waf := entitlements.NewWeightAllotmentFetcher() + suite.Run("returns a list of all order locations in the current users queue", func() { + orderFetcher := NewOrderFetcher(waf) + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeServicesCounselor}) + session := auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: officeUser.User.Roles, + OfficeUserID: officeUser.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } + + params := services.ListOrderParams{} + moves, err := orderFetcher.ListAllOrderLocations(suite.AppContextWithSessionForTest(&session), officeUser.ID, ¶ms) + + suite.FatalNoError(err) + suite.Equal(0, len(moves)) + }) +} + +func (suite *OrderServiceSuite) TestListOrdersFilteredByCustomerName() { + serviceMemberFirstName := "Margaret" + serviceMemberLastName := "Starlight" + edipi := "9999999998" + var officeUser models.OfficeUser + var session auth.Session + waf := entitlements.NewWeightAllotmentFetcher() + requestedMoveDate1 := time.Date(testdatagen.GHCTestYear, 05, 20, 0, 0, 0, 0, time.UTC) + requestedMoveDate2 := time.Date(testdatagen.GHCTestYear, 07, 03, 0, 0, 0, 0, time.UTC) + + setupData := func() { + factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusAPPROVED, + Locator: "AA1235", + }, + }, + { + Model: models.MTOShipment{ + RequestedPickupDate: &requestedMoveDate1, + }, + }, + }, nil) + factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Locator: "TTZ125", + }, + }, { - Model: models.DutyLocation{ - Name: "B", + Model: models.ServiceMember{ + FirstName: &serviceMemberFirstName, + Edipi: &edipi, }, }, - }, nil) - ppmShipmentB := factory.BuildPPMShipmentThatNeedsCloseout(suite.DB(), nil, []factory.Customization{ { - Model: dutyLocationB, - LinkOnly: true, - Type: &factory.DutyLocations.NewDutyLocation, + Model: models.MTOShipment{ + RequestedPickupDate: &requestedMoveDate2, + }, }, + }, nil) + factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ { - Model: closeoutOffice, - LinkOnly: true, - Type: &factory.TransportationOffices.CloseoutOffice, + Model: models.ServiceMember{ // Leo Zephyer + LastName: &serviceMemberLastName, + }, }, - }) + }, nil) + officeUser = factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) + session = auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: officeUser.User.Roles, + OfficeUserID: officeUser.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } + } - // Sort by destination duty location (ascending) - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{ - NeedsPPMCloseout: models.BoolPointer(true), - Sort: models.StringPointer("destinationDutyLocation"), - Order: models.StringPointer("asc"), - }) + orderFetcher := NewOrderFetcher(waf) - suite.FatalNoError(err) + suite.Run("list moves by customer name - full name (last, first)", func() { + setupData() + // Search "Spacemen, Margaret" + params := services.ListOrderParams{CustomerName: models.StringPointer("Spacemen, Margaret"), Sort: models.StringPointer("customerName"), Order: models.StringPointer("asc")} + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) + suite.NoError(err) + suite.Equal(1, len(moves)) + suite.Equal("Spacemen, Margaret", *moves[0].Orders.ServiceMember.LastName+", "+*moves[0].Orders.ServiceMember.FirstName) + }) + + suite.Run("list moves by customer name - full name (first last)", func() { + setupData() + // Search "Margaret Spacemen" + params := services.ListOrderParams{CustomerName: models.StringPointer("Margaret Spacemen"), Sort: models.StringPointer("customerName"), Order: models.StringPointer("asc")} + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) + suite.NoError(err) + suite.Equal(1, len(moves)) + suite.Equal("Spacemen, Margaret", *moves[0].Orders.ServiceMember.LastName+", "+*moves[0].Orders.ServiceMember.FirstName) + }) + + suite.Run("list moves by customer name - partial last (multiple)", func() { + setupData() + // Search "space" + params := services.ListOrderParams{CustomerName: models.StringPointer("space"), Sort: models.StringPointer("customerName"), Order: models.StringPointer("asc")} + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) + suite.NoError(err) suite.Equal(2, len(moves)) - suite.Equal(ppmShipmentA.Shipment.MoveTaskOrder.Locator, moves[0].Locator) - suite.Equal(ppmShipmentB.Shipment.MoveTaskOrder.Locator, moves[1].Locator) + suite.Equal("Spacemen, Leo", *moves[0].Orders.ServiceMember.LastName+", "+*moves[0].Orders.ServiceMember.FirstName) + suite.Equal("Spacemen, Margaret", *moves[1].Orders.ServiceMember.LastName+", "+*moves[1].Orders.ServiceMember.FirstName) + }) - // Sort by destination duty location (descending) - moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{ - NeedsPPMCloseout: models.BoolPointer(true), - Sort: models.StringPointer("destinationDutyLocation"), - Order: models.StringPointer("desc"), - }) + suite.Run("list moves by customer name - partial last (single)", func() { + setupData() + // Search "Light" + params := services.ListOrderParams{CustomerName: models.StringPointer("Light"), Sort: models.StringPointer("customerName"), Order: models.StringPointer("asc")} + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) + suite.NoError(err) + suite.Equal(1, len(moves)) + suite.Equal("Starlight, Leo", *moves[0].Orders.ServiceMember.LastName+", "+*moves[0].Orders.ServiceMember.FirstName) + }) - suite.FatalNoError(err) + suite.Run("list moves by customer name - partial first", func() { + setupData() + // Search "leo" + params := services.ListOrderParams{CustomerName: models.StringPointer("leo"), Sort: models.StringPointer("customerName"), Order: models.StringPointer("asc")} + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) + suite.NoError(err) suite.Equal(2, len(moves)) - suite.Equal(ppmShipmentB.Shipment.MoveTaskOrder.Locator, moves[0].Locator) - suite.Equal(ppmShipmentA.Shipment.MoveTaskOrder.Locator, moves[1].Locator) + suite.Equal("Spacemen, Leo", *moves[0].Orders.ServiceMember.LastName+", "+*moves[0].Orders.ServiceMember.FirstName) + suite.Equal("Starlight, Leo", *moves[1].Orders.ServiceMember.LastName+", "+*moves[1].Orders.ServiceMember.FirstName) }) - suite.Run("Sort by PPM type (full or partial)", func() { - officeUser := setupTestData() - closeoutOffice := factory.BuildTransportationOffice(suite.DB(), []factory.Customization{ - { - Model: models.TransportationOffice{Gbloc: "KKFA"}, - }, - }, nil) - ppmShipmentPartial := factory.BuildPPMShipmentThatNeedsCloseout(suite.DB(), nil, []factory.Customization{ + suite.Run("list moves by customer name - partial matching within first or last", func() { + setupData() + // Search "ar" + params := services.ListOrderParams{CustomerName: models.StringPointer("ar"), Sort: models.StringPointer("customerName"), Order: models.StringPointer("asc")} + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) + suite.NoError(err) + suite.Equal(2, len(moves)) + suite.Equal("Spacemen, Margaret", *moves[0].Orders.ServiceMember.LastName+", "+*moves[0].Orders.ServiceMember.FirstName) + suite.Equal("Starlight, Leo", *moves[1].Orders.ServiceMember.LastName+", "+*moves[1].Orders.ServiceMember.FirstName) + }) + + suite.Run("list moves by customer name - empty", func() { + setupData() + // Search "johnny" + params := services.ListOrderParams{CustomerName: models.StringPointer("johnny"), Sort: models.StringPointer("customerName"), Order: models.StringPointer("asc")} + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) + suite.NoError(err) + suite.Equal(0, len(moves)) + }) +} + +func (suite *OrderServiceSuite) TestListAllOrderLocationsWithViewAsGBLOCParam() { + waf := entitlements.NewWeightAllotmentFetcher() + suite.Run("returns a list of all order locations in the current users queue", func() { + orderFetcher := NewOrderFetcher(waf) + officeUserFetcher := officeuserservice.NewOfficeUserFetcherPop() + movesContainOriginDutyLocation := func(moves models.Moves, keyword string) func() (success bool) { + return func() (success bool) { + for _, record := range moves { + if strings.Contains(record.Orders.OriginDutyLocation.Name, keyword) { + return true + } + } + return false + } + } + + // Create SC office user with a default transportation office in the AGFM GBLOC + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ { - Model: models.Move{ - PPMType: models.StringPointer("Partial"), + Model: models.TransportationOffice{ + Name: "Fort Punxsutawney", + Gbloc: "AGFM", }, }, + }, []roles.RoleType{roles.RoleTypeServicesCounselor}) + // Add a secondary GBLOC to the above office user, this should default to KKFA + factory.BuildAlternateTransportationOfficeAssignment(suite.DB(), []factory.Customization{ { - Model: closeoutOffice, + Model: models.OfficeUser{ + ID: officeUser.ID, + }, LinkOnly: true, - Type: &factory.TransportationOffices.CloseoutOffice, }, - }) - ppmShipmentFull := factory.BuildPPMShipmentThatNeedsCloseout(suite.DB(), nil, []factory.Customization{ + }, nil) + session := auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: officeUser.User.Roles, + OfficeUserID: officeUser.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } + + // Create two default moves with shipment, should be in KKFA and have the status SUBMITTED + KKFAMove1 := factory.BuildMoveWithShipment(suite.DB(), nil, nil) + KKFAMove2 := factory.BuildMoveWithShipment(suite.DB(), nil, nil) + + // Create third move with the same origin duty location as one of the above + KKFAMove3 := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ { - Model: models.Move{ - PPMType: models.StringPointer("FULL"), + Model: models.DutyLocation{ + ID: KKFAMove2.Orders.OriginDutyLocation.ID, }, - }, - { - Model: closeoutOffice, + Type: &factory.DutyLocations.OriginDutyLocation, LinkOnly: true, - Type: &factory.TransportationOffices.CloseoutOffice, }, - }) + }, nil) - // Sort by PPM type (ascending) - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{ - NeedsPPMCloseout: models.BoolPointer(true), - Sort: models.StringPointer("ppmType"), - Order: models.StringPointer("asc"), - }) + officeUser, _ = officeUserFetcher.FetchOfficeUserByIDWithTransportationOfficeAssignments(suite.AppContextForTest(), officeUser.ID) + + // Confirm office user has the desired transportation office assignments + suite.Equal("AGFM", officeUser.TransportationOffice.Gbloc) + if officeUser.TransportationOfficeAssignments[0].TransportationOffice.Gbloc == "AGFM" { + suite.Equal("AGFM", officeUser.TransportationOfficeAssignments[0].TransportationOffice.Gbloc) + suite.Equal(true, *officeUser.TransportationOfficeAssignments[0].PrimaryOffice) + suite.Equal("KKFA", officeUser.TransportationOfficeAssignments[1].TransportationOffice.Gbloc) + suite.Equal(false, *officeUser.TransportationOfficeAssignments[1].PrimaryOffice) + } else { + suite.Equal("KKFA", officeUser.TransportationOfficeAssignments[0].TransportationOffice.Gbloc) + suite.Equal(false, *officeUser.TransportationOfficeAssignments[0].PrimaryOffice) + suite.Equal("AGFM", officeUser.TransportationOfficeAssignments[1].TransportationOffice.Gbloc) + suite.Equal(true, *officeUser.TransportationOfficeAssignments[1].PrimaryOffice) + } + + // Confirm the factory created moves have the desired GBLOCS, 3x KKFA, + suite.Equal("KKFA", *KKFAMove1.Orders.OriginDutyLocationGBLOC) + suite.Equal("KKFA", *KKFAMove2.Orders.OriginDutyLocationGBLOC) + suite.Equal("KKFA", *KKFAMove3.Orders.OriginDutyLocationGBLOC) + + // Fetch and check secondary GBLOC + KKFA := "KKFA" + params := services.ListOrderParams{ + ViewAsGBLOC: &KKFA, + } + KKFAmoves, err := orderFetcher.ListAllOrderLocations(suite.AppContextWithSessionForTest(&session), officeUser.ID, ¶ms) suite.FatalNoError(err) - suite.Equal(2, len(moves)) - suite.Equal(ppmShipmentFull.Shipment.MoveTaskOrder.Locator, moves[0].Locator) - suite.Equal(ppmShipmentPartial.Shipment.MoveTaskOrder.Locator, moves[1].Locator) + // This value should be updated to 3 if ListAllOrderLocations is updated to return distinct locations + suite.Equal(3, len(KKFAmoves)) - // Sort by PPM type (descending) - moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{ - NeedsPPMCloseout: models.BoolPointer(true), - Sort: models.StringPointer("ppmType"), - Order: models.StringPointer("desc"), - }) + suite.Equal("KKFA", *KKFAmoves[0].Orders.OriginDutyLocationGBLOC) + suite.Equal("KKFA", *KKFAmoves[1].Orders.OriginDutyLocationGBLOC) + suite.Equal("KKFA", *KKFAmoves[2].Orders.OriginDutyLocationGBLOC) + + suite.Condition(movesContainOriginDutyLocation(KKFAmoves, KKFAMove1.Orders.OriginDutyLocation.Name), "Should contain first KKFA move's origin duty location") + suite.Condition(movesContainOriginDutyLocation(KKFAmoves, KKFAMove2.Orders.OriginDutyLocation.Name), "Should contain second KKFA move's origin duty location") + suite.Condition(movesContainOriginDutyLocation(KKFAmoves, KKFAMove3.Orders.OriginDutyLocation.Name), "Should contain third KKFA move's origin duty location") + }) +} + +func (suite *OrderServiceSuite) TestOriginDutyLocationFilter() { + var session auth.Session + waf := entitlements.NewWeightAllotmentFetcher() + var expectedMove models.Move + var officeUser models.OfficeUser + orderFetcher := NewOrderFetcher(waf) + setupTestData := func() (models.OfficeUser, models.Move, auth.Session) { + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) + session := auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: officeUser.User.Roles, + OfficeUserID: officeUser.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } + move := factory.BuildMoveWithShipment(suite.DB(), nil, nil) + return officeUser, move, session + } - suite.FatalNoError(err) - suite.Equal(2, len(moves)) - suite.Equal(ppmShipmentPartial.Shipment.MoveTaskOrder.Locator, moves[0].Locator) - suite.Equal(ppmShipmentFull.Shipment.MoveTaskOrder.Locator, moves[1].Locator) + suite.Run("Returns orders matching full originDutyLocation name filter", func() { + officeUser, expectedMove, session = setupTestData() + locationName := expectedMove.Orders.OriginDutyLocation.Name + expectedMoves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{OriginDutyLocation: strings.Split(locationName, " ")}) + suite.NoError(err) + suite.Equal(1, len(expectedMoves)) + suite.Equal(locationName, string(expectedMoves[0].Orders.OriginDutyLocation.Name)) }) - suite.Run("Sort by PPM status", func() { - officeUser := setupTestData() - closeoutOffice := factory.BuildTransportationOffice(suite.DB(), []factory.Customization{ - { - Model: models.TransportationOffice{Gbloc: "KKFA"}, - }, - }, nil) - ppmShipmentNeedsCloseout := getPPMShipmentWithCloseoutOfficeNeedsCloseout(suite, closeoutOffice) - // Sort by PPM type (ascending) - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{ - NeedsPPMCloseout: models.BoolPointer(true), - Sort: models.StringPointer("ppmStatus"), - Order: models.StringPointer("asc"), - }) + suite.Run("Returns orders matching partial originDutyLocation name filter", func() { + officeUser, expectedMove, session = setupTestData() + locationName := expectedMove.Orders.OriginDutyLocation.Name + //Split the location name and retrieve a substring (first string) for the search param + partialParamSearch := strings.Split(locationName, " ")[0] + expectedMoves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{OriginDutyLocation: strings.Split(partialParamSearch, " ")}) + suite.NoError(err) + suite.Equal(1, len(expectedMoves)) + suite.Equal(locationName, string(expectedMoves[0].Orders.OriginDutyLocation.Name)) + }) +} - suite.FatalNoError(err) - suite.Equal(1, len(moves)) - suite.Equal(ppmShipmentNeedsCloseout.Status, moves[0].MTOShipments[0].PPMShipment.Status) +func (suite *OrderServiceSuite) TestListDestinationRequestsOrders() { + army := models.AffiliationARMY + airForce := models.AffiliationAIRFORCE + spaceForce := models.AffiliationSPACEFORCE + usmc := models.AffiliationMARINES - // Sort by PPM type (descending) - moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{ - NeedsPPMCloseout: models.BoolPointer(true), - Sort: models.StringPointer("ppmStatus"), - Order: models.StringPointer("desc"), - }) + setupTestData := func(officeUserGBLOC string) (models.OfficeUser, auth.Session) { - suite.FatalNoError(err) - suite.Equal(1, len(moves)) - suite.Equal(ppmShipmentNeedsCloseout.Status, moves[0].MTOShipments[0].PPMShipment.Status) - }) -} + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + { + Model: models.TransportationOffice{ + Gbloc: officeUserGBLOC, + }, + }, + }, []roles.RoleType{roles.RoleTypeTOO}) -func (suite *OrderServiceSuite) TestListOrdersNeedingServicesCounselingWithGBLOCSortFilter() { - waf := entitlements.NewWeightAllotmentFetcher() - suite.Run("Filter by origin GBLOC", func() { + factory.FetchOrBuildPostalCodeToGBLOC(suite.DB(), "99501", officeUser.TransportationOffice.Gbloc) - // TESTCASE SCENARIO - // Under test: OrderFetcher.ListOrders function - // Mocked: None - // Set up: We create 2 moves with different GBLOCs, KKFA and ZANY. Both moves require service counseling - // We create an office user with the GBLOC KKFA - // Then we request a list of moves sorted by GBLOC, ascending for service counseling - // Expected outcome: - // We expect only the move that matches the counselors GBLOC - aka the KKFA move. + fetcher := &mocks.OfficeUserGblocFetcher{} + fetcher.On("FetchGblocForOfficeUser", + mock.AnythingOfType("*appcontext.appContext"), + officeUser.ID, + ).Return(officeUserGBLOC, nil) - // Create a services counselor (default GBLOC is KKFA) - officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeServicesCounselor}) session := auth.Session{ ApplicationName: auth.OfficeApp, Roles: officeUser.User.Roles, @@ -1626,684 +3060,461 @@ func (suite *OrderServiceSuite) TestListOrdersNeedingServicesCounselingWithGBLOC AccessToken: "fakeAccessToken", } - // Create a move with Origin KKFA, needs service couseling - kkfaMove := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + return officeUser, session + } + + buildMoveKKFA := func() (models.Move, models.MTOShipment) { + postalCode := "90210" + factory.FetchOrBuildPostalCodeToGBLOC(suite.DB(), "90210", "KKFA") + + // setting up two moves, each with requested destination SIT service items + destinationAddress := factory.BuildAddress(suite.DB(), []factory.Customization{ { - Model: models.Move{ - Status: models.MoveStatusNeedsServiceCounseling, - }, + Model: models.Address{PostalCode: postalCode}, }, }, nil) - // Create data for a second Origin ZANY - dutyLocationAddress2 := factory.BuildAddress(suite.DB(), []factory.Customization{ + + move := factory.BuildAvailableToPrimeMove(suite.DB(), []factory.Customization{ { - Model: models.Address{ - StreetAddress1: "Anchor 1212", - City: "Fort Eisenhower", - State: "GA", - PostalCode: "89898", + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + Show: models.BoolPointer(true), }, - }, - }, nil) + }}, nil) - factory.FetchOrBuildPostalCodeToGBLOC(suite.DB(), dutyLocationAddress2.PostalCode, "ZANY") - originDutyLocation2 := factory.BuildDutyLocation(suite.DB(), []factory.Customization{ + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ { - Model: models.DutyLocation{ - Name: "Fort Sam Snap", + Model: models.MTOShipment{ + Status: models.MTOShipmentStatusApproved, }, }, { - Model: dutyLocationAddress2, + Model: move, LinkOnly: true, }, - }, nil) - - // Create a second move from the ZANY gbloc - factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ - { - Model: models.Move{ - Status: models.MoveStatusNeedsServiceCounseling, - Locator: "ZZ1234", - }, - }, { - Model: originDutyLocation2, + Model: destinationAddress, LinkOnly: true, - Type: &factory.DutyLocations.OriginDutyLocation, }, }, nil) - // Setup and run the function under test requesting status NEEDS SERVICE COUNSELING - orderFetcher := NewOrderFetcher(waf) - statuses := []string{"NEEDS SERVICE COUNSELING"} - // Sort by origin GBLOC, filter by status - params := services.ListOrderParams{Sort: models.StringPointer("originGBLOC"), Order: models.StringPointer("asc"), Status: statuses} - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeServicesCounselor, ¶ms) - - // Expect only LKNQ move to be returned - suite.NoError(err) - suite.Equal(1, len(moves)) - suite.Equal(kkfaMove.ID, moves[0].ID) - }) -} -func (suite *OrderServiceSuite) TestListOrdersForTOOWithNTSRelease() { - // Make an NTS-Release shipment (and a move). Should not have a pickup address. - factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ - { - Model: models.MTOShipment{ - ShipmentType: models.MTOShipmentTypeHHGOutOfNTS, - }, - }, - }, nil) - waf := entitlements.NewWeightAllotmentFetcher() - // Make a TOO user and the postal code to GBLOC link. - tooOfficeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) - session := auth.Session{ - ApplicationName: auth.OfficeApp, - Roles: tooOfficeUser.User.Roles, - OfficeUserID: tooOfficeUser.ID, - IDToken: "fake_token", - AccessToken: "fakeAccessToken", + return move, shipment } - orderFetcher := NewOrderFetcher(waf) - moves, moveCount, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), tooOfficeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{}) - - suite.FatalNoError(err) - suite.Equal(1, moveCount) - suite.Len(moves, 1) -} - -func (suite *OrderServiceSuite) TestListOrdersForTOOWithPPM() { - postalCode := "50309" - partialPPMType := models.MovePPMTypePARTIAL - waf := entitlements.NewWeightAllotmentFetcher() - ppmShipment := factory.BuildPPMShipment(suite.DB(), []factory.Customization{ - { - Model: models.Order{ - ID: uuid.UUID{uuid.V4}, - }, - }, - { - Model: models.Move{ - Status: models.MoveStatusAPPROVED, - PPMType: &partialPPMType, - }, - }, - { - Model: models.Address{ - PostalCode: postalCode, + buildMoveZone2AK := func(branch models.ServiceMemberAffiliation) (models.Move, models.MTOShipment) { + // Create a USAF move in Alaska Zone II + // this is a hard coded uuid that is a us_post_region_cities_id within AK Zone II + zone2UUID, err := uuid.FromString("66768964-e0de-41f3-b9be-7ef32e4ae2b4") + suite.FatalNoError(err) + destinationAddress := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + City: "Anchorage", + State: "AK", + PostalCode: "99501", + UsPostRegionCityID: &zone2UUID, + }, }, - Type: &factory.Addresses.PickupAddress, - }, - }, nil) - // Make a TOO user. - tooOfficeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) - session := auth.Session{ - ApplicationName: auth.OfficeApp, - Roles: tooOfficeUser.User.Roles, - OfficeUserID: tooOfficeUser.ID, - IDToken: "fake_token", - AccessToken: "fakeAccessToken", - } - - // GBLOC for the below doesn't really matter, it just means the query for the moves passes the inner join in ListOrders - factory.FetchOrBuildPostalCodeToGBLOC(suite.DB(), ppmShipment.PickupAddress.PostalCode, tooOfficeUser.TransportationOffice.Gbloc) - - orderFetcher := NewOrderFetcher(waf) - moves, moveCount, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), tooOfficeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{}) - suite.FatalNoError(err) - suite.Equal(1, moveCount) - suite.Len(moves, 1) -} - -func (suite *OrderServiceSuite) TestListOrdersWithViewAsGBLOCParam() { - var hqOfficeUser models.OfficeUser - var hqOfficeUserAGFM models.OfficeUser - waf := entitlements.NewWeightAllotmentFetcher() - requestedMoveDate1 := time.Date(testdatagen.GHCTestYear, 02, 20, 0, 0, 0, 0, time.UTC) - requestedMoveDate2 := time.Date(testdatagen.GHCTestYear, 03, 03, 0, 0, 0, 0, time.UTC) + }, nil) - setupTestData := func() (models.Move, models.Move, models.MTOShipment, auth.Session, auth.Session) { - // CREATE EXPECTED MOVES - expectedMove1 := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ - { // Default New Duty Location name is Fort Eisenhower + // setting up two moves, each with requested destination SIT service items + move := factory.BuildAvailableToPrimeMove(suite.DB(), []factory.Customization{ + { Model: models.Move{ - Status: models.MoveStatusAPPROVED, - Locator: "AA1234", + Status: models.MoveStatusAPPROVALSREQUESTED, + Show: models.BoolPointer(true), }, }, { - Model: models.MTOShipment{ - RequestedPickupDate: &requestedMoveDate1, + Model: models.ServiceMember{ + Affiliation: &branch, }, }, }, nil) - expectedMove2 := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ { - Model: models.Move{ - Locator: "TTZ123", + Model: models.MTOShipment{ + Status: models.MTOShipmentStatusApproved, }, }, { - Model: models.MTOShipment{ - RequestedPickupDate: &requestedMoveDate2, - }, + Model: move, + LinkOnly: true, + }, + { + Model: destinationAddress, + LinkOnly: true, }, }, nil) - factory.FetchOrBuildPostalCodeToGBLOC(suite.DB(), "06001", "AGFM") + return move, shipment + } - expectedShipment3 := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + buildMoveZone4AK := func(branch models.ServiceMemberAffiliation) (models.Move, models.MTOShipment) { + // Create a USAF move in Alaska Zone II + // this is a hard coded uuid that is a us_post_region_cities_id within AK Zone II + zone4UUID, err := uuid.FromString("78a6f230-9a3a-46ed-aa48-2e3decfe70ff") + suite.FatalNoError(err) + destinationAddress := factory.BuildAddress(suite.DB(), []factory.Customization{ { - Model: models.TransportationOffice{ - Name: "Fort Punxsutawney", - Gbloc: "AGFM", + Model: models.Address{ + City: "Anchorage", + State: "AK", + PostalCode: "99501", + UsPostRegionCityID: &zone4UUID, }, }, + }, nil) + // setting up two moves, each with requested destination SIT service items + move := factory.BuildAvailableToPrimeMove(suite.DB(), []factory.Customization{ { - Model: models.MTOShipment{ - Status: models.MTOShipmentStatusSubmitted, + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + Show: models.BoolPointer(true), }, }, { - Model: models.Address{ - PostalCode: "06001", + Model: models.ServiceMember{ + Affiliation: &branch, }, - Type: &factory.Addresses.PickupAddress, }, }, nil) - hqOfficeUser = factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeHQ}) - hqSession := auth.Session{ - ApplicationName: auth.OfficeApp, - Roles: hqOfficeUser.User.Roles, - OfficeUserID: hqOfficeUser.ID, - IDToken: "fake_token", - AccessToken: "fakeAccessToken", - } - - hqOfficeUserAGFM = factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ { - Model: models.TransportationOffice{ - Name: "Scott AFB", - Gbloc: "AGFM", + Model: models.MTOShipment{ + Status: models.MTOShipmentStatusApproved, }, }, - }, []roles.RoleType{roles.RoleTypeHQ}) - hqSessionAGFM := auth.Session{ - ApplicationName: auth.OfficeApp, - Roles: hqOfficeUserAGFM.User.Roles, - OfficeUserID: hqOfficeUserAGFM.ID, - IDToken: "fake_token", - AccessToken: "fakeAccessToken", - } + { + Model: move, + LinkOnly: true, + }, + { + Model: destinationAddress, + LinkOnly: true, + }, + }, nil) - return expectedMove1, expectedMove2, expectedShipment3, hqSession, hqSessionAGFM + return move, shipment } + waf := entitlements.NewWeightAllotmentFetcher() orderFetcher := NewOrderFetcher(waf) - suite.Run("Sort by locator code", func() { - expectedMove1, expectedMove2, expectedShipment3, hqSession, hqSessionAGFM := setupTestData() - - // Request as an HQ user with their default GBLOC, KKFA - params := services.ListOrderParams{Sort: models.StringPointer("locator"), Order: models.StringPointer("asc")} - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&hqSession), hqOfficeUser.ID, roles.RoleTypeTOO, ¶ms) - suite.NoError(err) - suite.Equal(2, len(moves)) - suite.Equal(expectedMove1.Locator, moves[0].Locator) - suite.Equal(expectedMove2.Locator, moves[1].Locator) - - // Expect the same results with a ViewAsGBLOC that equals the user's default GBLOC, KKFA - params = services.ListOrderParams{Sort: models.StringPointer("locator"), Order: models.StringPointer("asc"), ViewAsGBLOC: models.StringPointer("KKFA")} - moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&hqSession), hqOfficeUser.ID, roles.RoleTypeTOO, ¶ms) - suite.NoError(err) - suite.Equal(2, len(moves)) - suite.Equal(expectedMove1.Locator, moves[0].Locator) - suite.Equal(expectedMove2.Locator, moves[1].Locator) - - // Expect the AGFM move when using the ViewAsGBLOC param set to AGFM - params = services.ListOrderParams{ViewAsGBLOC: models.StringPointer("AGFM")} - moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&hqSession), hqOfficeUser.ID, roles.RoleTypeTOO, ¶ms) - suite.NoError(err) - suite.Equal(1, len(moves)) - suite.Equal(expectedShipment3.ID, moves[0].MTOShipments[0].ID) - - // Expect the same results without a ViewAsGBLOC for a user whose default GBLOC is AGFM - params = services.ListOrderParams{} - moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&hqSessionAGFM), hqOfficeUserAGFM.ID, roles.RoleTypeTOO, ¶ms) - suite.NoError(err) - suite.Equal(1, len(moves)) - suite.Equal(expectedShipment3.ID, moves[0].MTOShipments[0].ID) - }) -} + suite.Run("returns moves for KKFA GBLOC when destination address is in KKFA GBLOC", func() { + officeUser, session := setupTestData("KKFA") + // setting up two moves, each with requested destination SIT service items + move, shipment := buildMoveKKFA() -func (suite *OrderServiceSuite) TestListOrdersForTOOWithPPMWithDeletedShipment() { - postalCode := "50309" - deletedAt := time.Now() - waf := entitlements.NewWeightAllotmentFetcher() - move := factory.BuildMove(suite.DB(), []factory.Customization{ - { - Model: models.Move{ - Status: models.MoveStatusSUBMITTED, + // destination service item in SUBMITTED status + factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: models.ReService{ + Code: models.ReServiceCodeDDFSIT, + }, }, - }, - }, nil) - ppmShipment := factory.BuildPPMShipment(suite.DB(), []factory.Customization{ - { - Model: models.Address{ - PostalCode: postalCode, + { + Model: move, + LinkOnly: true, }, - Type: &factory.Addresses.PickupAddress, - }, - }, nil) - factory.BuildMTOShipment(suite.DB(), []factory.Customization{ - { - Model: move, - LinkOnly: true, - }, - { - Model: models.MTOShipment{ - Status: models.MTOShipmentStatusSubmitted, - DeletedAt: &deletedAt, + { + Model: shipment, + LinkOnly: true, }, - }, - { - Model: ppmShipment, - LinkOnly: true, - }, - }, nil) + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusSubmitted, + }, + }, + }, nil) - // Make a TOO user. - tooOfficeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) - session := auth.Session{ - ApplicationName: auth.OfficeApp, - Roles: tooOfficeUser.User.Roles, - OfficeUserID: tooOfficeUser.ID, - IDToken: "fake_token", - AccessToken: "fakeAccessToken", - } + move2, shipment2 := buildMoveKKFA() - orderFetcher := NewOrderFetcher(waf) - moves, moveCount, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), tooOfficeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{Status: []string{string(models.MoveStatusSUBMITTED)}}) - suite.FatalNoError(err) - suite.Equal(0, moveCount) - suite.Len(moves, 0) -} + // destination shuttle + factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: models.ReService{ + Code: models.ReServiceCodeDDSHUT, + }, + }, + { + Model: move2, + LinkOnly: true, + }, + { + Model: shipment2, + LinkOnly: true, + }, + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusSubmitted, + }, + }, + }, nil) -func (suite *OrderServiceSuite) TestListOrdersForTOOWithPPMWithOneDeletedShipmentButOtherExists() { - postalCode := "50309" - deletedAt := time.Now() - waf := entitlements.NewWeightAllotmentFetcher() - move := factory.BuildMove(suite.DB(), []factory.Customization{ - { - Model: models.Move{ - Status: models.MoveStatusAPPROVED, + move3, shipment3 := buildMoveKKFA() + factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: models.ReService{ + Code: models.ReServiceCodeMS, + }, }, - }, - }, nil) - // This shipment is created first, but later deleted - ppmShipment1 := factory.BuildPPMShipment(suite.DB(), []factory.Customization{ - { - Model: move, - LinkOnly: true, - }, - { - Model: models.PPMShipment{ - CreatedAt: time.Now(), + { + Model: move3, + LinkOnly: true, }, - }, - { - Model: models.Address{ - PostalCode: postalCode, + { + Model: shipment3, + LinkOnly: true, }, - Type: &factory.Addresses.PickupAddress, - }, - }, nil) - // This shipment is created after the first one, but not deleted - factory.BuildPPMShipment(suite.DB(), []factory.Customization{ - { - Model: move, - LinkOnly: true, - }, - { - Model: models.PPMShipment{ - CreatedAt: time.Now().Add(time.Minute * time.Duration(1)), + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusApproved, + }, }, - }, - { - Model: models.Address{ - PostalCode: postalCode, + }, nil) + factory.BuildShipmentAddressUpdate(suite.DB(), []factory.Customization{ + { + Model: shipment3, + LinkOnly: true, }, - Type: &factory.Addresses.PickupAddress, - }, - }, nil) - factory.BuildMTOShipment(suite.DB(), []factory.Customization{ - { - Model: move, - LinkOnly: true, - }, - { - Model: models.MTOShipment{ - Status: models.MTOShipmentStatusSubmitted, - DeletedAt: &deletedAt, + { + Model: move3, + LinkOnly: true, }, - }, - { - Model: ppmShipment1, - LinkOnly: true, - }, - }, nil) - - // Make a TOO user and the postal code to GBLOC link. - tooOfficeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) - session := auth.Session{ - ApplicationName: auth.OfficeApp, - Roles: tooOfficeUser.User.Roles, - OfficeUserID: tooOfficeUser.ID, - IDToken: "fake_token", - AccessToken: "fakeAccessToken", - } + }, []factory.Trait{factory.GetTraitShipmentAddressUpdateRequested}) - orderFetcher := NewOrderFetcher(waf) - moves, moveCount, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), tooOfficeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{}) - suite.FatalNoError(err) - suite.Equal(1, moveCount) - suite.Len(moves, 1) -} + move4, shipment4 := buildMoveKKFA() + // build the destination SIT service items and update their status to SUBMITTED + oneMonthLater := time.Now().AddDate(0, 1, 0) + factory.BuildDestSITServiceItems(suite.DB(), move4, shipment4, &oneMonthLater, nil) -func (suite *OrderServiceSuite) TestListAllOrderLocations() { - waf := entitlements.NewWeightAllotmentFetcher() - suite.Run("returns a list of all order locations in the current users queue", func() { - orderFetcher := NewOrderFetcher(waf) - officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeServicesCounselor}) - session := auth.Session{ - ApplicationName: auth.OfficeApp, - Roles: officeUser.User.Roles, - OfficeUserID: officeUser.ID, - IDToken: "fake_token", - AccessToken: "fakeAccessToken", - } + // build the SIT extension update + factory.BuildSITDurationUpdate(suite.DB(), []factory.Customization{ + { + Model: move4, + LinkOnly: true, + }, + { + Model: shipment4, + LinkOnly: true, + }, + { + Model: models.SITDurationUpdate{ + Status: models.SITExtensionStatusPending, + ContractorRemarks: models.StringPointer("gimme some more plz"), + }, + }, + }, nil) - params := services.ListOrderParams{} - moves, err := orderFetcher.ListAllOrderLocations(suite.AppContextWithSessionForTest(&session), officeUser.ID, ¶ms) + moves, moveCount, err := orderFetcher.ListDestinationRequestsOrders( + suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{}, + ) suite.FatalNoError(err) - suite.Equal(0, len(moves)) + suite.Equal(4, moveCount) + suite.Len(moves, 4) }) -} -func (suite *OrderServiceSuite) TestListOrdersFilteredByCustomerName() { - serviceMemberFirstName := "Margaret" - serviceMemberLastName := "Starlight" - edipi := "9999999998" - var officeUser models.OfficeUser - var session auth.Session - waf := entitlements.NewWeightAllotmentFetcher() - requestedMoveDate1 := time.Date(testdatagen.GHCTestYear, 05, 20, 0, 0, 0, 0, time.UTC) - requestedMoveDate2 := time.Date(testdatagen.GHCTestYear, 07, 03, 0, 0, 0, 0, time.UTC) + suite.Run("returns moves for MBFL GBLOC including USAF/SF in Alaska Zone II", func() { + officeUser, session := setupTestData("MBFL") - setupData := func() { - factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + // setting up two moves, each with requested destination SIT service items + // a move associated with an air force customer containing AK Zone II shipment + move, shipment := buildMoveZone2AK(airForce) + + // destination service item in SUBMITTED status + factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ { - Model: models.Move{ - Status: models.MoveStatusAPPROVED, - Locator: "AA1235", + Model: models.ReService{ + Code: models.ReServiceCodeDDFSIT, }, }, { - Model: models.MTOShipment{ - RequestedPickupDate: &requestedMoveDate1, + Model: move, + LinkOnly: true, + }, + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusSubmitted, }, }, }, nil) - factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + + // Create a move outside Alaska Zone II (Zone IV in this case) + move2, shipment2 := buildMoveZone4AK(spaceForce) + + // destination service item in SUBMITTED status + factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ { - Model: models.Move{ - Locator: "TTZ125", + Model: models.ReService{ + Code: models.ReServiceCodeDDFSIT, }, }, { - Model: models.ServiceMember{ - FirstName: &serviceMemberFirstName, - Edipi: &edipi, - }, + Model: move2, + LinkOnly: true, }, { - Model: models.MTOShipment{ - RequestedPickupDate: &requestedMoveDate2, - }, + Model: shipment2, + LinkOnly: true, }, - }, nil) - factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ { - Model: models.ServiceMember{ // Leo Zephyer - LastName: &serviceMemberLastName, + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusSubmitted, }, }, }, nil) - officeUser = factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) - session = auth.Session{ - ApplicationName: auth.OfficeApp, - Roles: officeUser.User.Roles, - OfficeUserID: officeUser.ID, - IDToken: "fake_token", - AccessToken: "fakeAccessToken", - } - } - orderFetcher := NewOrderFetcher(waf) + params := services.ListOrderParams{Status: []string{string(models.MoveStatusAPPROVALSREQUESTED)}} + moves, moveCount, err := orderFetcher.ListDestinationRequestsOrders( + suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms, + ) - suite.Run("list moves by customer name - full name (last, first)", func() { - setupData() - // Search "Spacemen, Margaret" - params := services.ListOrderParams{CustomerName: models.StringPointer("Spacemen, Margaret"), Sort: models.StringPointer("customerName"), Order: models.StringPointer("asc")} - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) - suite.NoError(err) - suite.Equal(1, len(moves)) - suite.Equal("Spacemen, Margaret", *moves[0].Orders.ServiceMember.LastName+", "+*moves[0].Orders.ServiceMember.FirstName) + // we should get both moves back because one is in Zone II & the other is within the postal code GBLOC + suite.FatalNoError(err) + suite.Equal(2, moveCount) + suite.Len(moves, 2) }) - suite.Run("list moves by customer name - full name (first last)", func() { - setupData() - // Search "Margaret Spacemen" - params := services.ListOrderParams{CustomerName: models.StringPointer("Margaret Spacemen"), Sort: models.StringPointer("customerName"), Order: models.StringPointer("asc")} - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) - suite.NoError(err) - suite.Equal(1, len(moves)) - suite.Equal("Spacemen, Margaret", *moves[0].Orders.ServiceMember.LastName+", "+*moves[0].Orders.ServiceMember.FirstName) - }) + suite.Run("returns moves for JEAT GBLOC excluding USAF/SF in Alaska Zone II", func() { + officeUser, session := setupTestData("JEAT") - suite.Run("list moves by customer name - partial last (multiple)", func() { - setupData() - // Search "space" - params := services.ListOrderParams{CustomerName: models.StringPointer("space"), Sort: models.StringPointer("customerName"), Order: models.StringPointer("asc")} - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) - suite.NoError(err) - suite.Equal(2, len(moves)) - suite.Equal("Spacemen, Leo", *moves[0].Orders.ServiceMember.LastName+", "+*moves[0].Orders.ServiceMember.FirstName) - suite.Equal("Spacemen, Margaret", *moves[1].Orders.ServiceMember.LastName+", "+*moves[1].Orders.ServiceMember.FirstName) - }) + // Create a move in Zone II, but not an air force or space force service member + move, shipment := buildMoveZone4AK(army) - suite.Run("list moves by customer name - partial last (single)", func() { - setupData() - // Search "Light" - params := services.ListOrderParams{CustomerName: models.StringPointer("Light"), Sort: models.StringPointer("customerName"), Order: models.StringPointer("asc")} - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) - suite.NoError(err) - suite.Equal(1, len(moves)) - suite.Equal("Starlight, Leo", *moves[0].Orders.ServiceMember.LastName+", "+*moves[0].Orders.ServiceMember.FirstName) - }) + // destination service item in SUBMITTED status + factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: models.ReService{ + Code: models.ReServiceCodeDDFSIT, + }, + }, + { + Model: move, + LinkOnly: true, + }, + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusSubmitted, + }, + }, + }, nil) - suite.Run("list moves by customer name - partial first", func() { - setupData() - // Search "leo" - params := services.ListOrderParams{CustomerName: models.StringPointer("leo"), Sort: models.StringPointer("customerName"), Order: models.StringPointer("asc")} - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) - suite.NoError(err) - suite.Equal(2, len(moves)) - suite.Equal("Spacemen, Leo", *moves[0].Orders.ServiceMember.LastName+", "+*moves[0].Orders.ServiceMember.FirstName) - suite.Equal("Starlight, Leo", *moves[1].Orders.ServiceMember.LastName+", "+*moves[1].Orders.ServiceMember.FirstName) - }) + moves, moveCount, err := orderFetcher.ListDestinationRequestsOrders( + suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{}, + ) - suite.Run("list moves by customer name - partial matching within first or last", func() { - setupData() - // Search "ar" - params := services.ListOrderParams{CustomerName: models.StringPointer("ar"), Sort: models.StringPointer("customerName"), Order: models.StringPointer("asc")} - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) - suite.NoError(err) - suite.Equal(2, len(moves)) - suite.Equal("Spacemen, Margaret", *moves[0].Orders.ServiceMember.LastName+", "+*moves[0].Orders.ServiceMember.FirstName) - suite.Equal("Starlight, Leo", *moves[1].Orders.ServiceMember.LastName+", "+*moves[1].Orders.ServiceMember.FirstName) + suite.FatalNoError(err) + suite.Equal(1, moveCount) + suite.Len(moves, 1) }) - suite.Run("list moves by customer name - empty", func() { - setupData() - // Search "johnny" - params := services.ListOrderParams{CustomerName: models.StringPointer("johnny"), Sort: models.StringPointer("customerName"), Order: models.StringPointer("asc")} - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) - suite.NoError(err) - suite.Equal(0, len(moves)) - }) -} + suite.Run("returns moves for USMC GBLOC when moves belong to USMC servicemembers", func() { + officeUser, session := setupTestData("USMC") -func (suite *OrderServiceSuite) TestListAllOrderLocationsWithViewAsGBLOCParam() { - waf := entitlements.NewWeightAllotmentFetcher() - suite.Run("returns a list of all order locations in the current users queue", func() { - orderFetcher := NewOrderFetcher(waf) - officeUserFetcher := officeuserservice.NewOfficeUserFetcherPop() - movesContainOriginDutyLocation := func(moves models.Moves, keyword string) func() (success bool) { - return func() (success bool) { - for _, record := range moves { - if strings.Contains(record.Orders.OriginDutyLocation.Name, keyword) { - return true - } - } - return false - } - } + // setting up two moves, each with requested destination SIT service items + // both will be USMC moves, one in Zone II AK and the other not + move, shipment := buildMoveZone2AK(usmc) - // Create SC office user with a default transportation office in the AGFM GBLOC - officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + // destination service item in SUBMITTED status + factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ { - Model: models.TransportationOffice{ - Name: "Fort Punxsutawney", - Gbloc: "AGFM", + Model: models.ReService{ + Code: models.ReServiceCodeDDFSIT, }, }, - }, []roles.RoleType{roles.RoleTypeServicesCounselor}) - // Add a secondary GBLOC to the above office user, this should default to KKFA - factory.BuildAlternateTransportationOfficeAssignment(suite.DB(), []factory.Customization{ { - Model: models.OfficeUser{ - ID: officeUser.ID, - }, + Model: move, LinkOnly: true, }, + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusSubmitted, + }, + }, }, nil) - session := auth.Session{ - ApplicationName: auth.OfficeApp, - Roles: officeUser.User.Roles, - OfficeUserID: officeUser.ID, - IDToken: "fake_token", - AccessToken: "fakeAccessToken", - } - // Create two default moves with shipment, should be in KKFA and have the status SUBMITTED - KKFAMove1 := factory.BuildMoveWithShipment(suite.DB(), nil, nil) - KKFAMove2 := factory.BuildMoveWithShipment(suite.DB(), nil, nil) + // this one won't be in Zone II + move2, shipment2 := buildMoveZone4AK(usmc) - // Create third move with the same origin duty location as one of the above - KKFAMove3 := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + // destination shuttle + factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ { - Model: models.DutyLocation{ - ID: KKFAMove2.Orders.OriginDutyLocation.ID, + Model: models.ReService{ + Code: models.ReServiceCodeDDSHUT, }, - Type: &factory.DutyLocations.OriginDutyLocation, + }, + { + Model: move2, + LinkOnly: true, + }, + { + Model: shipment2, LinkOnly: true, }, + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusSubmitted, + }, + }, }, nil) - officeUser, _ = officeUserFetcher.FetchOfficeUserByIDWithTransportationOfficeAssignments(suite.AppContextForTest(), officeUser.ID) - - // Confirm office user has the desired transportation office assignments - suite.Equal("AGFM", officeUser.TransportationOffice.Gbloc) - if officeUser.TransportationOfficeAssignments[0].TransportationOffice.Gbloc == "AGFM" { - suite.Equal("AGFM", officeUser.TransportationOfficeAssignments[0].TransportationOffice.Gbloc) - suite.Equal(true, *officeUser.TransportationOfficeAssignments[0].PrimaryOffice) - suite.Equal("KKFA", officeUser.TransportationOfficeAssignments[1].TransportationOffice.Gbloc) - suite.Equal(false, *officeUser.TransportationOfficeAssignments[1].PrimaryOffice) - } else { - suite.Equal("KKFA", officeUser.TransportationOfficeAssignments[0].TransportationOffice.Gbloc) - suite.Equal(false, *officeUser.TransportationOfficeAssignments[0].PrimaryOffice) - suite.Equal("AGFM", officeUser.TransportationOfficeAssignments[1].TransportationOffice.Gbloc) - suite.Equal(true, *officeUser.TransportationOfficeAssignments[1].PrimaryOffice) - } - - // Confirm the factory created moves have the desired GBLOCS, 3x KKFA, - suite.Equal("KKFA", *KKFAMove1.Orders.OriginDutyLocationGBLOC) - suite.Equal("KKFA", *KKFAMove2.Orders.OriginDutyLocationGBLOC) - suite.Equal("KKFA", *KKFAMove3.Orders.OriginDutyLocationGBLOC) + move3, shipment3 := buildMoveZone4AK(usmc) + // we need to create a service item and attach it to the move/shipment + // else the query will exclude the move since it doesn't use LEFT JOINs + factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: models.ReService{ + Code: models.ReServiceCodeMS, + }, + }, + { + Model: move3, + LinkOnly: true, + }, + { + Model: shipment3, + LinkOnly: true, + }, + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusApproved, + }, + }, + }, nil) + factory.BuildShipmentAddressUpdate(suite.DB(), []factory.Customization{ + { + Model: shipment3, + LinkOnly: true, + }, + { + Model: move3, + LinkOnly: true, + }, + }, []factory.Trait{factory.GetTraitShipmentAddressUpdateRequested}) - // Fetch and check secondary GBLOC - KKFA := "KKFA" - params := services.ListOrderParams{ - ViewAsGBLOC: &KKFA, - } - KKFAmoves, err := orderFetcher.ListAllOrderLocations(suite.AppContextWithSessionForTest(&session), officeUser.ID, ¶ms) + moves, moveCount, err := orderFetcher.ListDestinationRequestsOrders( + suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{}, + ) + // we should get three moves back since they're USMC moves and zone doesn't matter suite.FatalNoError(err) - // This value should be updated to 3 if ListAllOrderLocations is updated to return distinct locations - suite.Equal(3, len(KKFAmoves)) - - suite.Equal("KKFA", *KKFAmoves[0].Orders.OriginDutyLocationGBLOC) - suite.Equal("KKFA", *KKFAmoves[1].Orders.OriginDutyLocationGBLOC) - suite.Equal("KKFA", *KKFAmoves[2].Orders.OriginDutyLocationGBLOC) - - suite.Condition(movesContainOriginDutyLocation(KKFAmoves, KKFAMove1.Orders.OriginDutyLocation.Name), "Should contain first KKFA move's origin duty location") - suite.Condition(movesContainOriginDutyLocation(KKFAmoves, KKFAMove2.Orders.OriginDutyLocation.Name), "Should contain second KKFA move's origin duty location") - suite.Condition(movesContainOriginDutyLocation(KKFAmoves, KKFAMove3.Orders.OriginDutyLocation.Name), "Should contain third KKFA move's origin duty location") - }) -} - -func (suite *OrderServiceSuite) TestOriginDutyLocationFilter() { - var session auth.Session - waf := entitlements.NewWeightAllotmentFetcher() - var expectedMove models.Move - var officeUser models.OfficeUser - orderFetcher := NewOrderFetcher(waf) - setupTestData := func() (models.OfficeUser, models.Move, auth.Session) { - officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) - session := auth.Session{ - ApplicationName: auth.OfficeApp, - Roles: officeUser.User.Roles, - OfficeUserID: officeUser.ID, - IDToken: "fake_token", - AccessToken: "fakeAccessToken", - } - move := factory.BuildMoveWithShipment(suite.DB(), nil, nil) - return officeUser, move, session - } - - suite.Run("Returns orders matching full originDutyLocation name filter", func() { - officeUser, expectedMove, session = setupTestData() - locationName := expectedMove.Orders.OriginDutyLocation.Name - expectedMoves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{OriginDutyLocation: strings.Split(locationName, " ")}) - suite.NoError(err) - suite.Equal(1, len(expectedMoves)) - suite.Equal(locationName, string(expectedMoves[0].Orders.OriginDutyLocation.Name)) - }) - - suite.Run("Returns orders matching partial originDutyLocation name filter", func() { - officeUser, expectedMove, session = setupTestData() - locationName := expectedMove.Orders.OriginDutyLocation.Name - //Split the location name and retrieve a substring (first string) for the search param - partialParamSearch := strings.Split(locationName, " ")[0] - expectedMoves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{OriginDutyLocation: strings.Split(partialParamSearch, " ")}) - suite.NoError(err) - suite.Equal(1, len(expectedMoves)) - suite.Equal(locationName, string(expectedMoves[0].Orders.OriginDutyLocation.Name)) + suite.Equal(3, moveCount) + suite.Len(moves, 3) }) } diff --git a/pkg/services/order/order_updater.go b/pkg/services/order/order_updater.go index 8929d047684..492c43acabe 100644 --- a/pkg/services/order/order_updater.go +++ b/pkg/services/order/order_updater.go @@ -488,6 +488,8 @@ func allowanceFromTOOPayload(appCtx appcontext.AppContext, existingOrder models. if payload.WeightRestriction != nil { weightRestriction := int(*payload.WeightRestriction) order.Entitlement.WeightRestriction = &weightRestriction + } else { + order.Entitlement.WeightRestriction = nil } if payload.AccompaniedTour != nil { @@ -596,6 +598,8 @@ func allowanceFromCounselingPayload(appCtx appcontext.AppContext, existingOrder if payload.WeightRestriction != nil { weightRestriction := int(*payload.WeightRestriction) order.Entitlement.WeightRestriction = &weightRestriction + } else { + order.Entitlement.WeightRestriction = nil } if payload.AccompaniedTour != nil { diff --git a/pkg/services/order/order_updater_test.go b/pkg/services/order/order_updater_test.go index 9e86e990f95..b88d9ab435a 100644 --- a/pkg/services/order/order_updater_test.go +++ b/pkg/services/order/order_updater_test.go @@ -651,6 +651,32 @@ func (suite *OrderServiceSuite) TestUpdateAllowanceAsTOO() { suite.Equal(*payload.DependentsUnderTwelve, int64(*updatedOrder.Entitlement.DependentsUnderTwelve)) }) + suite.Run("Updates the allowance when weightRestriction is null", func() { + moveRouter := move.NewMoveRouter() + orderUpdater := NewOrderUpdater(moveRouter) + order := factory.BuildNeedsServiceCounselingMove(suite.DB(), []factory.Customization{ + { + Model: models.Entitlement{ + WeightRestriction: models.IntPointer(1000), + }, + }, + }, nil).Orders + + eTag := etag.GenerateEtag(order.UpdatedAt) + + payload := ghcmessages.UpdateAllowancePayload{ + WeightRestriction: nil, + } + + updatedOrder, _, err := orderUpdater.UpdateAllowanceAsTOO(suite.AppContextForTest(), order.ID, payload, eTag) + suite.NoError(err) + + var orderInDB models.Order + err = suite.DB().Find(&orderInDB, order.ID) + suite.NoError(err) + suite.Nil(updatedOrder.Entitlement.WeightRestriction) + }) + suite.Run("Updates the allowance when all fields are valid with dependents", func() { moveRouter := move.NewMoveRouter() orderUpdater := NewOrderUpdater(moveRouter) @@ -810,6 +836,32 @@ func (suite *OrderServiceSuite) TestUpdateAllowanceAsCounselor() { suite.Equal(*payload.WeightRestriction, int64(*updatedOrder.Entitlement.WeightRestriction)) }) + suite.Run("Updates the allowance when weightRestriction is null", func() { + moveRouter := move.NewMoveRouter() + orderUpdater := NewOrderUpdater(moveRouter) + order := factory.BuildNeedsServiceCounselingMove(suite.DB(), []factory.Customization{ + { + Model: models.Entitlement{ + WeightRestriction: models.IntPointer(1000), + }, + }, + }, nil).Orders + + eTag := etag.GenerateEtag(order.UpdatedAt) + + payload := ghcmessages.CounselingUpdateAllowancePayload{ + WeightRestriction: nil, + } + + updatedOrder, _, err := orderUpdater.UpdateAllowanceAsCounselor(suite.AppContextForTest(), order.ID, payload, eTag) + suite.NoError(err) + + var orderInDB models.Order + err = suite.DB().Find(&orderInDB, order.ID) + suite.NoError(err) + suite.Nil(updatedOrder.Entitlement.WeightRestriction) + }) + suite.Run("Updates the allowance when all fields are valid with dependents present and authorized", func() { moveRouter := move.NewMoveRouter() orderUpdater := NewOrderUpdater(moveRouter) diff --git a/pkg/services/shipment_address_update/shipment_address_update_requester.go b/pkg/services/shipment_address_update/shipment_address_update_requester.go index 1c2e93b198c..0cb8ba3c123 100644 --- a/pkg/services/shipment_address_update/shipment_address_update_requester.go +++ b/pkg/services/shipment_address_update/shipment_address_update_requester.go @@ -499,7 +499,7 @@ func (f *shipmentAddressUpdateRequester) ReviewShipmentAddressChange(appCtx appc if tooApprovalStatus == models.ShipmentAddressUpdateStatusApproved { queryBuilder := query.NewQueryBuilder() - serviceItemUpdater := mtoserviceitem.NewMTOServiceItemUpdater(f.planner, queryBuilder, f.moveRouter, f.shipmentFetcher, f.addressCreator, f.portLocationFetcher) + serviceItemUpdater := mtoserviceitem.NewMTOServiceItemUpdater(f.planner, queryBuilder, f.moveRouter, f.shipmentFetcher, f.addressCreator, f.portLocationFetcher, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) serviceItemCreator := mtoserviceitem.NewMTOServiceItemCreator(f.planner, queryBuilder, f.moveRouter, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticPackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticShorthaulPricer(), ghcrateengine.NewDomesticOriginPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) addressUpdate.Status = models.ShipmentAddressUpdateStatusApproved @@ -523,7 +523,7 @@ func (f *shipmentAddressUpdateRequester) ReviewShipmentAddressChange(appCtx appc } var shipmentDetails models.MTOShipment - err = appCtx.DB().EagerPreload("MoveTaskOrder", "MTOServiceItems.ReService").Find(&shipmentDetails, shipmentID) + err = appCtx.DB().EagerPreload("MoveTaskOrder", "MTOServiceItems.ReService", "MTOServiceItems.SITDestinationOriginalAddress", "MTOServiceItems.SITDestinationFinalAddress").Find(&shipmentDetails, shipmentID) if err != nil { if err == sql.ErrNoRows { return nil, apperror.NewNotFoundError(shipmentID, "looking for shipment") @@ -531,6 +531,33 @@ func (f *shipmentAddressUpdateRequester) ReviewShipmentAddressChange(appCtx appc return nil, apperror.NewQueryError("MTOShipment", err, "") } + shipmentHasApprovedDestSIT := f.doesShipmentContainApprovedDestinationSIT(shipmentDetails) + + for i, serviceItem := range shipmentDetails.MTOServiceItems { + if shipment.MarketCode != models.MarketCodeInternational && shipment.PrimeEstimatedWeight != nil || shipment.MarketCode != models.MarketCodeInternational && shipment.PrimeActualWeight != nil { + var updatedServiceItem *models.MTOServiceItem + if serviceItem.ReService.Code == models.ReServiceCodeDDP || serviceItem.ReService.Code == models.ReServiceCodeDUPK { + updatedServiceItem, err = serviceItemUpdater.UpdateMTOServiceItemPricingEstimate(appCtx, &serviceItem, shipment, etag.GenerateEtag(serviceItem.UpdatedAt)) + if err != nil { + return nil, apperror.NewUpdateError(serviceItem.ReServiceID, err.Error()) + } + } + + if !shipmentHasApprovedDestSIT { + if serviceItem.ReService.Code == models.ReServiceCodeDLH || serviceItem.ReService.Code == models.ReServiceCodeFSC { + updatedServiceItem, err = serviceItemUpdater.UpdateMTOServiceItemPricingEstimate(appCtx, &serviceItem, shipment, etag.GenerateEtag(serviceItem.UpdatedAt)) + if err != nil { + return nil, apperror.NewUpdateError(serviceItem.ReServiceID, err.Error()) + } + } + } + + if updatedServiceItem != nil { + shipmentDetails.MTOServiceItems[i] = *updatedServiceItem + } + } + } + // If the pricing type has changed then we automatically reject the DLH or DSH service item on the shipment since it is now inaccurate var approvedPaymentRequestsExistsForServiceItem bool if haulPricingTypeHasChanged && len(shipment.MTOServiceItems) > 0 && !isInternationalShipment { @@ -546,8 +573,6 @@ func (f *shipmentAddressUpdateRequester) ReviewShipmentAddressChange(appCtx appc return nil, apperror.NewQueryError("ServiceItemPaymentRequests", err, "") } - shipmentHasApprovedDestSIT := f.doesShipmentContainApprovedDestinationSIT(shipmentDetails) - // do NOT regenerate any service items if the following conditions exist: // payment has already been approved for DLH or DSH service item // destination SIT is on shipment and any of the service items have an appproved status diff --git a/pkg/services/shipment_address_update/shipment_address_update_requester_test.go b/pkg/services/shipment_address_update/shipment_address_update_requester_test.go index 223cf820988..ad1c2cd1ad9 100644 --- a/pkg/services/shipment_address_update/shipment_address_update_requester_test.go +++ b/pkg/services/shipment_address_update/shipment_address_update_requester_test.go @@ -15,8 +15,44 @@ import ( "github.com/transcom/mymove/pkg/services/address" moveservices "github.com/transcom/mymove/pkg/services/move" "github.com/transcom/mymove/pkg/testdatagen" + "github.com/transcom/mymove/pkg/unit" ) +func (suite *ShipmentAddressUpdateServiceSuite) setupServiceItemData() { + startDate := time.Date(2020, time.January, 1, 12, 0, 0, 0, time.UTC) + endDate := time.Date(2020, time.December, 31, 12, 0, 0, 0, time.UTC) + + testdatagen.FetchOrMakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + StartDate: startDate, + EndDate: endDate, + }, + }) + + originalDomesticServiceArea := testdatagen.FetchOrMakeReDomesticServiceArea(suite.AppContextForTest().DB(), testdatagen.Assertions{ + ReDomesticServiceArea: models.ReDomesticServiceArea{ + ServiceArea: "004", + ServicesSchedule: 2, + }, + ReContract: testdatagen.FetchOrMakeReContract(suite.AppContextForTest().DB(), testdatagen.Assertions{}), + }) + + testdatagen.FetchOrMakeReDomesticLinehaulPrice(suite.DB(), testdatagen.Assertions{ + ReDomesticLinehaulPrice: models.ReDomesticLinehaulPrice{ + Contract: originalDomesticServiceArea.Contract, + ContractID: originalDomesticServiceArea.ContractID, + DomesticServiceArea: originalDomesticServiceArea, + DomesticServiceAreaID: originalDomesticServiceArea.ID, + WeightLower: unit.Pound(500), + WeightUpper: unit.Pound(9999), + MilesLower: 500, + MilesUpper: 9999, + PriceMillicents: unit.Millicents(606800), + IsPeakPeriod: false, + }, + }) +} + func (suite *ShipmentAddressUpdateServiceSuite) TestCreateApprovedShipmentAddressUpdate() { setupTestData := func() models.Move { testdatagen.FetchOrMakeReContractYear(suite.DB(), testdatagen.Assertions{ @@ -760,6 +796,8 @@ func (suite *ShipmentAddressUpdateServiceSuite) TestTOOApprovedShipmentAddressUp suite.Run("TOO approves address change", func() { + suite.setupServiceItemData() + addressChange := factory.BuildShipmentAddressUpdate(suite.DB(), nil, []factory.Trait{ factory.GetTraitAvailableToPrimeMove, }) @@ -1460,6 +1498,9 @@ func (suite *ShipmentAddressUpdateServiceSuite) TestTOOApprovedShipmentAddressUp false, ).Return(2500, nil).Once() move := setupTestData() + + suite.setupServiceItemData() + shipment := factory.BuildMTOShipmentWithMove(&move, suite.DB(), []factory.Customization{ { Model: models.Address{ @@ -1528,6 +1569,9 @@ func (suite *ShipmentAddressUpdateServiceSuite) TestTOOApprovedShipmentAddressUp false, ).Return(2500, nil).Once() move := setupTestData() + + suite.setupServiceItemData() + shipment := factory.BuildMTOShipmentWithMove(&move, suite.DB(), []factory.Customization{ { Model: models.Address{ @@ -1583,6 +1627,9 @@ func (suite *ShipmentAddressUpdateServiceSuite) TestTOOApprovedShipmentAddressUp suite.Run("Service items are not rejected when pricing type does not change post TOO approval", func() { move := setupTestData() + + suite.setupServiceItemData() + shipment := factory.BuildMTOShipmentWithMove(&move, suite.DB(), nil, nil) //Generate service items to test their statuses upon approval @@ -1636,6 +1683,9 @@ func (suite *ShipmentAddressUpdateServiceSuite) TestTOOApprovedShipmentAddressUp false, ).Return(2500, nil).Once() move := setupTestData() + + suite.setupServiceItemData() + shipment := factory.BuildMTOShipmentWithMove(&move, suite.DB(), []factory.Customization{ { Model: models.Address{ @@ -1705,6 +1755,9 @@ func (suite *ShipmentAddressUpdateServiceSuite) TestTOOApprovedShipmentAddressUp PostalCode: "90210", } move := setupTestData() + + suite.setupServiceItemData() + shipment := factory.BuildMTOShipmentWithMove(&move, suite.DB(), []factory.Customization{ { Model: models.Address{ diff --git a/pkg/services/sit_extension/sit_extension_denier.go b/pkg/services/sit_extension/sit_extension_denier.go index 07cd9477c16..9f019a0c47c 100644 --- a/pkg/services/sit_extension/sit_extension_denier.go +++ b/pkg/services/sit_extension/sit_extension_denier.go @@ -15,6 +15,7 @@ import ( routemocks "github.com/transcom/mymove/pkg/route/mocks" "github.com/transcom/mymove/pkg/services" "github.com/transcom/mymove/pkg/services/address" + "github.com/transcom/mymove/pkg/services/ghcrateengine" mtoserviceitem "github.com/transcom/mymove/pkg/services/mto_service_item" mtoshipment "github.com/transcom/mymove/pkg/services/mto_shipment" portlocation "github.com/transcom/mymove/pkg/services/port_location" @@ -35,7 +36,7 @@ func NewSITExtensionDenier(moveRouter services.MoveRouter) services.SITExtension mock.Anything, false, ).Return(400, nil) - return &sitExtensionDenier{moveRouter, mtoserviceitem.NewMTOServiceItemUpdater(planner, query.NewQueryBuilder(), moveRouter, mtoshipment.NewMTOShipmentFetcher(), address.NewAddressCreator(), portlocation.NewPortLocationFetcher())} + return &sitExtensionDenier{moveRouter, mtoserviceitem.NewMTOServiceItemUpdater(planner, query.NewQueryBuilder(), moveRouter, mtoshipment.NewMTOShipmentFetcher(), address.NewAddressCreator(), portlocation.NewPortLocationFetcher(), ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer())} } // DenySITExtension denies the SIT Extension diff --git a/pkg/testdatagen/scenario/shared.go b/pkg/testdatagen/scenario/shared.go index 3eec4a8f5ee..69522d1d02f 100644 --- a/pkg/testdatagen/scenario/shared.go +++ b/pkg/testdatagen/scenario/shared.go @@ -4296,7 +4296,7 @@ func createHHGWithOriginSITServiceItems( mock.Anything, false, ).Return(400, nil) - serviceItemUpdator := mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher) + serviceItemUpdator := mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) var originFirstDaySIT models.MTOServiceItem var originAdditionalDaySIT models.MTOServiceItem @@ -4566,7 +4566,7 @@ func createHHGWithDestinationSITServiceItems(appCtx appcontext.AppContext, prime mock.Anything, false, ).Return(400, nil) - serviceItemUpdator := mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher) + serviceItemUpdator := mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) var destinationFirstDaySIT models.MTOServiceItem var destinationAdditionalDaySIT models.MTOServiceItem @@ -5053,7 +5053,7 @@ func createHHGWithPaymentServiceItems( mock.Anything, false, ).Return(400, nil) - serviceItemUpdater := mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher) + serviceItemUpdater := mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) var originFirstDaySIT models.MTOServiceItem var originAdditionalDaySIT models.MTOServiceItem diff --git a/pkg/testdatagen/testharness/dispatch.go b/pkg/testdatagen/testharness/dispatch.go index 0ed11caf74a..d008fd696c6 100644 --- a/pkg/testdatagen/testharness/dispatch.go +++ b/pkg/testdatagen/testharness/dispatch.go @@ -104,6 +104,9 @@ var actionDispatcher = map[string]actionFunc{ "HHGMoveWithIntlCratingServiceItemsTOO": func(appCtx appcontext.AppContext) testHarnessResponse { return MakeHHGMoveWithIntlCratingServiceItemsTOO(appCtx) }, + "HHGMoveWithIntlShuttleServiceItemsTOO": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeHHGMoveWithIntlShuttleServiceItemsTOO(appCtx) + }, "HHGMoveForTOOAfterActualPickupDate": func(appCtx appcontext.AppContext) testHarnessResponse { return MakeHHGMoveForTOOAfterActualPickupDate(appCtx) }, @@ -272,6 +275,298 @@ var actionDispatcher = map[string]actionFunc{ "InternationalHHGMoveWithServiceItemsandPaymentRequestsForTIO": func(appCtx appcontext.AppContext) testHarnessResponse { return MakeBasicInternationalHHGMoveWithServiceItemsandPaymentRequestsForTIO(appCtx) }, + "IntlHHGMoveWithCratingUncratingServiceItemsAndPaymentRequestsForTIO": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveWithCratingUncratingServiceItemsAndPaymentRequestsForTIO(appCtx) + }, + // basic iHHG move with CONUS -> AK needing TOO approval + "IntlHHGMoveDestAKZone1Army": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestAKZone1Army(appCtx) + }, + "IntlHHGMoveDestAKZone2Army": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestAKZone2Army(appCtx) + }, + "IntlHHGMoveDestAKZone1AirForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestAKZone1AirForce(appCtx) + }, + "IntlHHGMoveDestAKZone2AirForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestAKZone2AirForce(appCtx) + }, + "IntlHHGMoveDestAKZone1SpaceForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestAKZone1SpaceForce(appCtx) + }, + "IntlHHGMoveDestAKZone2SpaceForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestAKZone2SpaceForce(appCtx) + }, + "IntlHHGMoveDestAKZone1USMC": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestAKZone1USMC(appCtx) + }, + "IntlHHGMoveDestAKZone2USMC": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestAKZone2USMC(appCtx) + }, + // basic iHHG move with AK -> CONUS needing TOO approval + "IntlHHGMovePickupAKZone1Army": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMovePickupAKZone1Army(appCtx) + }, + "IntlHHGMovePickupAKZone2Army": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMovePickupAKZone2Army(appCtx) + }, + "IntlHHGMovePickupAKZone1AirForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMovePickupAKZone1AirForce(appCtx) + }, + "IntlHHGMovePickupAKZone2AirForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMovePickupAKZone2AirForce(appCtx) + }, + "IntlHHGMovePickupAKZone1SpaceForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMovePickupAKZone1SpaceForce(appCtx) + }, + "IntlHHGMovePickupAKZone2SpaceForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMovePickupAKZone2SpaceForce(appCtx) + }, + "IntlHHGMovePickupAKZone1USMC": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMovePickupAKZone1USMC(appCtx) + }, + "IntlHHGMovePickupAKZone2USMC": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMovePickupAKZone2USMC(appCtx) + }, + // iHHG with international origin SIT in SUBMITTED status + "IntlHHGMoveOriginSITRequestedAKZone1Army": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveOriginSITRequestedAKZone1Army(appCtx) + }, + "IntlHHGMoveOriginSITRequestedAKZone2Army": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveOriginSITRequestedAKZone2Army(appCtx) + }, + "IntlHHGMoveOriginSITRequestedAKZone1AirForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveOriginSITRequestedAKZone1AirForce(appCtx) + }, + "IntlHHGMoveOriginSITRequestedAKZone2AirForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveOriginSITRequestedAKZone2AirForce(appCtx) + }, + "IntlHHGMoveOriginSITRequestedAKZone1SpaceForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveOriginSITRequestedAKZone1SpaceForce(appCtx) + }, + "IntlHHGMoveOriginSITRequestedAKZone2SpaceForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveOriginSITRequestedAKZone2SpaceForce(appCtx) + }, + "IntlHHGMoveOriginSITRequestedAKZone1USMC": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveOriginSITRequestedAKZone1USMC(appCtx) + }, + "IntlHHGMoveOriginSITRequestedAKZone2USMC": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveOriginSITRequestedAKZone2USMC(appCtx) + }, + // iHHG with international destination SIT in SUBMITTED status + "IntlHHGMoveDestSITRequestedAKZone1Army": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestSITRequestedAKZone1Army(appCtx) + }, + "IntlHHGMoveDestSITRequestedAKZone2Army": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestSITRequestedAKZone2Army(appCtx) + }, + "IntlHHGMoveDestSITRequestedAKZone1AirForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestSITRequestedAKZone1AirForce(appCtx) + }, + "IntlHHGMoveDestSITRequestedAKZone2AirForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestSITRequestedAKZone2AirForce(appCtx) + }, + "IntlHHGMoveDestSITRequestedAKZone1SpaceForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestSITRequestedAKZone1SpaceForce(appCtx) + }, + "IntlHHGMoveDestSITRequestedAKZone2SpaceForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestSITRequestedAKZone2SpaceForce(appCtx) + }, + "IntlHHGMoveDestSITRequestedAKZone1USMC": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestSITRequestedAKZone1USMC(appCtx) + }, + "IntlHHGMoveDestSITRequestedAKZone2USMC": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestSITRequestedAKZone2USMC(appCtx) + }, + // iHHG with BOTH international origin & destination SIT in SUBMITTED status + "IntlHHGMoveBothSITRequestedAKZone1Army": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveBothSITRequestedAKZone1Army(appCtx) + }, + "IntlHHGMoveBothSITRequestedAKZone2Army": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveBothSITRequestedAKZone2Army(appCtx) + }, + "IntlHHGMoveBothSITRequestedAKZone1AirForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveBothSITRequestedAKZone1AirForce(appCtx) + }, + "IntlHHGMoveBothSITRequestedAKZone2AirForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveBothSITRequestedAKZone2AirForce(appCtx) + }, + "IntlHHGMoveBothSITRequestedAKZone1SpaceForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveBothSITRequestedAKZone1SpaceForce(appCtx) + }, + "IntlHHGMoveBothSITRequestedAKZone2SpaceForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveBothSITRequestedAKZone2SpaceForce(appCtx) + }, + "IntlHHGMoveBothSITRequestedAKZone1USMC": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveBothSITRequestedAKZone1USMC(appCtx) + }, + "IntlHHGMoveBothSITRequestedAKZone2USMC": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveBothSITRequestedAKZone2USMC(appCtx) + }, + // iHHG with international origin shuttle in SUBMITTED status + "IntlHHGMoveOriginShuttleRequestedAKZone1Army": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveOriginShuttleRequestedAKZone1Army(appCtx) + }, + "IntlHHGMoveOriginShuttleRequestedAKZone2Army": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveOriginShuttleRequestedAKZone2Army(appCtx) + }, + "IntlHHGMoveOriginShuttleRequestedAKZone1AirForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveOriginShuttleRequestedAKZone1AirForce(appCtx) + }, + "IntlHHGMoveOriginShuttleRequestedAKZone2AirForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveOriginShuttleRequestedAKZone2AirForce(appCtx) + }, + "IntlHHGMoveOriginShuttleRequestedAKZone1SpaceForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveOriginShuttleRequestedAKZone1SpaceForce(appCtx) + }, + "IntlHHGMoveOriginShuttleRequestedAKZone2SpaceForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveOriginShuttleRequestedAKZone2SpaceForce(appCtx) + }, + "IntlHHGMoveOriginShuttleRequestedAKZone1USMC": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveOriginShuttleRequestedAKZone1USMC(appCtx) + }, + "IntlHHGMoveOriginShuttleRequestedAKZone2USMC": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveOriginShuttleRequestedAKZone2USMC(appCtx) + }, + // iHHG with international destination shuttle in SUBMITTED status + "IntlHHGMoveDestShuttleRequestedAKZone1Army": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestShuttleRequestedAKZone1Army(appCtx) + }, + "IntlHHGMoveDestShuttleRequestedAKZone2Army": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestShuttleRequestedAKZone2Army(appCtx) + }, + "IntlHHGMoveDestShuttleRequestedAKZone1AirForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestShuttleRequestedAKZone1AirForce(appCtx) + }, + "IntlHHGMoveDestShuttleRequestedAKZone2AirForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestShuttleRequestedAKZone2AirForce(appCtx) + }, + "IntlHHGMoveDestShuttleRequestedAKZone1SpaceForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestShuttleRequestedAKZone1SpaceForce(appCtx) + }, + "IntlHHGMoveDestShuttleRequestedAKZone2SpaceForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestShuttleRequestedAKZone2SpaceForce(appCtx) + }, + "IntlHHGMoveDestShuttleRequestedAKZone1USMC": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestShuttleRequestedAKZone1USMC(appCtx) + }, + "IntlHHGMoveDestShuttleRequestedAKZone2USMC": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestShuttleRequestedAKZone2USMC(appCtx) + }, + // iHHG with BOTH international origin & destination shuttle in SUBMITTED status + "IntlHHGMoveBothShuttleRequestedAKZone1Army": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveBothShuttleRequestedAKZone1Army(appCtx) + }, + "IntlHHGMoveBothShuttleRequestedAKZone2Army": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveBothShuttleRequestedAKZone2Army(appCtx) + }, + "IntlHHGMoveBothShuttleRequestedAKZone1AirForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveBothShuttleRequestedAKZone1AirForce(appCtx) + }, + "IntlHHGMoveBothShuttleRequestedAKZone2AirForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveBothShuttleRequestedAKZone2AirForce(appCtx) + }, + "IntlHHGMoveBothShuttleRequestedAKZone1SpaceForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveBothShuttleRequestedAKZone1SpaceForce(appCtx) + }, + "IntlHHGMoveBothShuttleRequestedAKZone2SpaceForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveBothShuttleRequestedAKZone2SpaceForce(appCtx) + }, + "IntlHHGMoveBothShuttleRequestedAKZone1USMC": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveBothShuttleRequestedAKZone1USMC(appCtx) + }, + "IntlHHGMoveBothShuttleRequestedAKZone2USMC": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveBothShuttleRequestedAKZone2USMC(appCtx) + }, + // iHHG with a destination address request in REQUESTED status + "IntlHHGMoveDestAddressRequestedAKZone1Army": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestAddressRequestedAKZone1Army(appCtx) + }, + "IntlHHGMoveDestAddressRequestedAKZone2Army": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestAddressRequestedAKZone2Army(appCtx) + }, + "IntlHHGMoveDestAddressRequestedAKZone1AirForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestAddressRequestedAKZone1AirForce(appCtx) + }, + "IntlHHGMoveDestAddressRequestedAKZone2AirForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestAddressRequestedAKZone2AirForce(appCtx) + }, + "IntlHHGMoveDestAddressRequestedAKZone1SpaceForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestAddressRequestedAKZone1SpaceForce(appCtx) + }, + "IntlHHGMoveDestAddressRequestedAKZone2SpaceForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestAddressRequestedAKZone2SpaceForce(appCtx) + }, + "IntlHHGMoveDestAddressRequestedAKZone1USMC": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestAddressRequestedAKZone1USMC(appCtx) + }, + "IntlHHGMoveDestAddressRequestedAKZone2USMC": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestAddressRequestedAKZone2USMC(appCtx) + }, + // iHHG with a PENDING SIT extension request containing origin SIT + "IntlHHGMoveOriginSITExtensionRequestedAKZone1Army": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveOriginSITExtensionRequestedAKZone1Army(appCtx) + }, + "IntlHHGMoveOriginSITExtensionRequestedAKZone2Army": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveOriginSITExtensionRequestedAKZone2Army(appCtx) + }, + "IntlHHGMoveOriginSITExtensionRequestedAKZone1AirForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveOriginSITExtensionRequestedAKZone1AirForce(appCtx) + }, + "IntlHHGMoveOriginSITExtensionRequestedAKZone2AirForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveOriginSITExtensionRequestedAKZone2AirForce(appCtx) + }, + "IntlHHGMoveOriginSITExtensionRequestedAKZone1SpaceForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveOriginSITExtensionRequestedAKZone1SpaceForce(appCtx) + }, + "IntlHHGMoveOriginSITExtensionRequestedAKZone2SpaceForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveOriginSITExtensionRequestedAKZone2SpaceForce(appCtx) + }, + "IntlHHGMoveOriginSITExtensionRequestedAKZone1USMC": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveOriginSITExtensionRequestedAKZone1USMC(appCtx) + }, + "IntlHHGMoveOriginSITExtensionRequestedAKZone2USMC": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveOriginSITExtensionRequestedAKZone2USMC(appCtx) + }, + // iHHG with a PENDING SIT extension request containing destination SIT + "IntlHHGMoveDestSITExtensionRequestedAKZone1Army": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestSITExtensionRequestedAKZone1Army(appCtx) + }, + "IntlHHGMoveDestSITExtensionRequestedAKZone2Army": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestSITExtensionRequestedAKZone2Army(appCtx) + }, + "IntlHHGMoveDestSITExtensionRequestedAKZone1AirForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestSITExtensionRequestedAKZone1AirForce(appCtx) + }, + "IntlHHGMoveDestSITExtensionRequestedAKZone2AirForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestSITExtensionRequestedAKZone2AirForce(appCtx) + }, + "IntlHHGMoveDestSITExtensionRequestedAKZone1SpaceForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestSITExtensionRequestedAKZone1SpaceForce(appCtx) + }, + "IntlHHGMoveDestSITExtensionRequestedAKZone2SpaceForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestSITExtensionRequestedAKZone2SpaceForce(appCtx) + }, + "IntlHHGMoveDestSITExtensionRequestedAKZone1USMC": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestSITExtensionRequestedAKZone1USMC(appCtx) + }, + "IntlHHGMoveDestSITExtensionRequestedAKZone2USMC": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestSITExtensionRequestedAKZone2USMC(appCtx) + }, + // iHHG with a PENDING excess weight notification + "IntlHHGMoveExcessWeightAKZone1Army": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveExcessWeightAKZone1Army(appCtx) + }, + "IntlHHGMoveExcessWeightAKZone2Army": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveExcessWeightAKZone2Army(appCtx) + }, + // iUB with a PENDING excess UB weight notification + "IntlUBMoveExcessWeightAKZone1Army": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlUBMoveExcessWeightAKZone1Army(appCtx) + }, + "IntlUBMoveExcessWeightAKZone2Army": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlUBMoveExcessWeightAKZone2Army(appCtx) + }, } func Actions() []string { diff --git a/pkg/testdatagen/testharness/make_move.go b/pkg/testdatagen/testharness/make_move.go index 3e2f5fed100..5c8909b983b 100644 --- a/pkg/testdatagen/testharness/make_move.go +++ b/pkg/testdatagen/testharness/make_move.go @@ -729,6 +729,197 @@ func MakeHHGMoveWithIntlCratingServiceItemsTOO(appCtx appcontext.AppContext) mod return *newmove } +// MakeHHGMoveWithIntlShuttleServiceItemsTOO is a function +// that creates an HHG move with international service items +// from the Prime for review by the TOO +func MakeHHGMoveWithIntlShuttleServiceItemsTOO(appCtx appcontext.AppContext) models.Move { + userUploader := newUserUploader(appCtx) + primeUploader := newPrimeUploader(appCtx) + userInfo := newUserInfo("customer") + + user := factory.BuildUser(appCtx.DB(), []factory.Customization{ + { + Model: models.User{ + OktaEmail: userInfo.email, + Active: true, + }, + }, + }, nil) + customer := factory.BuildExtendedServiceMember(appCtx.DB(), []factory.Customization{ + { + Model: models.ServiceMember{ + PersonalEmail: &userInfo.email, + FirstName: &userInfo.firstName, + LastName: &userInfo.lastName, + CacValidated: true, + }, + }, + { + Model: user, + LinkOnly: true, + }, + }, nil) + dependentsAuthorized := true + entitlements := factory.BuildEntitlement(appCtx.DB(), []factory.Customization{ + { + Model: models.Entitlement{ + DependentsAuthorized: &dependentsAuthorized, + }, + }, + }, nil) + orders := factory.BuildOrder(appCtx.DB(), []factory.Customization{ + { + Model: customer, + LinkOnly: true, + }, + { + Model: entitlements, + LinkOnly: true, + }, + { + Model: models.UserUpload{}, + ExtendedParams: &factory.UserUploadExtendedParams{ + UserUploader: userUploader, + AppContext: appCtx, + }, + }, + }, nil) + mto := factory.BuildMove(appCtx.DB(), []factory.Customization{ + { + Model: orders, + LinkOnly: true, + }, + { + Model: models.Move{ + Status: models.MoveStatusSUBMITTED, + }, + }, + }, nil) + estimatedWeight := unit.Pound(1400) + actualWeight := unit.Pound(2000) + actualPickupDate := time.Now().AddDate(0, 0, 1) + + MTOShipment := factory.BuildMTOShipment(appCtx.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + PrimeEstimatedWeight: &estimatedWeight, + PrimeActualWeight: &actualWeight, + ShipmentType: models.MTOShipmentTypeHHG, + Status: models.MTOShipmentStatusSubmitted, + ActualPickupDate: &actualPickupDate, + }, + }, + { + Model: mto, + LinkOnly: true, + }, + }, nil) + + agentUserInfo := newUserInfo("agent") + factory.BuildMTOAgent(appCtx.DB(), []factory.Customization{ + { + Model: MTOShipment, + LinkOnly: true, + }, + { + Model: models.MTOAgent{ + FirstName: &agentUserInfo.firstName, + LastName: &agentUserInfo.lastName, + Email: &agentUserInfo.email, + MTOAgentType: models.MTOAgentReleasing, + }, + }, + }, nil) + + paymentRequest := factory.BuildPaymentRequest(appCtx.DB(), []factory.Customization{ + { + Model: models.PaymentRequest{ + IsFinal: false, + Status: models.PaymentRequestStatusPending, + }, + }, + { + Model: mto, + LinkOnly: true, + }, + }, nil) + + _ = factory.BuildMTOServiceItem(appCtx.DB(), []factory.Customization{ + { + Model: mto, + LinkOnly: true, + }, + { + Model: MTOShipment, + LinkOnly: true, + }, + { + Model: models.ReService{ + ID: uuid.FromStringOrNil("22fc07ed-be15-4f50-b941-cbd38153b378"), // IDSHUT - International Destination Shuttle + }, + }, + }, nil) + + _ = factory.BuildMTOServiceItem(appCtx.DB(), []factory.Customization{ + { + Model: mto, + LinkOnly: true, + }, + { + Model: MTOShipment, + LinkOnly: true, + }, + { + Model: models.ReService{ + ID: uuid.FromStringOrNil("624a97c5-dfbf-4da9-a6e9-526b4f95af8d"), // IOSHUT - International Origin Shuttle + }, + }, + }, nil) + + factory.BuildPrimeUpload(appCtx.DB(), []factory.Customization{ + { + Model: paymentRequest, + LinkOnly: true, + }, + }, nil) + posImage := factory.BuildProofOfServiceDoc(appCtx.DB(), []factory.Customization{ + { + Model: paymentRequest, + LinkOnly: true, + }, + }, nil) + primeContractor := uuid.FromStringOrNil("5db13bb4-6d29-4bdb-bc81-262f4513ecf6") + + // Creates custom test.jpg prime upload + file := testdatagen.Fixture("test.jpg") + _, verrs, err := primeUploader.CreatePrimeUploadForDocument(appCtx, &posImage.ID, primeContractor, uploader.File{File: file}, uploader.AllowedTypesPaymentRequest) + if verrs.HasAny() || err != nil { + appCtx.Logger().Error("errors encountered saving test.jpg prime upload", zap.Error(err)) + } + + // Creates custom test.png prime upload + file = testdatagen.Fixture("test.png") + _, verrs, err = primeUploader.CreatePrimeUploadForDocument(appCtx, &posImage.ID, primeContractor, uploader.File{File: file}, uploader.AllowedTypesPaymentRequest) + if verrs.HasAny() || err != nil { + appCtx.Logger().Error("errors encountered saving test.png prime upload", zap.Error(err)) + } + + // re-fetch the move so that we ensure we have exactly what is in + // the db + newmove, err := models.FetchMove(appCtx.DB(), &auth.Session{}, mto.ID) + if err != nil { + log.Panic(fmt.Errorf("failed to fetch move: %w", err)) + } + + // load payment requests so tests can confirm + err = appCtx.DB().Load(newmove, "PaymentRequests") + if err != nil { + log.Panic(fmt.Errorf("failed to fetch move payment requestse: %w", err)) + } + + return *newmove +} + // MakeHHGMoveForTOOAfterActualPickupDate is a function // that creates an HHG move with an actual pickup date in the past for diversion testing // copied almost verbatim from e2ebasic createHHGMoveWithServiceItemsAndPaymentRequestsAndFiles @@ -8916,6 +9107,7 @@ func MakeBasicInternationalHHGMoveWithServiceItemsandPaymentRequestsForTIO(appCt ihpkCost := unit.Cents(298800) ihupkCost := unit.Cents(33280) poefscCost := unit.Cents(25000) + idshutCost := unit.Cents(623) // Create Customer userInfo := newUserInfo("customer") @@ -9250,6 +9442,47 @@ func MakeBasicInternationalHHGMoveWithServiceItemsandPaymentRequestsForTIO(appCt }, }, nil) + // Shuttling service item + approvedAtTime := time.Now() + idshut := factory.BuildMTOServiceItem(appCtx.DB(), []factory.Customization{ + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusApproved, + ApprovedAt: &approvedAtTime, + EstimatedWeight: &estimatedWeight, + ActualWeight: &actualWeight, + }, + }, + { + Model: mto, + LinkOnly: true, + }, + { + Model: mtoShipmentHHG, + LinkOnly: true, + }, + { + Model: models.ReService{ + ID: uuid.FromStringOrNil("22fc07ed-be15-4f50-b941-cbd38153b378"), // IDSHUT - International Destination Shuttle + }, + }, + }, nil) + + factory.BuildPaymentServiceItemWithParams(appCtx.DB(), models.ReServiceCodeIDSHUT, + basicPaymentServiceItemParams, []factory.Customization{ + { + Model: models.PaymentServiceItem{ + PriceCents: &idshutCost, + }, + }, { + Model: paymentRequestHHG, + LinkOnly: true, + }, { + Model: idshut, + LinkOnly: true, + }, + }, nil) + basicPortFuelSurchargePaymentServiceItemParams := []factory.CreatePaymentServiceItemParams{ { Key: models.ServiceItemParamNameContractCode, @@ -9380,3 +9613,2078 @@ func MakeBasicInternationalHHGMoveWithServiceItemsandPaymentRequestsForTIO(appCt return *newmove } + +// MakeIntlHHGMoveWithCratingUncratingServiceItemsAndPaymentRequestsForTIO creates an iHHG move +// that has been approved by TOO & prime has requested payment for intl crating and uncrating service items +func MakeIntlHHGMoveWithCratingUncratingServiceItemsAndPaymentRequestsForTIO(appCtx appcontext.AppContext) models.Move { + userUploader := newUserUploader(appCtx) + + // Create Customer + userInfo := newUserInfo("customer") + customer := factory.BuildExtendedServiceMember(appCtx.DB(), []factory.Customization{ + { + Model: models.ServiceMember{ + PersonalEmail: &userInfo.email, + FirstName: &userInfo.firstName, + LastName: &userInfo.lastName, + CacValidated: true, + }, + }, + }, nil) + + // address setup + addressAK := factory.BuildAddress(appCtx.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "123 Cold St", + City: "Anchorage", + State: "AK", + PostalCode: "99505", + }, + }, + }, nil) + destDutyLocationAK := factory.BuildDutyLocation(appCtx.DB(), []factory.Customization{ + { + Model: addressAK, + LinkOnly: true, + }, + }, nil) + + // orders setup using AK destination duty location + orders := factory.BuildOrder(appCtx.DB(), []factory.Customization{ + { + Model: customer, + LinkOnly: true, + }, + { + Model: models.UserUpload{}, + ExtendedParams: &factory.UserUploadExtendedParams{ + UserUploader: userUploader, + AppContext: appCtx, + }, + }, + { + Model: models.Order{ + NewDutyLocationID: destDutyLocationAK.ID, + }, + }, + }, nil) + + mto := factory.BuildMove(appCtx.DB(), []factory.Customization{ + { + Model: orders, + LinkOnly: true, + }, + { + Model: models.Move{ + AvailableToPrimeAt: models.TimePointer(time.Now()), + }, + }, + }, nil) + + shipmentPickupAddress := factory.BuildAddress(appCtx.DB(), []factory.Customization{ + { + Model: models.Address{ + // This is a postal code that maps to the default office user gbloc KKFA in the PostalCodeToGBLOC table + PostalCode: "85004", + }, + }, + }, nil) + alaskaDestAddress := factory.BuildAddress(appCtx.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "123 Cold St", + City: "Anchorage", + State: "AK", + PostalCode: "99505", + IsOconus: models.BoolPointer(true), + }, + }, + }, nil) + + estimatedWeight := unit.Pound(2000) + actualWeight := unit.Pound(2000) + mtoShipmentHHG := factory.BuildMTOShipment(appCtx.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + PrimeEstimatedWeight: &estimatedWeight, + PrimeActualWeight: &actualWeight, + ShipmentType: models.MTOShipmentTypeHHG, + ApprovedDate: models.TimePointer(time.Now()), + MarketCode: models.MarketCodeInternational, + }, + }, + { + Model: shipmentPickupAddress, + LinkOnly: true, + Type: &factory.Addresses.PickupAddress, + }, + { + Model: alaskaDestAddress, + LinkOnly: true, + Type: &factory.Addresses.DeliveryAddress, + }, + { + Model: mto, + LinkOnly: true, + }, + }, nil) + + // Create Releasing Agent + agentUserInfo := newUserInfo("agent") + factory.BuildMTOAgent(appCtx.DB(), []factory.Customization{ + { + Model: mtoShipmentHHG, + LinkOnly: true, + }, + { + Model: models.MTOAgent{ + ID: uuid.Must(uuid.NewV4()), + FirstName: &agentUserInfo.firstName, + LastName: &agentUserInfo.lastName, + Email: &agentUserInfo.email, + MTOAgentType: models.MTOAgentReleasing, + }, + }, + }, nil) + + paymentRequestHHG := factory.BuildPaymentRequest(appCtx.DB(), []factory.Customization{ + { + Model: models.PaymentRequest{ + IsFinal: false, + Status: models.PaymentRequestStatusPending, + RejectionReason: nil, + }, + }, + { + Model: mto, + LinkOnly: true, + }, + }, nil) + + // for soft deleted proof of service docs + factory.BuildPrimeUpload(appCtx.DB(), []factory.Customization{ + { + Model: paymentRequestHHG, + LinkOnly: true, + }, + }, []factory.Trait{factory.GetTraitPrimeUploadDeleted}) + + currentTime := time.Now() + + cratingPaymentServiceItemParams := []factory.CreatePaymentServiceItemParams{ + { + Key: models.ServiceItemParamNameContractCode, + KeyType: models.ServiceItemParamTypeString, + Value: factory.DefaultContractCode, + }, + { + Key: models.ServiceItemParamNameEscalationCompounded, + KeyType: models.ServiceItemParamTypeString, + Value: strconv.FormatFloat(1.125, 'f', 5, 64), + }, + { + Key: models.ServiceItemParamNamePriceRateOrFactor, + KeyType: models.ServiceItemParamTypeString, + Value: "1.71", + }, + { + Key: models.ServiceItemParamNameCubicFeetBilled, + KeyType: models.ServiceItemParamTypeDecimal, + Value: "4.00", + }, + { + Key: models.ServiceItemParamNameCubicFeetCrating, + KeyType: models.ServiceItemParamTypeDecimal, + Value: "1", + }, + { + Key: models.ServiceItemParamNameReferenceDate, + KeyType: models.ServiceItemParamTypeDate, + Value: currentTime.Format("2006-01-02"), + }, + { + Key: models.ServiceItemParamNameStandaloneCrate, + KeyType: models.ServiceItemParamTypeBoolean, + Value: strconv.FormatBool(true), + }, + { + Key: models.ServiceItemParamNameStandaloneCrateCap, + KeyType: models.ServiceItemParamTypeInteger, + Value: strconv.FormatInt(100000, 10), + }, + { + Key: models.ServiceItemParamNameMarketOrigin, + KeyType: models.ServiceItemParamTypeString, + Value: "O", + }, + { + Key: models.ServiceItemParamNameExternalCrate, + KeyType: models.ServiceItemParamTypeBoolean, + Value: strconv.FormatBool(true), + }, + { + Key: models.ServiceItemParamNameDimensionHeight, + KeyType: models.ServiceItemParamTypeString, + Value: "1", + }, + { + Key: models.ServiceItemParamNameDimensionLength, + KeyType: models.ServiceItemParamTypeString, + Value: "1", + }, + { + Key: models.ServiceItemParamNameDimensionWidth, + KeyType: models.ServiceItemParamTypeString, + Value: "1", + }, + } + desc := "description test" + icrt := factory.BuildMTOServiceItem(appCtx.DB(), []factory.Customization{ + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusApproved, + Description: &desc, + StandaloneCrate: models.BoolPointer(true), + ExternalCrate: models.BoolPointer(true), + }, + }, + { + Model: mto, + LinkOnly: true, + }, + { + Model: mtoShipmentHHG, + LinkOnly: true, + }, + { + Model: models.ReService{ + Code: models.ReServiceCodeICRT, + }, + }, + }, nil) + + factory.BuildPaymentServiceItemWithParams(appCtx.DB(), models.ReServiceCodeICRT, + cratingPaymentServiceItemParams, []factory.Customization{ + { + Model: mto, + LinkOnly: true, + }, + { + Model: mtoShipmentHHG, + LinkOnly: true, + }, + { + Model: paymentRequestHHG, + LinkOnly: true, + }, + { + Model: icrt, + LinkOnly: true, + }, + }, nil) + + iucrt := factory.BuildMTOServiceItem(appCtx.DB(), []factory.Customization{ + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusApproved, + Description: &desc, + }, + }, + { + Model: mto, + LinkOnly: true, + }, + { + Model: mtoShipmentHHG, + LinkOnly: true, + }, + { + Model: models.ReService{ + Code: models.ReServiceCodeIUCRT, + }, + }, + }, nil) + + unCratingPaymentServiceItemParams := []factory.CreatePaymentServiceItemParams{ + { + Key: models.ServiceItemParamNameContractCode, + KeyType: models.ServiceItemParamTypeString, + Value: factory.DefaultContractCode, + }, + { + Key: models.ServiceItemParamNameEscalationCompounded, + KeyType: models.ServiceItemParamTypeString, + Value: strconv.FormatFloat(1.125, 'f', 5, 64), + }, + { + Key: models.ServiceItemParamNamePriceRateOrFactor, + KeyType: models.ServiceItemParamTypeString, + Value: "1.71", + }, + { + Key: models.ServiceItemParamNameCubicFeetBilled, + KeyType: models.ServiceItemParamTypeDecimal, + Value: "8", + }, + { + Key: models.ServiceItemParamNameReferenceDate, + KeyType: models.ServiceItemParamTypeDate, + Value: currentTime.Format("2006-01-02"), + }, + { + Key: models.ServiceItemParamNameMarketDest, + KeyType: models.ServiceItemParamTypeString, + Value: "O", + }, + { + Key: models.ServiceItemParamNameDimensionHeight, + KeyType: models.ServiceItemParamTypeString, + Value: "2", + }, + { + Key: models.ServiceItemParamNameDimensionLength, + KeyType: models.ServiceItemParamTypeString, + Value: "2", + }, + { + Key: models.ServiceItemParamNameDimensionWidth, + KeyType: models.ServiceItemParamTypeString, + Value: "2", + }, + } + + factory.BuildPaymentServiceItemWithParams(appCtx.DB(), models.ReServiceCodeIUCRT, + unCratingPaymentServiceItemParams, []factory.Customization{ + { + Model: mto, + LinkOnly: true, + }, + { + Model: mtoShipmentHHG, + LinkOnly: true, + }, + { + Model: paymentRequestHHG, + LinkOnly: true, + }, + { + Model: iucrt, + LinkOnly: true, + }, + }, nil) + + // re-fetch the move so that we ensure we have exactly what is in + // the db + newmove, err := models.FetchMove(appCtx.DB(), &auth.Session{}, mto.ID) + if err != nil { + log.Panic(fmt.Errorf("failed to fetch move: %w", err)) + } + + // load payment requests so tests can confirm + err = appCtx.DB().Load(newmove, "PaymentRequests") + if err != nil { + log.Panic(fmt.Errorf("failed to fetch move payment requestse: %w", err)) + } + + return *newmove +} + +// makeIntlHHGMoveCONUSToAKSubmitted creates an international HHG move +// with the given affiliation and destination address +// basic iHHG move that will require TOO approval +func makeIntlHHGMoveCONUSToAKSubmitted( + appCtx appcontext.AppContext, + affiliation models.ServiceMemberAffiliation, + streetAddress, city, state, postalCode string, +) models.Move { + userUploader := newUserUploader(appCtx) + userInfo := newUserInfo("customer") + + user := factory.BuildUser(appCtx.DB(), []factory.Customization{ + { + Model: models.User{ + OktaEmail: userInfo.email, + Active: true, + }, + }, + }, nil) + + customer := factory.BuildExtendedServiceMember(appCtx.DB(), []factory.Customization{ + { + Model: models.ServiceMember{ + PersonalEmail: &userInfo.email, + FirstName: &userInfo.firstName, + LastName: &userInfo.lastName, + CacValidated: true, + Affiliation: &affiliation, + }, + }, + { + Model: user, + LinkOnly: true, + }, + }, nil) + + dependentsAuthorized := true + sitDaysAllowance := 90 + entitlements := factory.BuildEntitlement(appCtx.DB(), []factory.Customization{ + { + Model: models.Entitlement{ + DependentsAuthorized: &dependentsAuthorized, + StorageInTransit: &sitDaysAllowance, + }, + }, + }, nil) + + orders := factory.BuildOrder(appCtx.DB(), []factory.Customization{ + { + Model: customer, + LinkOnly: true, + }, + { + Model: entitlements, + LinkOnly: true, + }, + { + Model: models.UserUpload{}, + ExtendedParams: &factory.UserUploadExtendedParams{ + UserUploader: userUploader, + AppContext: appCtx, + }, + }, + }, nil) + + now := time.Now() + move := factory.BuildMove(appCtx.DB(), []factory.Customization{ + { + Model: orders, + LinkOnly: true, + }, + { + Model: models.Move{ + Status: models.MoveStatusServiceCounselingCompleted, + AvailableToPrimeAt: &now, + }, + }, + }, nil) + + requestedPickupDate := now.AddDate(0, 3, 0) + requestedDeliveryDate := requestedPickupDate.AddDate(0, 1, 0) + + // build the destination address using the passed-in parameters. + address := factory.BuildAddress(appCtx.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: streetAddress, + City: city, + State: state, + PostalCode: postalCode, + IsOconus: models.BoolPointer(true), + }, + }, + }, nil) + + shipment := factory.BuildMTOShipment(appCtx.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + ShipmentType: models.MTOShipmentTypeHHG, + Status: models.MTOShipmentStatusSubmitted, + RequestedPickupDate: &requestedPickupDate, + RequestedDeliveryDate: &requestedDeliveryDate, + SITDaysAllowance: &sitDaysAllowance, + DestinationAddressID: &address.ID, + MarketCode: models.MarketCodeInternational, + }, + }, + { + Model: move, + LinkOnly: true, + }, + }, nil) + + agentUserInfo := newUserInfo("agent") + factory.BuildMTOAgent(appCtx.DB(), []factory.Customization{ + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.MTOAgent{ + FirstName: &agentUserInfo.firstName, + LastName: &agentUserInfo.lastName, + Email: &agentUserInfo.email, + MTOAgentType: models.MTOAgentReleasing, + }, + }, + }, nil) + + return move +} + +// these create an iHHG move with selected affiliation, destination of either Anchorage, AK (Zone I) or Fairbanks, AK (Zone II) +// contains an HHG shipment in SUBMITTED status that requires TOO approval +func MakeIntlHHGMoveDestAKZone1Army(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveCONUSToAKSubmitted(appCtx, models.AffiliationARMY, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveDestAKZone2Army(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveCONUSToAKSubmitted(appCtx, models.AffiliationARMY, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveDestAKZone1AirForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveCONUSToAKSubmitted(appCtx, models.AffiliationAIRFORCE, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveDestAKZone2AirForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveCONUSToAKSubmitted(appCtx, models.AffiliationAIRFORCE, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveDestAKZone1SpaceForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveCONUSToAKSubmitted(appCtx, models.AffiliationSPACEFORCE, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveDestAKZone2SpaceForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveCONUSToAKSubmitted(appCtx, models.AffiliationSPACEFORCE, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveDestAKZone1USMC(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveCONUSToAKSubmitted(appCtx, models.AffiliationMARINES, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveDestAKZone2USMC(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveCONUSToAKSubmitted(appCtx, models.AffiliationMARINES, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +// makeIntlHHGMoveAKToCONUSSubmitted creates an international HHG move +// with the given affiliation and pickup address +// basic iHHG move that will require TOO approval +func makeIntlHHGMoveAKToCONUSSubmitted( + appCtx appcontext.AppContext, + affiliation models.ServiceMemberAffiliation, + streetAddress, city, state, postalCode string, +) models.Move { + userUploader := newUserUploader(appCtx) + userInfo := newUserInfo("customer") + + user := factory.BuildUser(appCtx.DB(), []factory.Customization{ + { + Model: models.User{ + OktaEmail: userInfo.email, + Active: true, + }, + }, + }, nil) + + customer := factory.BuildExtendedServiceMember(appCtx.DB(), []factory.Customization{ + { + Model: models.ServiceMember{ + PersonalEmail: &userInfo.email, + FirstName: &userInfo.firstName, + LastName: &userInfo.lastName, + CacValidated: true, + Affiliation: &affiliation, + }, + }, + { + Model: user, + LinkOnly: true, + }, + }, nil) + + dependentsAuthorized := true + sitDaysAllowance := 90 + entitlements := factory.BuildEntitlement(appCtx.DB(), []factory.Customization{ + { + Model: models.Entitlement{ + DependentsAuthorized: &dependentsAuthorized, + StorageInTransit: &sitDaysAllowance, + }, + }, + }, nil) + + orders := factory.BuildOrder(appCtx.DB(), []factory.Customization{ + { + Model: customer, + LinkOnly: true, + }, + { + Model: entitlements, + LinkOnly: true, + }, + { + Model: models.UserUpload{}, + ExtendedParams: &factory.UserUploadExtendedParams{ + UserUploader: userUploader, + AppContext: appCtx, + }, + }, + }, nil) + + now := time.Now() + move := factory.BuildMove(appCtx.DB(), []factory.Customization{ + { + Model: orders, + LinkOnly: true, + }, + { + Model: models.Move{ + Status: models.MoveStatusServiceCounselingCompleted, + AvailableToPrimeAt: &now, + }, + }, + }, nil) + + requestedPickupDate := now.AddDate(0, 3, 0) + requestedDeliveryDate := requestedPickupDate.AddDate(0, 1, 0) + + // build the pickup address using the passed-in parameters. + address := factory.BuildAddress(appCtx.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: streetAddress, + City: city, + State: state, + PostalCode: postalCode, + IsOconus: models.BoolPointer(true), + }, + }, + }, nil) + + shipment := factory.BuildMTOShipment(appCtx.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + ShipmentType: models.MTOShipmentTypeHHG, + Status: models.MTOShipmentStatusSubmitted, + RequestedPickupDate: &requestedPickupDate, + RequestedDeliveryDate: &requestedDeliveryDate, + SITDaysAllowance: &sitDaysAllowance, + PickupAddressID: &address.ID, + MarketCode: models.MarketCodeInternational, + }, + }, + { + Model: move, + LinkOnly: true, + }, + }, nil) + + agentUserInfo := newUserInfo("agent") + factory.BuildMTOAgent(appCtx.DB(), []factory.Customization{ + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.MTOAgent{ + FirstName: &agentUserInfo.firstName, + LastName: &agentUserInfo.lastName, + Email: &agentUserInfo.email, + MTOAgentType: models.MTOAgentReleasing, + }, + }, + }, nil) + + return move +} + +// these create an iHHG move with selected affiliation, pickup of either Anchorage, AK (Zone I) or Fairbanks, AK (Zone II) +// contains an HHG shipment in SUBMITTED status that requires TOO approval +func MakeIntlHHGMovePickupAKZone1Army(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveAKToCONUSSubmitted(appCtx, models.AffiliationARMY, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMovePickupAKZone2Army(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveAKToCONUSSubmitted(appCtx, models.AffiliationARMY, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMovePickupAKZone1AirForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveAKToCONUSSubmitted(appCtx, models.AffiliationAIRFORCE, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMovePickupAKZone2AirForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveAKToCONUSSubmitted(appCtx, models.AffiliationAIRFORCE, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMovePickupAKZone1SpaceForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveAKToCONUSSubmitted(appCtx, models.AffiliationSPACEFORCE, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMovePickupAKZone2SpaceForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveAKToCONUSSubmitted(appCtx, models.AffiliationSPACEFORCE, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMovePickupAKZone1USMC(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveAKToCONUSSubmitted(appCtx, models.AffiliationMARINES, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMovePickupAKZone2USMC(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveAKToCONUSSubmitted(appCtx, models.AffiliationMARINES, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +// makeIntlHHGMoveWithSITRequested creates an international HHG move +// with the given affiliation and destination address +// parameters determine if ONLY origin or ONLY dest SIT service items are created +// or BOTH origin & dest are created +func makeIntlHHGMoveWithSITRequested( + appCtx appcontext.AppContext, + isOrigin bool, + isBoth bool, + affiliation models.ServiceMemberAffiliation, + streetAddress, city, state, postalCode string, +) models.Move { + userUploader := newUserUploader(appCtx) + userInfo := newUserInfo("customer") + + user := factory.BuildUser(appCtx.DB(), []factory.Customization{ + { + Model: models.User{ + OktaEmail: userInfo.email, + Active: true, + }, + }, + }, nil) + + customer := factory.BuildExtendedServiceMember(appCtx.DB(), []factory.Customization{ + { + Model: models.ServiceMember{ + PersonalEmail: &userInfo.email, + FirstName: &userInfo.firstName, + LastName: &userInfo.lastName, + CacValidated: true, + Affiliation: &affiliation, + }, + }, + { + Model: user, + LinkOnly: true, + }, + }, nil) + + dependentsAuthorized := true + sitDaysAllowance := 90 + entitlements := factory.BuildEntitlement(appCtx.DB(), []factory.Customization{ + { + Model: models.Entitlement{ + DependentsAuthorized: &dependentsAuthorized, + StorageInTransit: &sitDaysAllowance, + }, + }, + }, nil) + + orders := factory.BuildOrder(appCtx.DB(), []factory.Customization{ + { + Model: customer, + LinkOnly: true, + }, + { + Model: entitlements, + LinkOnly: true, + }, + { + Model: models.UserUpload{}, + ExtendedParams: &factory.UserUploadExtendedParams{ + UserUploader: userUploader, + AppContext: appCtx, + }, + }, + }, nil) + + now := time.Now() + move := factory.BuildMove(appCtx.DB(), []factory.Customization{ + { + Model: orders, + LinkOnly: true, + }, + { + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + AvailableToPrimeAt: &now, + }, + }, + }, nil) + + estimatedWeight := unit.Pound(2000) + actualWeight := unit.Pound(2000) + requestedPickupDate := now.AddDate(0, 3, 0) + requestedDeliveryDate := requestedPickupDate.AddDate(0, 1, 0) + + // build the destination address using the passed-in parameters. + address := factory.BuildAddress(appCtx.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: streetAddress, + City: city, + State: state, + PostalCode: postalCode, + IsOconus: models.BoolPointer(true), + }, + }, + }, nil) + + shipment := factory.BuildMTOShipment(appCtx.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + PrimeEstimatedWeight: &estimatedWeight, + PrimeActualWeight: &actualWeight, + ShipmentType: models.MTOShipmentTypeHHG, + Status: models.MTOShipmentStatusApproved, + RequestedPickupDate: &requestedPickupDate, + RequestedDeliveryDate: &requestedDeliveryDate, + SITDaysAllowance: &sitDaysAllowance, + DestinationAddressID: &address.ID, + MarketCode: models.MarketCodeInternational, + }, + }, + { + Model: move, + LinkOnly: true, + }, + }, nil) + + agentUserInfo := newUserInfo("agent") + factory.BuildMTOAgent(appCtx.DB(), []factory.Customization{ + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.MTOAgent{ + FirstName: &agentUserInfo.firstName, + LastName: &agentUserInfo.lastName, + Email: &agentUserInfo.email, + MTOAgentType: models.MTOAgentReleasing, + }, + }, + }, nil) + + // build the origin/destination SIT service items and update their status to SUBMITTED + oneMonthLater := now.AddDate(0, 1, 0) + var sitItems models.MTOServiceItems + if isBoth { + sitItems = factory.BuildOriginSITServiceItems(appCtx.DB(), move, shipment, &oneMonthLater, nil) + destSitItems := factory.BuildDestSITServiceItems(appCtx.DB(), move, shipment, &oneMonthLater, nil) + sitItems = append(sitItems, destSitItems...) + } else if isOrigin { + sitItems = factory.BuildOriginSITServiceItems(appCtx.DB(), move, shipment, &oneMonthLater, nil) + } else { + sitItems = factory.BuildDestSITServiceItems(appCtx.DB(), move, shipment, &oneMonthLater, nil) + } + for i := range sitItems { + sitItems[i].Status = models.MTOServiceItemStatusSubmitted + if err := appCtx.DB().Update(&sitItems[i]); err != nil { + log.Panic(fmt.Errorf("failed to update sit service item: %w", err)) + } + } + + return move +} + +// these create an iHHG move with selected affiliation, destination of either Anchorage, AK (Zone I) or Fairbanks, AK (Zone II) +// containing all 4 international origin SIT service items in SUBMITTED status +func MakeIntlHHGMoveOriginSITRequestedAKZone1Army(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveWithSITRequested(appCtx, true, false, models.AffiliationARMY, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveOriginSITRequestedAKZone2Army(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveWithSITRequested(appCtx, true, false, models.AffiliationARMY, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveOriginSITRequestedAKZone1AirForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveWithSITRequested(appCtx, true, false, models.AffiliationAIRFORCE, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveOriginSITRequestedAKZone2AirForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveWithSITRequested(appCtx, true, false, models.AffiliationAIRFORCE, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveOriginSITRequestedAKZone1SpaceForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveWithSITRequested(appCtx, true, false, models.AffiliationSPACEFORCE, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveOriginSITRequestedAKZone2SpaceForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveWithSITRequested(appCtx, true, false, models.AffiliationSPACEFORCE, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveOriginSITRequestedAKZone1USMC(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveWithSITRequested(appCtx, true, false, models.AffiliationMARINES, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveOriginSITRequestedAKZone2USMC(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveWithSITRequested(appCtx, true, false, models.AffiliationMARINES, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +// these create an iHHG move with selected affiliation, destination of either Anchorage, AK (Zone I) or Fairbanks, AK (Zone II) +// containing all 4 international destination SIT service items in SUBMITTED status +func MakeIntlHHGMoveDestSITRequestedAKZone1Army(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveWithSITRequested(appCtx, false, false, models.AffiliationARMY, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveDestSITRequestedAKZone2Army(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveWithSITRequested(appCtx, false, false, models.AffiliationARMY, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveDestSITRequestedAKZone1AirForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveWithSITRequested(appCtx, false, false, models.AffiliationAIRFORCE, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveDestSITRequestedAKZone2AirForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveWithSITRequested(appCtx, false, false, models.AffiliationAIRFORCE, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveDestSITRequestedAKZone1SpaceForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveWithSITRequested(appCtx, false, false, models.AffiliationSPACEFORCE, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveDestSITRequestedAKZone2SpaceForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveWithSITRequested(appCtx, false, false, models.AffiliationSPACEFORCE, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveDestSITRequestedAKZone1USMC(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveWithSITRequested(appCtx, false, false, models.AffiliationMARINES, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveDestSITRequestedAKZone2USMC(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveWithSITRequested(appCtx, false, false, models.AffiliationMARINES, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +// these create an iHHG move with selected affiliation, destination of either Anchorage, AK (Zone I) or Fairbanks, AK (Zone II) +// containing all 4 international destination SIT service items AND all 4 origin SIT service items in SUBMITTED status +func MakeIntlHHGMoveBothSITRequestedAKZone1Army(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveWithSITRequested(appCtx, false, true, models.AffiliationARMY, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveBothSITRequestedAKZone2Army(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveWithSITRequested(appCtx, false, true, models.AffiliationARMY, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveBothSITRequestedAKZone1AirForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveWithSITRequested(appCtx, false, true, models.AffiliationAIRFORCE, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveBothSITRequestedAKZone2AirForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveWithSITRequested(appCtx, false, true, models.AffiliationAIRFORCE, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveBothSITRequestedAKZone1SpaceForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveWithSITRequested(appCtx, false, true, models.AffiliationSPACEFORCE, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveBothSITRequestedAKZone2SpaceForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveWithSITRequested(appCtx, false, true, models.AffiliationSPACEFORCE, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveBothSITRequestedAKZone1USMC(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveWithSITRequested(appCtx, false, true, models.AffiliationMARINES, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveBothSITRequestedAKZone2USMC(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveWithSITRequested(appCtx, false, true, models.AffiliationMARINES, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +// makeIntlHHGMoveShuttleRequested creates an international HHG move +// with the given affiliation and destination address +// contains either origin, destination, or BOTH origin/destination shuttle in SUBMITTED status +func makeIntlHHGMoveShuttleRequested( + appCtx appcontext.AppContext, + isOrigin bool, + isBoth bool, + affiliation models.ServiceMemberAffiliation, + streetAddress, city, state, postalCode string, +) models.Move { + userUploader := newUserUploader(appCtx) + userInfo := newUserInfo("customer") + + user := factory.BuildUser(appCtx.DB(), []factory.Customization{ + { + Model: models.User{ + OktaEmail: userInfo.email, + Active: true, + }, + }, + }, nil) + + customer := factory.BuildExtendedServiceMember(appCtx.DB(), []factory.Customization{ + { + Model: models.ServiceMember{ + PersonalEmail: &userInfo.email, + FirstName: &userInfo.firstName, + LastName: &userInfo.lastName, + CacValidated: true, + Affiliation: &affiliation, + }, + }, + { + Model: user, + LinkOnly: true, + }, + }, nil) + + dependentsAuthorized := true + sitDaysAllowance := 90 + entitlements := factory.BuildEntitlement(appCtx.DB(), []factory.Customization{ + { + Model: models.Entitlement{ + DependentsAuthorized: &dependentsAuthorized, + StorageInTransit: &sitDaysAllowance, + }, + }, + }, nil) + + orders := factory.BuildOrder(appCtx.DB(), []factory.Customization{ + { + Model: customer, + LinkOnly: true, + }, + { + Model: entitlements, + LinkOnly: true, + }, + { + Model: models.UserUpload{}, + ExtendedParams: &factory.UserUploadExtendedParams{ + UserUploader: userUploader, + AppContext: appCtx, + }, + }, + }, nil) + + now := time.Now() + move := factory.BuildMove(appCtx.DB(), []factory.Customization{ + { + Model: orders, + LinkOnly: true, + }, + { + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + AvailableToPrimeAt: &now, + }, + }, + }, nil) + + estimatedWeight := unit.Pound(2000) + actualWeight := unit.Pound(2000) + requestedPickupDate := now.AddDate(0, 3, 0) + requestedDeliveryDate := requestedPickupDate.AddDate(0, 1, 0) + + // build the destination address using the passed-in parameters. + address := factory.BuildAddress(appCtx.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: streetAddress, + City: city, + State: state, + PostalCode: postalCode, + IsOconus: models.BoolPointer(true), + }, + }, + }, nil) + + shipment := factory.BuildMTOShipment(appCtx.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + PrimeEstimatedWeight: &estimatedWeight, + PrimeActualWeight: &actualWeight, + ShipmentType: models.MTOShipmentTypeHHG, + Status: models.MTOShipmentStatusApproved, + RequestedPickupDate: &requestedPickupDate, + RequestedDeliveryDate: &requestedDeliveryDate, + SITDaysAllowance: &sitDaysAllowance, + DestinationAddressID: &address.ID, + MarketCode: models.MarketCodeInternational, + }, + }, + { + Model: move, + LinkOnly: true, + }, + }, nil) + + agentUserInfo := newUserInfo("agent") + factory.BuildMTOAgent(appCtx.DB(), []factory.Customization{ + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.MTOAgent{ + FirstName: &agentUserInfo.firstName, + LastName: &agentUserInfo.lastName, + Email: &agentUserInfo.email, + MTOAgentType: models.MTOAgentReleasing, + }, + }, + }, nil) + + // build the destination shuttle service item in SUBMITTED status + if isBoth { + factory.BuildMTOServiceItem(appCtx.DB(), []factory.Customization{ + { + Model: models.ReService{ + Code: models.ReServiceCodeIOSHUT, + }, + }, + { + Model: models.MTOServiceItem{ + Reason: models.StringPointer("internatioanl destination shuttle"), + EstimatedWeight: models.PoundPointer(1000), + ActualWeight: models.PoundPointer(1000), + Status: models.MTOServiceItemStatusSubmitted, + }, + }, + { + Model: move, + LinkOnly: true, + }, + { + Model: shipment, + LinkOnly: true, + }, + }, nil) + + factory.BuildMTOServiceItem(appCtx.DB(), []factory.Customization{ + { + Model: models.ReService{ + Code: models.ReServiceCodeIDSHUT, + }, + }, + { + Model: models.MTOServiceItem{ + Reason: models.StringPointer("internatioanl destination shuttle"), + EstimatedWeight: models.PoundPointer(1000), + ActualWeight: models.PoundPointer(1000), + Status: models.MTOServiceItemStatusSubmitted, + }, + }, + { + Model: move, + LinkOnly: true, + }, + { + Model: shipment, + LinkOnly: true, + }, + }, nil) + } else if isOrigin { + factory.BuildMTOServiceItem(appCtx.DB(), []factory.Customization{ + { + Model: models.ReService{ + Code: models.ReServiceCodeIOSHUT, + }, + }, + { + Model: models.MTOServiceItem{ + Reason: models.StringPointer("internatioanl destination shuttle"), + EstimatedWeight: models.PoundPointer(1000), + ActualWeight: models.PoundPointer(1000), + Status: models.MTOServiceItemStatusSubmitted, + }, + }, + { + Model: move, + LinkOnly: true, + }, + { + Model: shipment, + LinkOnly: true, + }, + }, nil) + } else { + factory.BuildMTOServiceItem(appCtx.DB(), []factory.Customization{ + { + Model: models.ReService{ + Code: models.ReServiceCodeIDSHUT, + }, + }, + { + Model: models.MTOServiceItem{ + Reason: models.StringPointer("internatioanl destination shuttle"), + EstimatedWeight: models.PoundPointer(1000), + ActualWeight: models.PoundPointer(1000), + Status: models.MTOServiceItemStatusSubmitted, + }, + }, + { + Model: move, + LinkOnly: true, + }, + { + Model: shipment, + LinkOnly: true, + }, + }, nil) + } + + return move +} + +// these create an iHHG move with selected affiliation, destination of either Anchorage, AK (Zone I) or Fairbanks, AK (Zone II) +// containing an international origin shuttle request in SUBMITTED status +func MakeIntlHHGMoveOriginShuttleRequestedAKZone1Army(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveShuttleRequested(appCtx, true, false, models.AffiliationARMY, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveOriginShuttleRequestedAKZone2Army(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveShuttleRequested(appCtx, true, false, models.AffiliationARMY, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveOriginShuttleRequestedAKZone1AirForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveShuttleRequested(appCtx, true, false, models.AffiliationAIRFORCE, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveOriginShuttleRequestedAKZone2AirForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveShuttleRequested(appCtx, true, false, models.AffiliationAIRFORCE, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveOriginShuttleRequestedAKZone1SpaceForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveShuttleRequested(appCtx, true, false, models.AffiliationSPACEFORCE, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveOriginShuttleRequestedAKZone2SpaceForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveShuttleRequested(appCtx, true, false, models.AffiliationSPACEFORCE, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveOriginShuttleRequestedAKZone1USMC(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveShuttleRequested(appCtx, true, false, models.AffiliationMARINES, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveOriginShuttleRequestedAKZone2USMC(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveShuttleRequested(appCtx, true, false, models.AffiliationMARINES, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +// these create an iHHG move with selected affiliation, destination of either Anchorage, AK (Zone I) or Fairbanks, AK (Zone II) +// containing an international destination shuttle request in SUBMITTED status +func MakeIntlHHGMoveDestShuttleRequestedAKZone1Army(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveShuttleRequested(appCtx, false, false, models.AffiliationARMY, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveDestShuttleRequestedAKZone2Army(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveShuttleRequested(appCtx, false, false, models.AffiliationARMY, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveDestShuttleRequestedAKZone1AirForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveShuttleRequested(appCtx, false, false, models.AffiliationAIRFORCE, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveDestShuttleRequestedAKZone2AirForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveShuttleRequested(appCtx, false, false, models.AffiliationAIRFORCE, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveDestShuttleRequestedAKZone1SpaceForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveShuttleRequested(appCtx, false, false, models.AffiliationSPACEFORCE, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveDestShuttleRequestedAKZone2SpaceForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveShuttleRequested(appCtx, false, false, models.AffiliationSPACEFORCE, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveDestShuttleRequestedAKZone1USMC(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveShuttleRequested(appCtx, false, false, models.AffiliationMARINES, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveDestShuttleRequestedAKZone2USMC(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveShuttleRequested(appCtx, false, false, models.AffiliationMARINES, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +// these create an iHHG move with selected affiliation, destination of either Anchorage, AK (Zone I) or Fairbanks, AK (Zone II) +// containing BOTH international origin & destination shuttle requests in SUBMITTED status +func MakeIntlHHGMoveBothShuttleRequestedAKZone1Army(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveShuttleRequested(appCtx, false, true, models.AffiliationARMY, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveBothShuttleRequestedAKZone2Army(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveShuttleRequested(appCtx, false, true, models.AffiliationARMY, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveBothShuttleRequestedAKZone1AirForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveShuttleRequested(appCtx, false, true, models.AffiliationAIRFORCE, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveBothShuttleRequestedAKZone2AirForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveShuttleRequested(appCtx, false, true, models.AffiliationAIRFORCE, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveBothShuttleRequestedAKZone1SpaceForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveShuttleRequested(appCtx, false, true, models.AffiliationSPACEFORCE, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveBothShuttleRequestedAKZone2SpaceForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveShuttleRequested(appCtx, false, true, models.AffiliationSPACEFORCE, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveBothShuttleRequestedAKZone1USMC(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveShuttleRequested(appCtx, false, true, models.AffiliationMARINES, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveBothShuttleRequestedAKZone2USMC(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveShuttleRequested(appCtx, false, true, models.AffiliationMARINES, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +// makeIntlHHGMoveDestAddressRequested creates an international HHG move +// with the given affiliation and destination address +// contains a pending destination address request +func makeIntlHHGMoveDestAddressRequested( + appCtx appcontext.AppContext, + affiliation models.ServiceMemberAffiliation, + streetAddress, city, state, postalCode string, +) models.Move { + userUploader := newUserUploader(appCtx) + userInfo := newUserInfo("customer") + + user := factory.BuildUser(appCtx.DB(), []factory.Customization{ + { + Model: models.User{ + OktaEmail: userInfo.email, + Active: true, + }, + }, + }, nil) + + customer := factory.BuildExtendedServiceMember(appCtx.DB(), []factory.Customization{ + { + Model: models.ServiceMember{ + PersonalEmail: &userInfo.email, + FirstName: &userInfo.firstName, + LastName: &userInfo.lastName, + CacValidated: true, + Affiliation: &affiliation, + }, + }, + { + Model: user, + LinkOnly: true, + }, + }, nil) + + dependentsAuthorized := true + sitDaysAllowance := 90 + entitlements := factory.BuildEntitlement(appCtx.DB(), []factory.Customization{ + { + Model: models.Entitlement{ + DependentsAuthorized: &dependentsAuthorized, + StorageInTransit: &sitDaysAllowance, + }, + }, + }, nil) + + var departmentIndicator *string = nil + if affiliation == models.AffiliationAIRFORCE || affiliation == models.AffiliationSPACEFORCE { + departmentIndicator = models.StringPointer(models.DepartmentIndicatorAIRANDSPACEFORCE.String()) + } else if affiliation == models.AffiliationNAVY || affiliation == models.AffiliationMARINES { + departmentIndicator = models.StringPointer(models.DepartmentIndicatorNAVYANDMARINES.String()) + } else if affiliation == models.AffiliationARMY { + departmentIndicator = models.StringPointer(models.DepartmentIndicatorARMY.String()) + } else if affiliation == models.AffiliationCOASTGUARD { + departmentIndicator = models.StringPointer(models.DepartmentIndicatorCOASTGUARD.String()) + } + + orders := factory.BuildOrder(appCtx.DB(), []factory.Customization{ + { + Model: models.Order{ + DepartmentIndicator: departmentIndicator, + }, + }, + { + Model: customer, + LinkOnly: true, + }, + { + Model: entitlements, + LinkOnly: true, + }, + { + Model: models.UserUpload{}, + ExtendedParams: &factory.UserUploadExtendedParams{ + UserUploader: userUploader, + AppContext: appCtx, + }, + }, + }, nil) + + now := time.Now() + + estimatedWeight := unit.Pound(2000) + actualWeight := unit.Pound(2000) + requestedPickupDate := now.AddDate(0, 3, 0) + requestedDeliveryDate := requestedPickupDate.AddDate(0, 1, 0) + + // build the destination address using the passed-in parameters. + address := factory.BuildAddress(appCtx.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: streetAddress, + City: city, + State: state, + PostalCode: postalCode, + IsOconus: models.BoolPointer(true), + }, + }, + }, nil) + + newDeliveryAddress := factory.BuildAddress(appCtx.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "Another Cold St.", + City: "Juneau", + State: "AK", + PostalCode: "99811", + }, + }, + }, nil) + + // build the shipment destination address update + shipmentAddressUpdate := factory.BuildShipmentAddressUpdate(appCtx.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + PrimeEstimatedWeight: &estimatedWeight, + PrimeActualWeight: &actualWeight, + ShipmentType: models.MTOShipmentTypeHHG, + Status: models.MTOShipmentStatusApproved, + RequestedPickupDate: &requestedPickupDate, + RequestedDeliveryDate: &requestedDeliveryDate, + SITDaysAllowance: &sitDaysAllowance, + DestinationAddressID: &address.ID, + MarketCode: models.MarketCodeInternational, + }, + }, + { + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + AvailableToPrimeAt: &now, + Show: models.BoolPointer(true), + }, + }, + { + Model: models.ShipmentAddressUpdate{ + Status: models.ShipmentAddressUpdateStatusRequested, + OriginalAddressID: address.ID, + NewAddressID: newDeliveryAddress.ID, + ContractorRemarks: *models.StringPointer("let's move this to another really cold place"), + }, + }, + { + Model: orders, + LinkOnly: true, + }, + }, nil) + + factory.BuildMTOServiceItemBasic(appCtx.DB(), []factory.Customization{ + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusApproved, + MTOShipmentID: &shipmentAddressUpdate.Shipment.ID, + }, + }, + { + Model: shipmentAddressUpdate.Shipment.MoveTaskOrder, + LinkOnly: true, + }, + { + Model: models.ReService{ + Code: models.ReServiceCodeMS, + }, + }, + }, nil) + + agentUserInfo := newUserInfo("agent") + factory.BuildMTOAgent(appCtx.DB(), []factory.Customization{ + { + Model: shipmentAddressUpdate.Shipment, + LinkOnly: true, + }, + { + Model: models.MTOAgent{ + FirstName: &agentUserInfo.firstName, + LastName: &agentUserInfo.lastName, + Email: &agentUserInfo.email, + MTOAgentType: models.MTOAgentReleasing, + }, + }, + }, nil) + + return shipmentAddressUpdate.Shipment.MoveTaskOrder +} + +// these create an iHHG move with selected affiliation, destination of either Anchorage, AK (Zone I) or Fairbanks, AK (Zone II) +// containing a destination address request that the TOO will be required to review +func MakeIntlHHGMoveDestAddressRequestedAKZone1Army(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveDestAddressRequested(appCtx, models.AffiliationARMY, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveDestAddressRequestedAKZone2Army(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveDestAddressRequested(appCtx, models.AffiliationARMY, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveDestAddressRequestedAKZone1AirForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveDestAddressRequested(appCtx, models.AffiliationAIRFORCE, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveDestAddressRequestedAKZone2AirForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveDestAddressRequested(appCtx, models.AffiliationAIRFORCE, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveDestAddressRequestedAKZone1SpaceForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveDestAddressRequested(appCtx, models.AffiliationSPACEFORCE, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveDestAddressRequestedAKZone2SpaceForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveDestAddressRequested(appCtx, models.AffiliationSPACEFORCE, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveDestAddressRequestedAKZone1USMC(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveDestAddressRequested(appCtx, models.AffiliationMARINES, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveDestAddressRequestedAKZone2USMC(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveDestAddressRequested(appCtx, models.AffiliationMARINES, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +// makeIntlHHGMoveSITExtensionRequested creates an international HHG move +// with the given affiliation and destination address +// contains a SIT extension request requiring TOO action +func makeIntlHHGMoveSITExtensionRequested( + appCtx appcontext.AppContext, + isOrigin bool, + affiliation models.ServiceMemberAffiliation, + streetAddress, city, state, postalCode string, +) models.Move { + userUploader := newUserUploader(appCtx) + userInfo := newUserInfo("customer") + + user := factory.BuildUser(appCtx.DB(), []factory.Customization{ + { + Model: models.User{ + OktaEmail: userInfo.email, + Active: true, + }, + }, + }, nil) + + customer := factory.BuildExtendedServiceMember(appCtx.DB(), []factory.Customization{ + { + Model: models.ServiceMember{ + PersonalEmail: &userInfo.email, + FirstName: &userInfo.firstName, + LastName: &userInfo.lastName, + CacValidated: true, + Affiliation: &affiliation, + }, + }, + { + Model: user, + LinkOnly: true, + }, + }, nil) + + dependentsAuthorized := true + sitDaysAllowance := 90 + entitlements := factory.BuildEntitlement(appCtx.DB(), []factory.Customization{ + { + Model: models.Entitlement{ + DependentsAuthorized: &dependentsAuthorized, + StorageInTransit: &sitDaysAllowance, + }, + }, + }, nil) + + orders := factory.BuildOrder(appCtx.DB(), []factory.Customization{ + { + Model: customer, + LinkOnly: true, + }, + { + Model: entitlements, + LinkOnly: true, + }, + { + Model: models.UserUpload{}, + ExtendedParams: &factory.UserUploadExtendedParams{ + UserUploader: userUploader, + AppContext: appCtx, + }, + }, + }, nil) + + now := time.Now() + move := factory.BuildMove(appCtx.DB(), []factory.Customization{ + { + Model: orders, + LinkOnly: true, + }, + { + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + AvailableToPrimeAt: &now, + }, + }, + }, nil) + + estimatedWeight := unit.Pound(2000) + actualWeight := unit.Pound(2000) + requestedPickupDate := now.AddDate(0, 3, 0) + requestedDeliveryDate := requestedPickupDate.AddDate(0, 1, 0) + + // build the destination address using the passed-in parameters. + address := factory.BuildAddress(appCtx.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: streetAddress, + City: city, + State: state, + PostalCode: postalCode, + IsOconus: models.BoolPointer(true), + }, + }, + }, nil) + + shipment := factory.BuildMTOShipment(appCtx.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + PrimeEstimatedWeight: &estimatedWeight, + PrimeActualWeight: &actualWeight, + ShipmentType: models.MTOShipmentTypeHHG, + Status: models.MTOShipmentStatusApproved, + RequestedPickupDate: &requestedPickupDate, + RequestedDeliveryDate: &requestedDeliveryDate, + SITDaysAllowance: &sitDaysAllowance, + DestinationAddressID: &address.ID, + MarketCode: models.MarketCodeInternational, + }, + }, + { + Model: move, + LinkOnly: true, + }, + }, nil) + + agentUserInfo := newUserInfo("agent") + factory.BuildMTOAgent(appCtx.DB(), []factory.Customization{ + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.MTOAgent{ + FirstName: &agentUserInfo.firstName, + LastName: &agentUserInfo.lastName, + Email: &agentUserInfo.email, + MTOAgentType: models.MTOAgentReleasing, + }, + }, + }, nil) + + // build the origin/destination SIT service items + oneMonthLater := now.AddDate(0, 1, 0) + if isOrigin { + factory.BuildOriginSITServiceItems(appCtx.DB(), move, shipment, &oneMonthLater, nil) + } else { + factory.BuildDestSITServiceItems(appCtx.DB(), move, shipment, &oneMonthLater, nil) + } + + // build the SIT extension update in PENDING status + factory.BuildSITDurationUpdate(appCtx.DB(), []factory.Customization{ + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.SITDurationUpdate{ + Status: models.SITExtensionStatusPending, + ContractorRemarks: models.StringPointer("gimme some more plz"), + }, + }, + }, nil) + + return move +} + +// these create an iHHG move with selected affiliation, destination of either Anchorage, AK (Zone I) or Fairbanks, AK (Zone II) +// containing a SIT extension request for a shipment containing origin SIT only +func MakeIntlHHGMoveOriginSITExtensionRequestedAKZone1Army(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveSITExtensionRequested(appCtx, true, models.AffiliationARMY, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveOriginSITExtensionRequestedAKZone2Army(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveSITExtensionRequested(appCtx, true, models.AffiliationARMY, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveOriginSITExtensionRequestedAKZone1AirForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveSITExtensionRequested(appCtx, true, models.AffiliationAIRFORCE, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveOriginSITExtensionRequestedAKZone2AirForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveSITExtensionRequested(appCtx, true, models.AffiliationAIRFORCE, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveOriginSITExtensionRequestedAKZone1SpaceForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveSITExtensionRequested(appCtx, true, models.AffiliationSPACEFORCE, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveOriginSITExtensionRequestedAKZone2SpaceForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveSITExtensionRequested(appCtx, true, models.AffiliationSPACEFORCE, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveOriginSITExtensionRequestedAKZone1USMC(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveSITExtensionRequested(appCtx, true, models.AffiliationMARINES, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveOriginSITExtensionRequestedAKZone2USMC(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveSITExtensionRequested(appCtx, true, models.AffiliationMARINES, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +// these create an iHHG move with selected affiliation, destination of either Anchorage, AK (Zone I) or Fairbanks, AK (Zone II) +// containing a SIT extension request for a shipment containing destination SIT only +func MakeIntlHHGMoveDestSITExtensionRequestedAKZone1Army(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveSITExtensionRequested(appCtx, false, models.AffiliationARMY, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveDestSITExtensionRequestedAKZone2Army(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveSITExtensionRequested(appCtx, false, models.AffiliationARMY, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveDestSITExtensionRequestedAKZone1AirForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveSITExtensionRequested(appCtx, false, models.AffiliationAIRFORCE, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveDestSITExtensionRequestedAKZone2AirForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveSITExtensionRequested(appCtx, false, models.AffiliationAIRFORCE, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveDestSITExtensionRequestedAKZone1SpaceForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveSITExtensionRequested(appCtx, false, models.AffiliationSPACEFORCE, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveDestSITExtensionRequestedAKZone2SpaceForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveSITExtensionRequested(appCtx, false, models.AffiliationSPACEFORCE, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveDestSITExtensionRequestedAKZone1USMC(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveSITExtensionRequested(appCtx, false, models.AffiliationMARINES, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveDestSITExtensionRequestedAKZone2USMC(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveSITExtensionRequested(appCtx, false, models.AffiliationMARINES, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +// makeIntlHHGMoveCONUSToAKWithExcessWeight creates an international HHG move +// with the given affiliation and destination address +// contains one approved shipment and an pending at-risk excess weight +func makeIntlHHGMoveCONUSToAKWithExcessWeight( + appCtx appcontext.AppContext, + affiliation models.ServiceMemberAffiliation, + streetAddress, city, state, postalCode string, +) models.Move { + userUploader := newUserUploader(appCtx) + userInfo := newUserInfo("customer") + + user := factory.BuildUser(appCtx.DB(), []factory.Customization{ + { + Model: models.User{ + OktaEmail: userInfo.email, + Active: true, + }, + }, + }, nil) + + customer := factory.BuildExtendedServiceMember(appCtx.DB(), []factory.Customization{ + { + Model: models.ServiceMember{ + PersonalEmail: &userInfo.email, + FirstName: &userInfo.firstName, + LastName: &userInfo.lastName, + CacValidated: true, + Affiliation: &affiliation, + }, + }, + { + Model: user, + LinkOnly: true, + }, + }, nil) + + dependentsAuthorized := true + sitDaysAllowance := 90 + entitlements := factory.BuildEntitlement(appCtx.DB(), []factory.Customization{ + { + Model: models.Entitlement{ + DependentsAuthorized: &dependentsAuthorized, + StorageInTransit: &sitDaysAllowance, + }, + }, + }, nil) + + orders := factory.BuildOrder(appCtx.DB(), []factory.Customization{ + { + Model: customer, + LinkOnly: true, + }, + { + Model: entitlements, + LinkOnly: true, + }, + { + Model: models.UserUpload{}, + ExtendedParams: &factory.UserUploadExtendedParams{ + UserUploader: userUploader, + AppContext: appCtx, + }, + }, + }, nil) + + now := time.Now() + move := factory.BuildAvailableToPrimeMove(appCtx.DB(), []factory.Customization{ + { + Model: orders, + LinkOnly: true, + }, + { + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + ExcessWeightQualifiedAt: &now, + }, + }, + }, nil) + + requestedPickupDate := now.AddDate(0, 3, 0) + requestedDeliveryDate := requestedPickupDate.AddDate(0, 1, 0) + + // build the destination address using the passed-in parameters. + address := factory.BuildAddress(appCtx.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: streetAddress, + City: city, + State: state, + PostalCode: postalCode, + IsOconus: models.BoolPointer(true), + }, + }, + }, nil) + + shipment := factory.BuildMTOShipment(appCtx.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + PrimeEstimatedWeight: models.PoundPointer(8000), + ShipmentType: models.MTOShipmentTypeHHG, + Status: models.MTOShipmentStatusApproved, + RequestedPickupDate: &requestedPickupDate, + RequestedDeliveryDate: &requestedDeliveryDate, + SITDaysAllowance: &sitDaysAllowance, + DestinationAddressID: &address.ID, + MarketCode: models.MarketCodeInternational, + }, + }, + { + Model: move, + LinkOnly: true, + }, + }, nil) + + agentUserInfo := newUserInfo("agent") + factory.BuildMTOAgent(appCtx.DB(), []factory.Customization{ + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.MTOAgent{ + FirstName: &agentUserInfo.firstName, + LastName: &agentUserInfo.lastName, + Email: &agentUserInfo.email, + MTOAgentType: models.MTOAgentReleasing, + }, + }, + }, nil) + + factory.BuildMTOServiceItemBasic(appCtx.DB(), []factory.Customization{ + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusApproved, + MTOShipmentID: &shipment.ID, + }, + }, + { + Model: move, + LinkOnly: true, + }, + { + Model: models.ReService{ + Code: models.ReServiceCodeMS, + }, + }, + }, nil) + + return move +} + +// these create an iHHG move with selected affiliation, destination of either Anchorage, AK (Zone I) or Fairbanks, AK (Zone II) +// containing an excess weight alert that requires action from TOO +func MakeIntlHHGMoveExcessWeightAKZone1Army(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveCONUSToAKWithExcessWeight(appCtx, models.AffiliationARMY, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveExcessWeightAKZone2Army(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveCONUSToAKWithExcessWeight(appCtx, models.AffiliationARMY, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +// makeIntlUBMoveCONUSToAKWithExcessWeight creates an international UB move +// with the given affiliation and destination address +// contains one approved shipment and an pending at-risk excess weight +func makeIntlUBMoveCONUSToAKWithExcessWeight( + appCtx appcontext.AppContext, + affiliation models.ServiceMemberAffiliation, + streetAddress, city, state, postalCode string, +) models.Move { + userUploader := newUserUploader(appCtx) + userInfo := newUserInfo("customer") + + user := factory.BuildUser(appCtx.DB(), []factory.Customization{ + { + Model: models.User{ + OktaEmail: userInfo.email, + Active: true, + }, + }, + }, nil) + + customer := factory.BuildExtendedServiceMember(appCtx.DB(), []factory.Customization{ + { + Model: models.ServiceMember{ + PersonalEmail: &userInfo.email, + FirstName: &userInfo.firstName, + LastName: &userInfo.lastName, + CacValidated: true, + Affiliation: &affiliation, + }, + }, + { + Model: user, + LinkOnly: true, + }, + }, nil) + + dependentsAuthorized := true + sitDaysAllowance := 90 + entitlements := factory.BuildEntitlement(appCtx.DB(), []factory.Customization{ + { + Model: models.Entitlement{ + DependentsAuthorized: &dependentsAuthorized, + StorageInTransit: &sitDaysAllowance, + }, + }, + }, nil) + + orders := factory.BuildOrder(appCtx.DB(), []factory.Customization{ + { + Model: customer, + LinkOnly: true, + }, + { + Model: entitlements, + LinkOnly: true, + }, + { + Model: models.UserUpload{}, + ExtendedParams: &factory.UserUploadExtendedParams{ + UserUploader: userUploader, + AppContext: appCtx, + }, + }, + }, nil) + + now := time.Now() + move := factory.BuildApprovalsRequestedMove(appCtx.DB(), []factory.Customization{ + { + Model: orders, + LinkOnly: true, + }, + { + Model: models.Move{ + ExcessUnaccompaniedBaggageWeightQualifiedAt: &now, + AvailableToPrimeAt: &now, + }, + }, + }, nil) + + requestedPickupDate := now.AddDate(0, 3, 0) + requestedDeliveryDate := requestedPickupDate.AddDate(0, 1, 0) + + // build the destination address using the passed-in parameters. + address := factory.BuildAddress(appCtx.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: streetAddress, + City: city, + State: state, + PostalCode: postalCode, + IsOconus: models.BoolPointer(true), + }, + }, + }, nil) + + shipment := factory.BuildMTOShipment(appCtx.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + PrimeEstimatedWeight: models.PoundPointer(2000), + ShipmentType: models.MTOShipmentTypeUnaccompaniedBaggage, + Status: models.MTOShipmentStatusApproved, + RequestedPickupDate: &requestedPickupDate, + RequestedDeliveryDate: &requestedDeliveryDate, + SITDaysAllowance: &sitDaysAllowance, + DestinationAddressID: &address.ID, + MarketCode: models.MarketCodeInternational, + }, + }, + { + Model: move, + LinkOnly: true, + }, + }, nil) + + agentUserInfo := newUserInfo("agent") + factory.BuildMTOAgent(appCtx.DB(), []factory.Customization{ + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.MTOAgent{ + FirstName: &agentUserInfo.firstName, + LastName: &agentUserInfo.lastName, + Email: &agentUserInfo.email, + MTOAgentType: models.MTOAgentReleasing, + }, + }, + }, nil) + + factory.BuildMTOServiceItemBasic(appCtx.DB(), []factory.Customization{ + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusApproved, + MTOShipmentID: &shipment.ID, + }, + }, + { + Model: move, + LinkOnly: true, + }, + { + Model: models.ReService{ + Code: models.ReServiceCodeMS, + }, + }, + }, nil) + + return move +} + +// these create an iHHG move with selected affiliation, destination of either Anchorage, AK (Zone I) or Fairbanks, AK (Zone II) +// containing an excess weight alert that requires action from TOO +func MakeIntlUBMoveExcessWeightAKZone1Army(appCtx appcontext.AppContext) models.Move { + return makeIntlUBMoveCONUSToAKWithExcessWeight(appCtx, models.AffiliationARMY, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlUBMoveExcessWeightAKZone2Army(appCtx appcontext.AppContext) models.Move { + return makeIntlUBMoveCONUSToAKWithExcessWeight(appCtx, models.AffiliationARMY, "Alaska Zone II St.", "North Pole", "AK", "99705") +} diff --git a/pkg/unit/millicents_test.go b/pkg/unit/millicents_test.go index 0e7056f5f66..d7af7278abe 100644 --- a/pkg/unit/millicents_test.go +++ b/pkg/unit/millicents_test.go @@ -4,6 +4,16 @@ import ( "testing" ) +func TestMillicents_Int64(t *testing.T) { + millicents := Millicents(250000) + result := millicents.Int64() + + expected := int64(250000) + if result != expected { + t.Errorf("wrong number of Millicents: expected %v, got %v", expected, result) + } +} + func TestMillicents_Float64(t *testing.T) { millicents := Millicents(250000) result := millicents.Float64() diff --git a/playwright/tests/office/txo/tioFlowsInternational.spec.js b/playwright/tests/office/txo/tioFlowsInternational.spec.js index 2b97f19078b..cf8ed39c541 100644 --- a/playwright/tests/office/txo/tioFlowsInternational.spec.js +++ b/playwright/tests/office/txo/tioFlowsInternational.spec.js @@ -143,6 +143,17 @@ test.describe('TIO user', () => { await page.getByText('Next').click(); await tioFlowPage.slowDown(); + await expect(page.getByText('International destination shuttle service')).toBeVisible(); + await page.getByText('Show calculations').click(); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Calculations'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Billable weight (cwt)'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Destination price'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Price escalation factor'); + // approve + await tioFlowPage.approveServiceItem(); + await page.getByText('Next').click(); + await tioFlowPage.slowDown(); + await expect(page.getByText('International POE Fuel Surcharge')).toBeVisible(); await page.getByText('Show calculations').click(); await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Calculations'); @@ -159,8 +170,8 @@ test.describe('TIO user', () => { await expect(page.getByText('needs your review')).toHaveCount(0, { timeout: 10000 }); await page.getByText('Complete request').click(); - await expect(page.locator('[data-testid="requested"]')).toContainText('$4,281.48'); - await expect(page.locator('[data-testid="accepted"]')).toContainText('$4,281.48'); + await expect(page.locator('[data-testid="requested"]')).toContainText('$4,287.71'); + await expect(page.locator('[data-testid="accepted"]')).toContainText('$4,287.71'); await expect(page.locator('[data-testid="rejected"]')).toContainText('$0.00'); await page.getByText('Authorize payment').click(); @@ -187,4 +198,94 @@ test.describe('TIO user', () => { // in the TIO queue - only "Payment requested" moves will appear await expect(paymentSection.locator('td', { hasText: 'Reviewed' })).not.toBeVisible(); }); + + test('can review a payment request for international crating/uncrating service items', async ({ + page, + officePage, + }) => { + test.slow(); + const move = + await officePage.testHarness.buildIntlHHGMoveWithCratingUncratingServiceItemsAndPaymentRequestsForTIO(); + await officePage.signInAsNewTIOUser(); + + tioFlowPage = new TioFlowPage(officePage, move, true); + await tioFlowPage.waitForLoading(); + await officePage.tioNavigateToMove(tioFlowPage.moveLocator); + await officePage.page.getByRole('heading', { name: 'Payment Requests', exact: true }).waitFor(); + expect(page.url()).toContain('/payment-requests'); + await expect(page.getByTestId('MovePaymentRequests')).toBeVisible(); + + const prNumber = tioFlowPage.paymentRequest.payment_request_number; + const prHeading = page.getByRole('heading', { name: `Payment Request ${prNumber}` }); + await expect(prHeading).toBeVisible(); + await tioFlowPage.waitForLoading(); + + await page.getByRole('button', { name: 'Review service items' }).click(); + + await page.waitForURL(`**/payment-requests/${tioFlowPage.paymentRequest.id}`); + await tioFlowPage.waitForLoading(); + + // ICRT + await expect(page.getByTestId('ReviewServiceItems')).toBeVisible(); + await expect(page.getByText('International crating')).toBeVisible(); + await page.getByText('Show calculations').click(); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Calculations'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Crating size (cu ft)'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Description'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Dimensions'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Actual size'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('External crate'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Crating price (per cu ft)'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Crating date'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Price escalation factor'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Uncapped request total'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Standalone crate cap'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Minimum crating size applied'); + // approve + await tioFlowPage.approveServiceItem(); + await page.getByTestId('nextServiceItem').click(); + await tioFlowPage.slowDown(); + + // IUCRT + await expect(page.getByText('International uncrating')).toBeVisible(); + await page.getByText('Show calculations').click(); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Calculations'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Crating size (cu ft)'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Description'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Dimensions'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Uncrating price (per cu ft)'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Uncrating date'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Price escalation factor'); + // approve + await tioFlowPage.approveServiceItem(); + await page.getByTestId('nextServiceItem').click(); + await tioFlowPage.slowDown(); + + await expect(page.getByText('needs your review')).toHaveCount(0, { timeout: 10000 }); + await page.getByText('Complete request').click(); + + await page.getByText('Authorize payment').click(); + await tioFlowPage.waitForLoading(); + + await tioFlowPage.slowDown(); + expect(page.url()).toContain('/payment-requests'); + + await expect(page.getByTestId('tag')).toBeVisible(); + await expect(page.getByTestId('tag').getByText('Reviewed')).toHaveCount(1); + + // ensure the payment request we approved no longer has the "Review Service Items" button + await expect(page.getByText('Review Service Items')).toHaveCount(0); + + // Go back to queue + await page.locator('a[title="Home"]').click(); + await tioFlowPage.waitForLoading(); + + // search for the moveLocator in case this move doesn't show up on the first page + await page.locator('#locator').fill(tioFlowPage.moveLocator); + await page.locator('#locator').blur(); + const paymentSection = page.locator(`[data-uuid="${tioFlowPage.paymentRequest.id}"]`); + // the payment request that is now in the "Reviewed" status will no longer appear + // in the TIO queue - only "Payment requested" moves will appear + await expect(paymentSection.locator('td', { hasText: 'Reviewed' })).not.toBeVisible(); + }); }); diff --git a/playwright/tests/office/txo/tooFlows.spec.js b/playwright/tests/office/txo/tooFlows.spec.js index dd85150f3fd..412076deb28 100644 --- a/playwright/tests/office/txo/tooFlows.spec.js +++ b/playwright/tests/office/txo/tooFlows.spec.js @@ -626,17 +626,10 @@ test.describe('TOO user', () => { await page.getByRole('button', { name: 'Select task_ordering_officer' }).click(); }); test('weight-based multiplier prioritizes billed weight', async ({ page }) => { - await page.getByRole('row', { name: 'Select...' }).getByTestId('locator').getByTestId('TextBoxFilter').click(); - await page - .getByRole('row', { name: 'Select...' }) - .getByTestId('locator') - .getByTestId('TextBoxFilter') - .fill(moveLoc); - await page - .getByRole('row', { name: 'Select...' }) - .getByTestId('locator') - .getByTestId('TextBoxFilter') - .press('Enter'); + await page.getByRole('link', { name: 'Search' }).click(); + await page.getByTestId('searchText').click(); + await page.getByTestId('searchText').fill(moveLoc); + await page.getByTestId('searchText').press('Enter'); await page.getByTestId('locator-0').click(); await page.getByRole('link', { name: 'Payment requests' }).click(); await page.getByRole('button', { name: 'Review shipment weights' }).click(); diff --git a/playwright/tests/office/txo/tooFlowsInternational.spec.js b/playwright/tests/office/txo/tooFlowsInternational.spec.js index 31fa0741487..2b209b143ec 100644 --- a/playwright/tests/office/txo/tooFlowsInternational.spec.js +++ b/playwright/tests/office/txo/tooFlowsInternational.spec.js @@ -59,7 +59,6 @@ test.describe('TOO user', () => { // Edit the shipment address to AK await page.locator('[data-testid="ShipmentContainer"] .usa-button').first().click(); await page.locator('input[id="delivery.address-location-input"]').fill('99505'); - await page.keyboard.press('Enter'); await page.getByRole('button', { name: 'Save' }).click(); await tooFlowPage.waitForPage.moveDetails(); @@ -89,17 +88,19 @@ test.describe('TOO user', () => { return table.getByRole('rowgroup').nth(1).getByRole('row'); }; + await expect(page.getByText('Requested Service Items', { exact: false })).toBeVisible(); + await expect(page.getByTestId('modal')).not.toBeVisible(); + const requestedServiceItemsTable = page.getByTestId('RequestedServiceItemsTable'); - let requestedServiceItemCount = await getServiceItemsInTable(requestedServiceItemsTable).count(); const approvedServiceItemsTable = page.getByTestId('ApprovedServiceItemsTable'); - let approvedServiceItemCount = await getServiceItemsInTable(approvedServiceItemsTable).count(); const rejectedServiceItemsTable = page.getByTestId('RejectedServiceItemsTable'); - let rejectedServiceItemCount = await getServiceItemsInTable(rejectedServiceItemsTable).count(); - await expect(page.getByText('Requested Service Items', { exact: false })).toBeVisible(); await expect(getServiceItemsInTable(requestedServiceItemsTable).nth(1)).toBeVisible(); + await expect(getServiceItemsInTable(approvedServiceItemsTable).nth(1)).toBeVisible(); - await expect(page.getByTestId('modal')).not.toBeVisible(); + let requestedServiceItemCount = await getServiceItemsInTable(requestedServiceItemsTable).count(); + let approvedServiceItemCount = await getServiceItemsInTable(approvedServiceItemsTable).count(); + let rejectedServiceItemCount = await getServiceItemsInTable(rejectedServiceItemsTable).count(); // Approve a requested service item expect((await getServiceItemsInTable(requestedServiceItemsTable).count()) > 0); @@ -163,5 +164,94 @@ test.describe('TOO user', () => { await expect(getServiceItemsInTable(requestedServiceItemsTable)).toHaveCount(requestedServiceItemCount - 1); }); + + test.skip(alaskaEnabled === 'false', 'Skip if Alaska FF is disabled.'); + test('is able to approve and reject international shuttle service items', async ({ officePage, page }) => { + const move = await officePage.testHarness.buildHHGMoveWithIntlShuttleServiceItemsTOO(); + await officePage.signInAsNewTOOUser(); + tooFlowPage = new TooFlowPage(officePage, move); + await tooFlowPage.waitForLoading(); + await officePage.tooNavigateToMove(tooFlowPage.moveLocator); + + // Edit the shipment address to AK + await page.locator('[data-testid="ShipmentContainer"] .usa-button').first().click(); + await page.locator('input[id="delivery.address-location-input"]').fill('99505'); + + await page.getByRole('button', { name: 'Save' }).click(); + await tooFlowPage.waitForPage.moveDetails(); + + await tooFlowPage.waitForLoading(); + await tooFlowPage.approveAllShipments(); + + await page.getByTestId('MoveTaskOrder-Tab').click(); + await tooFlowPage.waitForLoading(); + expect(page.url()).toContain(`/moves/${tooFlowPage.moveLocator}/mto`); + + // Wait for page to load to deal with flakiness resulting from Service Item tables loading + await tooFlowPage.page.waitForLoadState(); + + // Move Task Order page + await expect(page.getByTestId('ShipmentContainer')).toHaveCount(1); + + /** + * @function + * @description This test approves and rejects service items, which moves them from one table to another + * and expects the counts of each table to increment/decrement by one item each time + * This function gets the service items for a given table to help count them + * @param {import("playwright-core").Locator} table + * @returns {import("playwright-core").Locator} + */ + const getServiceItemsInTable = (table) => { + return table.getByRole('rowgroup').nth(1).getByRole('row'); + }; + + await expect(page.getByText('Requested Service Items', { exact: false })).toBeVisible(); + await expect(page.getByTestId('modal')).not.toBeVisible(); + + const requestedServiceItemsTable = page.getByTestId('RequestedServiceItemsTable'); + const approvedServiceItemsTable = page.getByTestId('ApprovedServiceItemsTable'); + const rejectedServiceItemsTable = page.getByTestId('RejectedServiceItemsTable'); + + await expect(getServiceItemsInTable(requestedServiceItemsTable).nth(1)).toBeVisible(); + await expect(getServiceItemsInTable(approvedServiceItemsTable).nth(1)).toBeVisible(); + + let requestedServiceItemCount = await getServiceItemsInTable(requestedServiceItemsTable).count(); + let approvedServiceItemCount = await getServiceItemsInTable(approvedServiceItemsTable).count(); + let rejectedServiceItemCount = await getServiceItemsInTable(rejectedServiceItemsTable).count(); + + // Approve a requested service item + expect((await getServiceItemsInTable(requestedServiceItemsTable).count()) > 0); + + await requestedServiceItemsTable.getByRole('button', { name: 'Accept' }).first().click(); + await tooFlowPage.waitForLoading(); + + await expect(getServiceItemsInTable(approvedServiceItemsTable)).toHaveCount(approvedServiceItemCount + 1); + approvedServiceItemCount = await getServiceItemsInTable(approvedServiceItemsTable).count(); + + await expect(getServiceItemsInTable(requestedServiceItemsTable)).toHaveCount(requestedServiceItemCount - 1); + requestedServiceItemCount = await getServiceItemsInTable(requestedServiceItemsTable).count(); + + // Reject a requested service item + await expect(page.getByText('Requested Service Items', { exact: false })).toBeVisible(); + expect((await getServiceItemsInTable(requestedServiceItemsTable).count()) > 0); + + await requestedServiceItemsTable.getByRole('button', { name: 'Reject' }).first().click(); + + await expect(page.getByTestId('modal')).toBeVisible(); + const modal = page.getByTestId('modal'); + + await expect(modal.getByRole('button', { name: 'Submit' })).toBeDisabled(); + await modal.getByRole('textbox').fill('my very valid reason'); + await modal.getByRole('button', { name: 'Submit' }).click(); + + await expect(page.getByTestId('modal')).not.toBeVisible(); + + await expect(page.getByText('Rejected Service Items', { exact: false })).toBeVisible(); + await expect(getServiceItemsInTable(rejectedServiceItemsTable)).toHaveCount(rejectedServiceItemCount + 1); + rejectedServiceItemCount = await getServiceItemsInTable(rejectedServiceItemsTable).count(); + + await expect(getServiceItemsInTable(requestedServiceItemsTable)).toHaveCount(requestedServiceItemCount - 1); + requestedServiceItemCount = await getServiceItemsInTable(requestedServiceItemsTable).count(); + }); }); }); diff --git a/playwright/tests/utils/testharness.js b/playwright/tests/utils/testharness.js index cc84f4c61f9..ceae7386107 100644 --- a/playwright/tests/utils/testharness.js +++ b/playwright/tests/utils/testharness.js @@ -299,6 +299,14 @@ export class TestHarness { return this.buildDefault('HHGMoveWithIntlCratingServiceItemsTOO'); } + /** + * Use testharness to build hhg move with international crating service items for TOO + * @returns {Promise} + */ + async buildHHGMoveWithIntlShuttleServiceItemsTOO() { + return this.buildDefault('HHGMoveWithIntlShuttleServiceItemsTOO'); + } + /** * Use testharness to build hhg move for TOO with actualPickupDate in the past * @returns {Promise} @@ -387,6 +395,14 @@ export class TestHarness { return this.buildDefault('InternationalHHGMoveWithServiceItemsandPaymentRequestsForTIO'); } + /** + * Use testharness to build ihhg move for TIO + * @returns {Promise} + */ + async buildIntlHHGMoveWithCratingUncratingServiceItemsAndPaymentRequestsForTIO() { + return this.buildDefault('IntlHHGMoveWithCratingUncratingServiceItemsAndPaymentRequestsForTIO'); + } + /** * Use testharness to build hhg move for QAE * @returns {Promise} @@ -694,5 +710,353 @@ export class TestHarness { async buildOfficeUserWithGSR() { return this.buildDefault('OfficeUserWithGSR'); } + + /** + * Use testharness to build international move with requested origin SIT + * @returns {Promise} + */ + async buildIntlHHGMoveOriginSITRequestedAKZone1Army() { + return this.buildDefault('IntlHHGMoveOriginSITRequestedAKZone1Army'); + } + + async buildIntlHHGMoveOriginSITRequestedAKZone2Army() { + return this.buildDefault('IntlHHGMoveOriginSITRequestedAKZone2Army'); + } + + async buildIntlHHGMoveOriginSITRequestedAKZone1AirForce() { + return this.buildDefault('IntlHHGMoveOriginSITRequestedAKZone1AirForce'); + } + + async buildIntlHHGMoveOriginSITRequestedAKZone2AirForce() { + return this.buildDefault('IntlHHGMoveOriginSITRequestedAKZone2AirForce'); + } + + async buildIntlHHGMoveOriginSITRequestedAKZone1SpaceForce() { + return this.buildDefault('IntlHHGMoveOriginSITRequestedAKZone1SpaceForce'); + } + + async buildIntlHHGMoveOriginSITRequestedAKZone2SpaceForce() { + return this.buildDefault('IntlHHGMoveOriginSITRequestedAKZone2SpaceForce'); + } + + async buildIntlHHGMoveOriginSITRequestedAKZone1USMC() { + return this.buildDefault('IntlHHGMoveOriginSITRequestedAKZone1USMC'); + } + + async buildIntlHHGMoveOriginSITRequestedAKZone2USMC() { + return this.buildDefault('IntlHHGMoveOriginSITRequestedAKZone2USMC'); + } + + /** + * Use testharness to build international move with requested destination SIT + * @returns {Promise} + */ + async buildIntlHHGMoveDestSITRequestedAKZone1Army() { + return this.buildDefault('IntlHHGMoveDestSITRequestedAKZone1Army'); + } + + async buildIntlHHGMoveDestSITRequestedAKZone2Army() { + return this.buildDefault('IntlHHGMoveDestSITRequestedAKZone2Army'); + } + + async buildIntlHHGMoveDestSITRequestedAKZone1AirForce() { + return this.buildDefault('IntlHHGMoveDestSITRequestedAKZone1AirForce'); + } + + async buildIntlHHGMoveDestSITRequestedAKZone2AirForce() { + return this.buildDefault('IntlHHGMoveDestSITRequestedAKZone2AirForce'); + } + + async buildIntlHHGMoveDestSITRequestedAKZone1SpaceForce() { + return this.buildDefault('IntlHHGMoveDestSITRequestedAKZone1SpaceForce'); + } + + async buildIntlHHGMoveDestSITRequestedAKZone2SpaceForce() { + return this.buildDefault('IntlHHGMoveDestSITRequestedAKZone2SpaceForce'); + } + + async buildIntlHHGMoveDestSITRequestedAKZone1USMC() { + return this.buildDefault('IntlHHGMoveDestSITRequestedAKZone1USMC'); + } + + async buildIntlHHGMoveDestSITRequestedAKZone2USMC() { + return this.buildDefault('IntlHHGMoveDestSITRequestedAKZone2USMC'); + } + + /** + * Use testharness to build international move with both requested origin & destination SIT + * @returns {Promise} + */ + async buildIntlHHGMoveBothSITRequestedAKZone1Army() { + return this.buildDefault('IntlHHGMoveBothSITRequestedAKZone1Army'); + } + + async buildIntlHHGMoveBothSITRequestedAKZone2Army() { + return this.buildDefault('IntlHHGMoveBothSITRequestedAKZone2Army'); + } + + async buildIntlHHGMoveBothSITRequestedAKZone1AirForce() { + return this.buildDefault('IntlHHGMoveBothSITRequestedAKZone1AirForce'); + } + + async buildIntlHHGMoveBothSITRequestedAKZone2AirForce() { + return this.buildDefault('IntlHHGMoveBothSITRequestedAKZone2AirForce'); + } + + async buildIntlHHGMoveBothSITRequestedAKZone1SpaceForce() { + return this.buildDefault('IntlHHGMoveBothSITRequestedAKZone1SpaceForce'); + } + + async buildIntlHHGMoveBothSITRequestedAKZone2SpaceForce() { + return this.buildDefault('IntlHHGMoveBothSITRequestedAKZone2SpaceForce'); + } + + async buildIntlHHGMoveBothSITRequestedAKZone1USMC() { + return this.buildDefault('IntlHHGMoveBothSITRequestedAKZone1USMC'); + } + + async buildIntlHHGMoveBothSITRequestedAKZone2USMC() { + return this.buildDefault('IntlHHGMoveBothSITRequestedAKZone2USMC'); + } + + /** + * Use testharness to build international move with requested origin shuttle service + * @returns {Promise} + */ + async buildIntlHHGMoveOriginShuttleRequestedAKZone1Army() { + return this.buildDefault('IntlHHGMoveOriginShuttleRequestedAKZone1Army'); + } + + async buildIntlHHGMoveOriginShuttleRequestedAKZone2Army() { + return this.buildDefault('IntlHHGMoveOriginShuttleRequestedAKZone2Army'); + } + + async buildIntlHHGMoveOriginShuttleRequestedAKZone1AirForce() { + return this.buildDefault('IntlHHGMoveOriginShuttleRequestedAKZone1AirForce'); + } + + async buildIntlHHGMoveOriginShuttleRequestedAKZone2AirForce() { + return this.buildDefault('IntlHHGMoveOriginShuttleRequestedAKZone2AirForce'); + } + + async buildIntlHHGMoveOriginShuttleRequestedAKZone1SpaceForce() { + return this.buildDefault('IntlHHGMoveOriginShuttleRequestedAKZone1SpaceForce'); + } + + async buildIntlHHGMoveOriginShuttleRequestedAKZone2SpaceForce() { + return this.buildDefault('IntlHHGMoveOriginShuttleRequestedAKZone2SpaceForce'); + } + + async buildIntlHHGMoveOriginShuttleRequestedAKZone1USMC() { + return this.buildDefault('IntlHHGMoveOriginShuttleRequestedAKZone1USMC'); + } + + async buildIntlHHGMoveOriginShuttleRequestedAKZone2USMC() { + return this.buildDefault('IntlHHGMoveOriginShuttleRequestedAKZone2USMC'); + } + + /** + * Use testharness to build international move with requested destination shuttle service + * @returns {Promise} + */ + async buildIntlHHGMoveDestShuttleRequestedAKZone1Army() { + return this.buildDefault('IntlHHGMoveDestShuttleRequestedAKZone1Army'); + } + + async buildIntlHHGMoveDestShuttleRequestedAKZone2Army() { + return this.buildDefault('IntlHHGMoveDestShuttleRequestedAKZone2Army'); + } + + async buildIntlHHGMoveDestShuttleRequestedAKZone1AirForce() { + return this.buildDefault('IntlHHGMoveDestShuttleRequestedAKZone1AirForce'); + } + + async buildIntlHHGMoveDestShuttleRequestedAKZone2AirForce() { + return this.buildDefault('IntlHHGMoveDestShuttleRequestedAKZone2AirForce'); + } + + async buildIntlHHGMoveDestShuttleRequestedAKZone1SpaceForce() { + return this.buildDefault('IntlHHGMoveDestShuttleRequestedAKZone1SpaceForce'); + } + + async buildIntlHHGMoveDestShuttleRequestedAKZone2SpaceForce() { + return this.buildDefault('IntlHHGMoveDestShuttleRequestedAKZone2SpaceForce'); + } + + async buildIntlHHGMoveDestShuttleRequestedAKZone1USMC() { + return this.buildDefault('IntlHHGMoveDestShuttleRequestedAKZone1USMC'); + } + + async buildIntlHHGMoveDestShuttleRequestedAKZone2USMC() { + return this.buildDefault('IntlHHGMoveDestShuttleRequestedAKZone2USMC'); + } + + /** + * Use testharness to build international move with both requested origin & destination shuttle service + * @returns {Promise} + */ + async buildIntlHHGMoveBothShuttleRequestedAKZone1Army() { + return this.buildDefault('IntlHHGMoveBothShuttleRequestedAKZone1Army'); + } + + async buildIntlHHGMoveBothShuttleRequestedAKZone2Army() { + return this.buildDefault('IntlHHGMoveBothShuttleRequestedAKZone2Army'); + } + + async buildIntlHHGMoveBothShuttleRequestedAKZone1AirForce() { + return this.buildDefault('IntlHHGMoveBothShuttleRequestedAKZone1AirForce'); + } + + async buildIntlHHGMoveBothShuttleRequestedAKZone2AirForce() { + return this.buildDefault('IntlHHGMoveBothShuttleRequestedAKZone2AirForce'); + } + + async buildIntlHHGMoveBothShuttleRequestedAKZone1SpaceForce() { + return this.buildDefault('IntlHHGMoveBothShuttleRequestedAKZone1SpaceForce'); + } + + async buildIntlHHGMoveBothShuttleRequestedAKZone2SpaceForce() { + return this.buildDefault('IntlHHGMoveBothShuttleRequestedAKZone2SpaceForce'); + } + + async buildIntlHHGMoveBothShuttleRequestedAKZone1USMC() { + return this.buildDefault('IntlHHGMoveBothShuttleRequestedAKZone1USMC'); + } + + async buildIntlHHGMoveBothShuttleRequestedAKZone2USMC() { + return this.buildDefault('IntlHHGMoveBothShuttleRequestedAKZone2USMC'); + } + + /** + * Use testharness to build international move with requested destination address request + * @returns {Promise} + */ + async buildIntlHHGMoveDestAddressRequestedAKZone1Army() { + return this.buildDefault('IntlHHGMoveDestAddressRequestedAKZone1Army'); + } + + async buildIntlHHGMoveDestAddressRequestedAKZone2Army() { + return this.buildDefault('IntlHHGMoveDestAddressRequestedAKZone2Army'); + } + + async buildIntlHHGMoveDestAddressRequestedAKZone1AirForce() { + return this.buildDefault('IntlHHGMoveDestAddressRequestedAKZone1AirForce'); + } + + async buildIntlHHGMoveDestAddressRequestedAKZone2AirForce() { + return this.buildDefault('IntlHHGMoveDestAddressRequestedAKZone2AirForce'); + } + + async buildIntlHHGMoveDestAddressRequestedAKZone1SpaceForce() { + return this.buildDefault('IntlHHGMoveDestAddressRequestedAKZone1SpaceForce'); + } + + async buildIntlHHGMoveDestAddressRequestedAKZone2SpaceForce() { + return this.buildDefault('IntlHHGMoveDestAddressRequestedAKZone2SpaceForce'); + } + + async buildIntlHHGMoveDestAddressRequestedAKZone1USMC() { + return this.buildDefault('IntlHHGMoveDestAddressRequestedAKZone1USMC'); + } + + async buildIntlHHGMoveDestAddressRequestedAKZone2USMC() { + return this.buildDefault('IntlHHGMoveDestAddressRequestedAKZone2USMC'); + } + + /** + * Use testharness to build international move with a pending SIT extension request with origin SIT + * @returns {Promise} + */ + async buildIntlHHGMoveOriginSITExtensionRequestedAKZone1Army() { + return this.buildDefault('IntlHHGMoveOriginSITExtensionRequestedAKZone1Army'); + } + + async buildIntlHHGMoveOriginSITExtensionRequestedAKZone2Army() { + return this.buildDefault('IntlHHGMoveOriginSITExtensionRequestedAKZone2Army'); + } + + async buildIntlHHGMoveOriginSITExtensionRequestedAKZone1AirForce() { + return this.buildDefault('IntlHHGMoveOriginSITExtensionRequestedAKZone1AirForce'); + } + + async buildIntlHHGMoveOriginSITExtensionRequestedAKZone2AirForce() { + return this.buildDefault('IntlHHGMoveOriginSITExtensionRequestedAKZone2AirForce'); + } + + async buildIntlHHGMoveOriginSITExtensionRequestedAKZone1SpaceForce() { + return this.buildDefault('IntlHHGMoveOriginSITExtensionRequestedAKZone1SpaceForce'); + } + + async buildIntlHHGMoveOriginSITExtensionRequestedAKZone2SpaceForce() { + return this.buildDefault('IntlHHGMoveOriginSITExtensionRequestedAKZone2SpaceForce'); + } + + async buildIntlHHGMoveOriginSITExtensionRequestedAKZone1USMC() { + return this.buildDefault('IntlHHGMoveOriginSITExtensionRequestedAKZone1USMC'); + } + + async buildIntlHHGMoveOriginSITExtensionRequestedAKZone2USMC() { + return this.buildDefault('IntlHHGMoveOriginSITExtensionRequestedAKZone2USMC'); + } + + /** + * Use testharness to build international move with a pending SIT extension request with destination SIT + * @returns {Promise} + */ + async buildIntlHHGMoveDestSITExtensionRequestedAKZone1Army() { + return this.buildDefault('IntlHHGMoveDestSITExtensionRequestedAKZone1Army'); + } + + async buildIntlHHGMoveDestSITExtensionRequestedAKZone2Army() { + return this.buildDefault('IntlHHGMoveDestSITExtensionRequestedAKZone2Army'); + } + + async buildIntlHHGMoveDestSITExtensionRequestedAKZone1AirForce() { + return this.buildDefault('IntlHHGMoveDestSITExtensionRequestedAKZone1AirForce'); + } + + async buildIntlHHGMoveDestSITExtensionRequestedAKZone2AirForce() { + return this.buildDefault('IntlHHGMoveDestSITExtensionRequestedAKZone2AirForce'); + } + + async buildIntlHHGMoveDestSITExtensionRequestedAKZone1SpaceForce() { + return this.buildDefault('IntlHHGMoveDestSITExtensionRequestedAKZone1SpaceForce'); + } + + async buildIntlHHGMoveDestSITExtensionRequestedAKZone2SpaceForce() { + return this.buildDefault('IntlHHGMoveDestSITExtensionRequestedAKZone2SpaceForce'); + } + + async buildIntlHHGMoveDestSITExtensionRequestedAKZone1USMC() { + return this.buildDefault('IntlHHGMoveDestSITExtensionRequestedAKZone1USMC'); + } + + async buildIntlHHGMoveDestSITExtensionRequestedAKZone2USMC() { + return this.buildDefault('IntlHHGMoveDestSITExtensionRequestedAKZone2USMC'); + } + + /** + * Use testharness to build international move with an at risk of excess weight + * @returns {Promise} + */ + async buildIntlHHGMoveExcessWeightAKZone1Army() { + return this.buildDefault('IntlHHGMoveExcessWeightAKZone1Army'); + } + + async buildIntlHHGMoveExcessWeightAKZone2Army() { + return this.buildDefault('IntlHHGMoveExcessWeightAKZone2Army'); + } + + /** + * Use testharness to build international move with an at risk of UB excess weight + * @returns {Promise} + */ + async buildIntlUBMoveExcessWeightAKZone1Army() { + return this.buildDefault('IntlUBMoveExcessWeightAKZone1Army'); + } + + async buildIntlUBMoveExcessWeightAKZone2Army() { + return this.buildDefault('IntlUBMoveExcessWeightAKZone2Army'); + } } export default TestHarness; diff --git a/scripts/README.md b/scripts/README.md index 46aa61a74c1..d010b67b7fe 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -170,6 +170,7 @@ migrations. | `download-secure-migration` | A script to download secure migrations from all environments | | `generate-secure-migration` | A script to help manage the creation of secure migrations | | `upload-secure-migration` | A script to upload secure migrations to all environments in both commercial and GovCloud AWS | +| `generate-ddl-migration` | A script to help manage the creation of DDL migrations | ### Database Scripts diff --git a/scripts/generate-ddl-migration b/scripts/generate-ddl-migration new file mode 100755 index 00000000000..06a297168c2 --- /dev/null +++ b/scripts/generate-ddl-migration @@ -0,0 +1,22 @@ +#!/bin/bash + +dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +#version=$(date +"%Y%m%d%H%M%S") +filename=$1 +type=$2 + +if [ "$type" == "functions" ]; then + echo "fn_${filename}.up.sql" >> "${dir}/../migrations/app/ddl_functions_manifest.txt" + touch "${dir}/../migrations/app/ddl_migrations/ddl_functions/fn_${filename}.up.sql" +elif [ "$type" == "tables" ]; then + echo "tbl_${filename}.up.sql" >> "${dir}/../migrations/app/ddl_tables_manifest.txt" + touch "${dir}/../migrations/app/ddl_migrations/ddl_tables/tbl_${filename}.up.sql" +elif [ "$type" == "types" ]; then + echo "ty_${filename}.up.sql" >> "${dir}/../migrations/app/ddl_types_manifest.txt" + touch "${dir}/../migrations/app/ddl_migrations/ddl_types/ty_${filename}.up.sql" + elif [ "$type" == "views" ]; then + echo "vw_${filename}.up.sql" >> "${dir}/../migrations/app/ddl_views_manifest.txt" + touch "${dir}/../migrations/app/ddl_migrations/ddl_views/vw_${filename}.up.sql" +else + echo "Invalid type" +fi diff --git a/scripts/generate-secure-migration b/scripts/generate-secure-migration index f8053049d66..82a3adba869 100755 --- a/scripts/generate-secure-migration +++ b/scripts/generate-secure-migration @@ -83,5 +83,9 @@ EOM # # Update the migrations manifest # +# Add migration to DML manifest +readonly manifest_file="${dir}/../migrations/app/dml_migrations_manifest.txt" +echo "${secure_migration_name}" >> "$manifest_file" -./scripts/update-migrations-manifest + +##./scripts/update-migrations-manifest diff --git a/scripts/run-server-test b/scripts/run-server-test index 45e8ab87afe..497039f6186 100755 --- a/scripts/run-server-test +++ b/scripts/run-server-test @@ -47,6 +47,12 @@ else gotest_args+=("-parallel" "8") gotest_args+=("-failfast") fi + +## Add SKIP_FAIL_TESTS on dev machine within .envrc.local file +if [ -n "${SKIP_FAIL_TESTS+x}" ]; then + gotest_args+=("-skip" "TestGHCRateEngineImportSuite") +fi + ## mac users uncomment the following line to run tests with the classic linker, which clears a lot of warnings that fill the console, do not commit to repo uncommented #gotest_args+=("-ldflags=-extldflags=-Wl,-ld_classic") diff --git a/src/components/BulkAssignment/BulkAssignmentModal.jsx b/src/components/BulkAssignment/BulkAssignmentModal.jsx index ca58c6814a2..9b84c51b4ce 100644 --- a/src/components/BulkAssignment/BulkAssignmentModal.jsx +++ b/src/components/BulkAssignment/BulkAssignmentModal.jsx @@ -1,46 +1,126 @@ -import React from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import PropTypes from 'prop-types'; import { Button } from '@trussworks/react-uswds'; +import styles from './BulkAssignmentModal.module.scss'; + import Modal, { ModalTitle, ModalClose, ModalActions, connectModal } from 'components/Modal/Modal'; +import { getBulkAssignmentData } from 'services/ghcApi'; +import { milmoveLogger } from 'utils/milmoveLog'; +import { userName } from 'utils/formatters'; + +export const BulkAssignmentModal = ({ onClose, onSubmit, title, submitText, closeText, queueType }) => { + const [showCancelModal, setShowCancelModal] = useState(false); + const [bulkAssignmentData, setBulkAssignmentData] = useState(null); + const [isDisabled, setIsDisabled] = useState(false); + const [numberOfMoves, setNumberOfMoves] = useState(0); + const fetchData = useCallback(async () => { + try { + const data = await getBulkAssignmentData(queueType); + setBulkAssignmentData(data); + + if (!data.bulkAssignmentMoveIDs) { + setIsDisabled(true); + setNumberOfMoves(0); + } else { + setNumberOfMoves(data.bulkAssignmentMoveIDs.length); + } + } catch (err) { + milmoveLogger.error('Error fetching bulk assignment data:', err); + } + }, [queueType]); -export const BulkAssignmentModal = ({ onClose, onSubmit, title, content, submitText, closeText }) => ( - - onClose()} /> - -

{title}

-
-

{content}

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

+ {title} ({numberOfMoves}) +

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

{userName(user)}

+
+

{user.workload || 0}

+
+ +
+
+ {showCancelModal ? ( +
+ Any unsaved work will be lost. Are you sure you want to cancel? +
+ + +
+
+ ) : ( + + + + + )} +
+
+ ); +}; BulkAssignmentModal.propTypes = { onClose: PropTypes.func.isRequired, onSubmit: PropTypes.func.isRequired, title: PropTypes.string, - content: PropTypes.string, submitText: PropTypes.string, closeText: PropTypes.string, }; BulkAssignmentModal.defaultProps = { title: 'Bulk Assignment', - content: 'Here we will display moves to be assigned in bulk.', submitText: 'Save', closeText: 'Cancel', }; diff --git a/src/components/BulkAssignment/BulkAssignmentModal.module.scss b/src/components/BulkAssignment/BulkAssignmentModal.module.scss new file mode 100644 index 00000000000..4fa9fd266a5 --- /dev/null +++ b/src/components/BulkAssignment/BulkAssignmentModal.module.scss @@ -0,0 +1,72 @@ +@import 'shared/styles/colors.scss'; + +.BulkModal { + min-width: 650px !important; + max-width: 90vw; + overflow-y: auto; + max-height: 90vh; + overflow-x: hidden; + + button, + :global(.usa-button) { + margin: 0; + flex-grow: 0; + flex-basis: auto; + text-decoration: none; + font-size: 1rem; + } + + /* these styles are used to fix an issue with duplicate usa-button--secondary definitions in USWDS */ + .cancelNoButton { + border-radius: 0.2666666667rem; + color: #0050d8; + background-color: transparent; + box-shadow: inset 0 0 0 2px #0050d8; + padding-left: 1.6rem; + padding-right: 1.6rem; + padding-bottom: 0.8rem; + padding-top: 0.8rem; + height: 42px; + width: 68px; + } +} + +.BulkAssignmentTable { + table { + th { + max-width: 10px; + text-align: center; + } + .BulkAssignmentDataCenter { + text-align: center; + } + .BulkAssignmentAssignment { + width: 60px; + text-align: center; + padding-left: 15px; + padding-top: 4px; + } + } +} + +.areYouSureSection { + display: flex; + justify-content: space-evenly; + align-items: center; + padding-top: 20px; + + .confirmButtons { + display: flex; + align-items: center; + gap: 10px; + padding: 10px; + max-height: 42px; + } + + .cancelYesButton { + font-size: 12px; + max-width: 100px; + height: 42px; + align-items: center; + } +} diff --git a/src/components/BulkAssignment/BulkAssignmentModal.test.jsx b/src/components/BulkAssignment/BulkAssignmentModal.test.jsx index 4ffc69e7bd3..d1cf48141ef 100644 --- a/src/components/BulkAssignment/BulkAssignmentModal.test.jsx +++ b/src/components/BulkAssignment/BulkAssignmentModal.test.jsx @@ -1,8 +1,10 @@ import React from 'react'; -import { render, screen } from '@testing-library/react'; +import { act, render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { BulkAssignmentModal } from 'components/BulkAssignment/BulkAssignmentModal'; +import { QUEUE_TYPES } from 'constants/queues'; +import { MockProviders } from 'testUtils'; let onClose; let onSubmit; @@ -11,40 +13,105 @@ beforeEach(() => { onSubmit = jest.fn(); }); +const bulkAssignmentData = { + availableOfficeUsers: [ + { + firstName: 'sc', + lastName: 'user', + officeUserId: '045c3048-df9a-4d44-88ed-8cd6e2100e08', + workload: 1, + }, + { + firstName: 'test1', + lastName: 'person', + officeUserId: '4b1f2722-b0bf-4b16-b8c4-49b4e49ba42a', + }, + ], + bulkAssignmentMoveIDs: [ + 'b3baf6ce-f43b-437c-85be-e1145c0ddb96', + '962ce8d2-03a2-435c-94ca-6b9ef6c226c1', + 'fee7f916-35a6-4c0b-9ea6-a1d8094b3ed3', + ], +}; + +jest.mock('services/ghcApi', () => ({ + getBulkAssignmentData: jest.fn().mockImplementation(() => Promise.resolve(bulkAssignmentData)), +})); + describe('BulkAssignmentModal', () => { it('renders the component', async () => { - render(); + render( + + + , + ); - expect(await screen.findByRole('heading', { level: 3, name: 'Bulk Assignment' })).toBeInTheDocument(); + expect(await screen.findByRole('heading', { level: 3, name: 'Bulk Assignment (3)' })).toBeInTheDocument(); }); - it('closes the modal when close icon is clicked', async () => { + it('shows cancel confirmation modal when close icon is clicked', async () => { render(); const closeButton = await screen.findByTestId('modalCloseButton'); await userEvent.click(closeButton); - expect(onClose).toHaveBeenCalledTimes(1); + expect(screen.getByTestId('cancelModalYes')).toBeInTheDocument(); }); - it('closes the modal when the Cancel button is clicked', async () => { + it('shows cancel confirmation modal when the Cancel button is clicked', async () => { render(); const cancelButton = await screen.findByRole('button', { name: 'Cancel' }); await userEvent.click(cancelButton); - expect(onClose).toHaveBeenCalledTimes(1); + expect(screen.getByTestId('cancelModalYes')).toBeInTheDocument(); }); it('calls the submit function when Save button is clicked', async () => { render(); + const saveButton = await screen.findByTestId('modalSubmitButton'); + await userEvent.click(saveButton); + expect(onSubmit).toHaveBeenCalledTimes(1); + }); - const saveButton = await screen.findByRole('button', { name: 'Save' }); + it('renders the user data', async () => { + render(); + const userTable = await screen.findByRole('table'); + expect(userTable).toBeInTheDocument(); + expect(screen.getByText('User')).toBeInTheDocument(); + expect(screen.getByText('Workload')).toBeInTheDocument(); + expect(screen.getByText('Assignment')).toBeInTheDocument(); + await act(async () => { + expect(await screen.getByText('user, sc')).toBeInTheDocument(); + }); + expect(screen.getAllByTestId('bulkAssignmentUserWorkload')[0]).toHaveTextContent('1'); + }); - await userEvent.click(saveButton); + it('closes the modal when the close is confirmed', async () => { + render(); - expect(onSubmit).toHaveBeenCalledTimes(1); + const closeButton = await screen.findByTestId('modalCloseButton'); + + await userEvent.click(closeButton); + + const confirmButton = await screen.findByTestId('cancelModalYes'); + await userEvent.click(confirmButton); + + expect(onClose).toHaveBeenCalledTimes(1); + }); + + it('close confirmation goes away when clicking no', async () => { + render(); + + const closeButton = await screen.findByTestId('modalCloseButton'); + await userEvent.click(closeButton); + + const cancelModalNo = await screen.findByTestId('cancelModalNo'); + await userEvent.click(cancelModalNo); + + const confirmButton = await screen.queryByTestId('cancelModalYes'); + expect(confirmButton).not.toBeInTheDocument(); }); }); diff --git a/src/components/Office/AllowancesDetailForm/AllowancesDetailForm.jsx b/src/components/Office/AllowancesDetailForm/AllowancesDetailForm.jsx index 7b38d00bb68..cfbcd359779 100644 --- a/src/components/Office/AllowancesDetailForm/AllowancesDetailForm.jsx +++ b/src/components/Office/AllowancesDetailForm/AllowancesDetailForm.jsx @@ -21,7 +21,7 @@ const AllowancesDetailForm = ({ header, entitlements, branchOptions, formIsDisab entitlements?.dependentsTwelveAndOver || entitlements?.dependentsUnderTwelve ); - const { setFieldValue } = useFormikContext(); + const { values, setFieldValue } = useFormikContext(); const [isAdminWeightLocationChecked, setIsAdminWeightLocationChecked] = useState(entitlements?.weightRestriction > 0); useEffect(() => { // Functional component version of "componentDidMount" @@ -37,20 +37,20 @@ const AllowancesDetailForm = ({ header, entitlements, branchOptions, formIsDisab useEffect(() => { if (!isAdminWeightLocationChecked) { - // Find the weight restriction input and reset its value to 0 - const weightRestrictionInput = document.getElementById('weightRestrictionId'); - if (weightRestrictionInput) { - weightRestrictionInput.value = ''; - } + setFieldValue('weightRestriction', `${values.weightRestriction}`); } - }, [isAdminWeightLocationChecked]); + }, [setFieldValue, values.weightRestriction, isAdminWeightLocationChecked]); const handleAdminWeightLocationChange = (e) => { const isChecked = e.target.checked; setIsAdminWeightLocationChecked(isChecked); if (!isChecked) { - setFieldValue('weightRestriction', ''); + setFieldValue('weightRestriction', `${values.weightRestriction}`); + } else if (isChecked && values.weightRestriction) { + setFieldValue('weightRestriction', `${values.weightRestriction}`); + } else { + setFieldValue('weightRestriction', null); } }; @@ -205,14 +205,13 @@ const AllowancesDetailForm = ({ header, entitlements, branchOptions, formIsDisab )} diff --git a/src/components/Office/AllowancesDetailForm/AllowancesDetailForm.test.jsx b/src/components/Office/AllowancesDetailForm/AllowancesDetailForm.test.jsx index fcf825bd404..3b0e5dbe29e 100644 --- a/src/components/Office/AllowancesDetailForm/AllowancesDetailForm.test.jsx +++ b/src/components/Office/AllowancesDetailForm/AllowancesDetailForm.test.jsx @@ -12,6 +12,7 @@ const initialValues = { proGearWeightSpouse: '500', requiredMedicalEquipmentWeight: '1000', organizationalClothingAndIndividualEquipment: true, + weightRestriction: '500', }; const initialValuesOconusAdditions = { @@ -81,6 +82,7 @@ const entitlements = { storageInTransit: 90, totalWeight: 11000, totalDependents: 2, + weightRestriction: 500, }; const entitlementOconusAdditions = { @@ -177,13 +179,28 @@ describe('AllowancesDetailForm additional tests', () => { , ); + const adminWeightCheckbox = await screen.findByTestId('adminWeightLocation'); + expect(adminWeightCheckbox).toBeInTheDocument(); + expect(screen.getByLabelText('Admin restricted weight location')).toBeChecked(); + + const weightRestrictionInput = screen.getByTestId('weightRestrictionInput'); + expect(weightRestrictionInput).toBeInTheDocument(); + expect(weightRestrictionInput).toHaveValue('500'); + }); + + it('does not render the admin weight location section when the weightRestriction entitlement is null', async () => { + render( + + + , + ); + const adminWeightCheckbox = await screen.findByTestId('adminWeightLocation'); expect(adminWeightCheckbox).toBeInTheDocument(); expect(screen.queryByTestId('weightRestrictionInput')).not.toBeInTheDocument(); - await act(async () => { - adminWeightCheckbox.click(); - }); - expect(screen.getByTestId('weightRestrictionInput')).toBeInTheDocument(); }); it('displays the total weight allowance correctly', async () => { diff --git a/src/components/Office/PaymentRequestDetails/PaymentRequestDetails.jsx b/src/components/Office/PaymentRequestDetails/PaymentRequestDetails.jsx index 4dbd4e2f0b0..e1c38c20c91 100644 --- a/src/components/Office/PaymentRequestDetails/PaymentRequestDetails.jsx +++ b/src/components/Office/PaymentRequestDetails/PaymentRequestDetails.jsx @@ -106,8 +106,8 @@ const PaymentRequestDetails = ({ const [headingType, shipmentStyle] = shipmentHeadingAndStyle(mtoShipmentType); const { modificationType, departureDate, address, mtoServiceItems } = shipment; - const findAdditionalServiceItemData = (mtoServiceItemCode) => { - return mtoServiceItems?.find((mtoServiceItem) => mtoServiceItem.reServiceCode === mtoServiceItemCode); + const findAdditionalServiceItemData = (mtoServiceItemID) => { + return mtoServiceItems?.find((mtoServiceItem) => mtoServiceItem.id === mtoServiceItemID); }; return ( @@ -167,7 +167,7 @@ const PaymentRequestDetails = ({ return ( { expect(screen.queryByText('HHG')).not.toBeInTheDocument(); }); + + it('should not display move management fee if move management service item is not requested', () => { + render( + , + ); + + expect(screen.queryByText('Move management fee')).not.toBeInTheDocument(); + expect(screen.getByText('Counseling fee')).toBeInTheDocument(); + expect(screen.getByText('$20.65')).toBeInTheDocument(); + }); + + it('should not display counseling fee if counseling service item is not requested', () => { + render( + , + ); + + expect(screen.queryByText('Counseling fee')).not.toBeInTheDocument(); + expect(screen.getByText('Move management fee')).toBeInTheDocument(); + expect(screen.getByText('$44.33')).toBeInTheDocument(); + }); }); describe('can display codes', () => { diff --git a/src/components/Office/ServiceItemCalculations/ServiceItemCalculations.jsx b/src/components/Office/ServiceItemCalculations/ServiceItemCalculations.jsx index fea91773c2e..9e4fd608148 100644 --- a/src/components/Office/ServiceItemCalculations/ServiceItemCalculations.jsx +++ b/src/components/Office/ServiceItemCalculations/ServiceItemCalculations.jsx @@ -83,7 +83,7 @@ const ServiceItemCalculations = ({ {calc.label} - {appendSign(index, calculations.length)} + {calc.value === null || calc.value === '' ? null : appendSign(index, calculations.length)} {calc.value} diff --git a/src/components/Office/ServiceItemCalculations/ServiceItemCalculations.module.scss b/src/components/Office/ServiceItemCalculations/ServiceItemCalculations.module.scss index 48e25cd1efb..d7b5246dc2b 100644 --- a/src/components/Office/ServiceItemCalculations/ServiceItemCalculations.module.scss +++ b/src/components/Office/ServiceItemCalculations/ServiceItemCalculations.module.scss @@ -102,6 +102,7 @@ .descriptionTitle { @include u-font-weight('bold'); + @include u-margin-right(0.5); color: $base-darker; } diff --git a/src/components/Office/ServiceItemCalculations/helpers.js b/src/components/Office/ServiceItemCalculations/helpers.js index 8412e4192d7..ca185d5c87f 100644 --- a/src/components/Office/ServiceItemCalculations/helpers.js +++ b/src/components/Office/ServiceItemCalculations/helpers.js @@ -1,4 +1,9 @@ -import { SERVICE_ITEM_CALCULATION_LABELS, SERVICE_ITEM_CODES, SERVICE_ITEM_PARAM_KEYS } from 'constants/serviceItems'; +import { + SERVICE_ITEM_CALCULATION_LABELS, + SERVICE_ITEM_CODES, + SERVICE_ITEM_PARAM_KEYS, + EXTERNAL_CRATE_MIN_CUBIC_FT, +} from 'constants/serviceItems'; import { LONGHAUL_MIN_DISTANCE } from 'constants/shipments'; import { formatDateWithUTC } from 'shared/dates'; import { @@ -28,6 +33,13 @@ const peak = (params) => { }`; }; +const getMarket = (params) => { + const marketValue = + getParamValue(SERVICE_ITEM_PARAM_KEYS.MarketOrigin, params) || + getParamValue(SERVICE_ITEM_PARAM_KEYS.MarketDest, params); + return ` ${marketValue?.toLowerCase() === 'o' ? 'OCONUS' : 'CONUS'}`; +}; + const serviceAreaOrigin = (params) => { return `${SERVICE_ITEM_CALCULATION_LABELS[SERVICE_ITEM_PARAM_KEYS.ServiceAreaOrigin]}: ${getParamValue( SERVICE_ITEM_PARAM_KEYS.ServiceAreaOrigin, @@ -368,6 +380,20 @@ const shuttleOriginPriceDomestic = (params) => { ); }; +const shuttleOriginPriceInternational = (params) => { + const value = getPriceRateOrFactor(params); + const label = SERVICE_ITEM_CALCULATION_LABELS.OriginPrice; + + const pickupDate = `${SERVICE_ITEM_CALCULATION_LABELS.PickupDate}: ${formatDateWithUTC( + getParamValue(SERVICE_ITEM_PARAM_KEYS.ReferenceDate, params), + 'DD MMM YYYY', + )}`; + + const market = getParamValue(SERVICE_ITEM_PARAM_KEYS.MarketDest, params) === 'O' ? 'Oconus' : 'Conus'; + + return calculation(value, label, formatDetail(pickupDate), formatDetail(market)); +}; + // There is no param representing the destination price as available in the re_domestic_service_area_prices table // A param to return the service schedule is also not being created const destinationPrice = (params, shipmentType) => { @@ -406,6 +432,20 @@ const shuttleDestinationPriceDomestic = (params) => { ); }; +const shuttleDestinationPriceInternational = (params) => { + const value = getPriceRateOrFactor(params); + const label = SERVICE_ITEM_CALCULATION_LABELS.DestinationPrice; + + const deliveryDate = `${SERVICE_ITEM_CALCULATION_LABELS.DeliveryDate}: ${formatDateWithUTC( + getParamValue(SERVICE_ITEM_PARAM_KEYS.ReferenceDate, params), + 'DD MMM YYYY', + )}`; + + const market = getParamValue(SERVICE_ITEM_PARAM_KEYS.MarketDest, params) === 'O' ? 'OCONUS' : 'CONUS'; + + return calculation(value, label, formatDetail(deliveryDate), formatDetail(market)); +}; + const priceEscalationFactor = (params) => { const value = getParamValue(SERVICE_ITEM_PARAM_KEYS.EscalationCompounded, params) ? getParamValue(SERVICE_ITEM_PARAM_KEYS.EscalationCompounded, params) @@ -647,18 +687,71 @@ const unCratingPrice = (params) => { ); }; +const cratingPriceIntl = (params) => { + const value = getParamValue(SERVICE_ITEM_PARAM_KEYS.PriceRateOrFactor, params); + const label = SERVICE_ITEM_CALCULATION_LABELS.CratingPrice; + + return calculation(value, label, formatDetail(cratingDate(params)), formatDetail(getMarket(params))); +}; + +const unCratingPriceIntl = (params) => { + const value = getParamValue(SERVICE_ITEM_PARAM_KEYS.PriceRateOrFactor, params); + const label = SERVICE_ITEM_CALCULATION_LABELS.UncratingPrice; + + return calculation(value, label, formatDetail(unCratingDate(params)), formatDetail(getMarket(params))); +}; + +const isExternalCrateMinSizeApplied = (params) => { + const cubicFeetBilled = getParamValue(SERVICE_ITEM_PARAM_KEYS.CubicFeetBilled, params); + const cubicFeetCrating = getParamValue(SERVICE_ITEM_PARAM_KEYS.CubicFeetCrating, params); + const externalCrate = + getParamValue(SERVICE_ITEM_PARAM_KEYS.ExternalCrate, params)?.toLowerCase() === 'true' + ? SERVICE_ITEM_CALCULATION_LABELS.ExternalCrate + : ''; + + return ( + cubicFeetCrating !== cubicFeetBilled && externalCrate && cubicFeetBilled?.toString() === EXTERNAL_CRATE_MIN_CUBIC_FT + ); +}; + const cratingSize = (params, mtoParams) => { - const value = getParamValue(SERVICE_ITEM_PARAM_KEYS.CubicFeetBilled, params); + const cubicFeetBilled = getParamValue(SERVICE_ITEM_PARAM_KEYS.CubicFeetBilled, params); const length = getParamValue(SERVICE_ITEM_PARAM_KEYS.DimensionLength, params); const height = getParamValue(SERVICE_ITEM_PARAM_KEYS.DimensionHeight, params); const width = getParamValue(SERVICE_ITEM_PARAM_KEYS.DimensionWidth, params); - const label = SERVICE_ITEM_CALCULATION_LABELS.CubicFeetBilled; + let label = SERVICE_ITEM_CALCULATION_LABELS.CubicFeetBilled; + let cubicFeetCratingInfo = ''; const description = `${SERVICE_ITEM_CALCULATION_LABELS.Description}: ${mtoParams.description}`; const formattedDimensions = `${SERVICE_ITEM_CALCULATION_LABELS.Dimensions}: ${length}x${width}x${height} in`; - return calculation(value, label, formatDetail(description), formatDetail(formattedDimensions)); + const externalCrate = + getParamValue(SERVICE_ITEM_PARAM_KEYS.ExternalCrate, params)?.toLowerCase() === 'true' + ? SERVICE_ITEM_CALCULATION_LABELS.ExternalCrate + : ''; + + // currently external intl crate gets 4 cu ft min applied to pricing + const isMinCrateSizeApplied = isExternalCrateMinSizeApplied(params); + + if (isMinCrateSizeApplied) { + label += ' - Minimum'; + + // show actual size if minimum was applied + cubicFeetCratingInfo = `${SERVICE_ITEM_CALCULATION_LABELS.CubicFeetCrating}: ${getParamValue( + SERVICE_ITEM_PARAM_KEYS.CubicFeetCrating, + params, + )} cu ft`; + } + + return calculation( + cubicFeetBilled, + label, + formatDetail(description), + formatDetail(formattedDimensions), + formatDetail(cubicFeetCratingInfo), + formatDetail(externalCrate), + ); }; const standaloneCrate = (params) => { @@ -680,11 +773,17 @@ const standaloneCrate = (params) => { const uncappedRequestTotal = (params) => { const uncappedTotal = getParamValue(SERVICE_ITEM_PARAM_KEYS.UncappedRequestTotal, params); const value = toDollarString(uncappedTotal); - const label = `${SERVICE_ITEM_CALCULATION_LABELS.UncappedRequestTotal}:`; + const label = `${SERVICE_ITEM_CALCULATION_LABELS.UncappedRequestTotal}`; return calculation(value, label); }; +const minSizeCrateApplied = () => { + const label = SERVICE_ITEM_CALCULATION_LABELS.MinSizeCrateApplied; + + return calculation('', label); +}; + const totalAmountRequested = (totalAmount) => { const value = toDollarString(formatCents(totalAmount)); const label = `${SERVICE_ITEM_CALCULATION_LABELS.Total}: `; @@ -851,6 +950,15 @@ export default function makeCalculations(itemCode, totalAmount, params, mtoParam totalAmountRequested(totalAmount), ]; break; + // International origin shuttle service + case SERVICE_ITEM_CODES.IOSHUT: + result = [ + shuttleBillableWeight(params), + shuttleOriginPriceInternational(params), + priceEscalationFactorWithoutContractYear(params), + totalAmountRequested(totalAmount), + ]; + break; // Domestic Destination Additional Days SIT case SERVICE_ITEM_CODES.DDASIT: result = [ @@ -879,6 +987,15 @@ export default function makeCalculations(itemCode, totalAmount, params, mtoParam totalAmountRequested(totalAmount), ]; break; + // International destination shuttle service + case SERVICE_ITEM_CODES.IDSHUT: + result = [ + shuttleBillableWeight(params), + shuttleDestinationPriceInternational(params), + priceEscalationFactorWithoutContractYear(params), + totalAmountRequested(totalAmount), + ]; + break; // Domestic crating case SERVICE_ITEM_CODES.DCRT: result = [ @@ -950,6 +1067,34 @@ export default function makeCalculations(itemCode, totalAmount, params, mtoParam totalAmountRequested(totalAmount), ]; break; + // International crating + case SERVICE_ITEM_CODES.ICRT: + result = [ + cratingSize(params, mtoParams), + cratingPriceIntl(params), + priceEscalationFactorWithoutContractYear(params), + totalAmountRequested(totalAmount), + ]; + if ( + SERVICE_ITEM_PARAM_KEYS.StandaloneCrate !== null && + getParamValue(SERVICE_ITEM_PARAM_KEYS.StandaloneCrate, params) === 'true' + ) { + result.splice(result.length - 1, 0, uncappedRequestTotal(params)); + result.splice(result.length - 1, 0, standaloneCrate(params)); + } + if (isExternalCrateMinSizeApplied(params)) { + result.splice(result.length - 1, 0, minSizeCrateApplied(params)); + } + break; + // International uncrating + case SERVICE_ITEM_CODES.IUCRT: + result = [ + cratingSize(params, mtoParams), + unCratingPriceIntl(params), + priceEscalationFactorWithoutContractYear(params), + totalAmountRequested(totalAmount), + ]; + break; default: break; } diff --git a/src/components/Office/ServiceItemCalculations/helpers.test.js b/src/components/Office/ServiceItemCalculations/helpers.test.js index 8ca38112f65..4ff4fb3e02b 100644 --- a/src/components/Office/ServiceItemCalculations/helpers.test.js +++ b/src/components/Office/ServiceItemCalculations/helpers.test.js @@ -11,12 +11,12 @@ function testData(code) { 'Crating size (cu ft)': '4.00', }; } - if (code === 'DCRT') { + if (code === 'DCRT' || code === 'ICRT') { result = { ...result, 'Crating price (per cu ft)': '1.71', }; - } else if (code === 'DUCRT') { + } else if (code === 'DUCRT' || code === 'IUCRT') { result = { ...result, 'Uncrating price (per cu ft)': '1.71', @@ -363,4 +363,28 @@ describe('DomesticDestinationSITDelivery', () => { const expected = testData('PODFSC'); testAB(result, expected); }); + + it('returns correct data for ICRT', () => { + const result = makeCalculations( + 'ICRT', + 99999, + testParams.InternationalCrating, + testParams.additionalCratingDataDCRT, + ); + const expected = testData('ICRT'); + + testAB(result, expected); + }); + + it('returns correct data for IUCRT', () => { + const result = makeCalculations( + 'IUCRT', + 99999, + testParams.InternationalUncrating, + testParams.additionalCratingDataDCRT, + ); + const expected = testData('IUCRT'); + + testAB(result, expected); + }); }); diff --git a/src/components/Office/ServiceItemCalculations/serviceItemTestParams.js b/src/components/Office/ServiceItemCalculations/serviceItemTestParams.js index e234dc167c9..bf29cdd1807 100644 --- a/src/components/Office/ServiceItemCalculations/serviceItemTestParams.js +++ b/src/components/Office/ServiceItemCalculations/serviceItemTestParams.js @@ -493,6 +493,33 @@ const PortZip = { type: 'STRING', value: '99505', }; +const ExternalCrate = { + eTag: 'MjAyMS0wNy0yOVQyMDoxNTowMS4xNDA1MjZa', + id: 'f5bb063e-38da-4c86-88ce-a6a328e70b92', + key: 'ExternalCrate', + origin: 'PRIME', + paymentServiceItemID: '28039a62-387d-479f-b50f-e0041b7e6e22', + type: 'BOOLEAN', + value: 'FALSE', +}; +const MarketOrigin = { + eTag: 'MjAyMS0wNy0yOVQyMDoxNTowMS4xNDA1MjZa', + id: 'f5bb063e-38da-4c86-88ce-a6a328e70b92', + key: 'MarketOrigin', + origin: 'PRIME', + paymentServiceItemID: '28039a62-387d-479f-b50f-e0041b7e6e22', + type: 'STRING', + value: 'O', +}; +const MarketDest = { + eTag: 'MjAyMS0wNy0yOVQyMDoxNTowMS4xNDA1MjZa', + id: 'f5bb063e-38da-4c86-88ce-a6a328e70b92', + key: 'MarketDest', + origin: 'PRIME', + paymentServiceItemID: '28039a62-387d-479f-b50f-e0041b7e6e22', + type: 'STRING', + value: 'C', +}; const testParams = { DomesticLongHaul: [ ContractCode, @@ -916,6 +943,33 @@ const testParams = { ZipPickupAddress, PortZip, ], + InternationalCrating: [ + ContractYearName, + EscalationCompounded, + PriceRateOrFactor, + ReferenceDate, + CubicFeetBilled, + MarketOrigin, + ServiceAreaOrigin, + ZipPickupAddress, + DimensionWidth, + DimensionHeight, + DimensionLength, + StandaloneCrate, + ExternalCrate, + ], + InternationalUncrating: [ + ReferenceDate, + EscalationCompounded, + CubicFeetBilled, + PriceRateOrFactor, + MarketDest, + ServiceAreaDest, + ZipDestAddress, + DimensionWidth, + DimensionHeight, + DimensionLength, + ], additionalCratingDataDCRT: { reServiceCode: 'DCRT', description: 'Grand piano', diff --git a/src/components/Office/ServiceItemDetails/ServiceItemDetails.jsx b/src/components/Office/ServiceItemDetails/ServiceItemDetails.jsx index 76be9ca5d57..416134e0672 100644 --- a/src/components/Office/ServiceItemDetails/ServiceItemDetails.jsx +++ b/src/components/Office/ServiceItemDetails/ServiceItemDetails.jsx @@ -481,6 +481,39 @@ const ServiceItemDetails = ({ id, code, details, serviceRequestDocs, shipment, s ); break; } + case 'IOSHUT': + case 'IDSHUT': { + const estimatedWeight = details.estimatedWeight != null ? formatWeight(details.estimatedWeight) : `— lbs`; + detailSection = ( +
+
+
+
{estimatedWeight}
estimated weight
+
+ {generateDetailText({ + 'Estimated Price': details.estimatedPrice ? toDollarString(formatCents(details.estimatedPrice)) : '-', + })} + {generateDetailText({ Reason: details.reason })} + {generateDetailText({ Market: details.market })} + {details.rejectionReason && + generateDetailText({ 'Rejection reason': details.rejectionReason }, id, 'margin-top-2')} + {!isEmpty(serviceRequestDocUploads) ? ( +
+

Download service item documentation:

+ {serviceRequestDocUploads.map((file) => ( + + ))} +
+ ) : null} +
+
+ ); + break; + } case 'DLH': case 'DSH': case 'FSC': diff --git a/src/components/Office/ServiceItemDetails/ServiceItemDetails.test.jsx b/src/components/Office/ServiceItemDetails/ServiceItemDetails.test.jsx index fab0ded5351..53aa270d13f 100644 --- a/src/components/Office/ServiceItemDetails/ServiceItemDetails.test.jsx +++ b/src/components/Office/ServiceItemDetails/ServiceItemDetails.test.jsx @@ -475,6 +475,33 @@ describe('ServiceItemDetails Domestic Shuttling', () => { }); }); +describe('ServiceItemDetails International Shuttling', () => { + const shuttleDetails = { + ...details, + market: 'OCONUS', + }; + + it.each([['IOSHUT'], ['IDSHUT']])('renders formatted estimated weight and reason', (code) => { + render(); + + expect(screen.getByText('2,500 lbs')).toBeInTheDocument(); + expect(screen.getByText('estimated weight')).toBeInTheDocument(); + expect(screen.getByText('Reason:')).toBeInTheDocument(); + expect(screen.getByText('Market:')).toBeInTheDocument(); + expect(screen.getByText('some reason')).toBeInTheDocument(); + expect(screen.getByText('Download service item documentation:')).toBeInTheDocument(); + const downloadLink = screen.getByText('receipt.pdf'); + expect(downloadLink).toBeInstanceOf(HTMLAnchorElement); + }); + + it.each([['DOSHUT'], ['DDSHUT']])('renders estimated weight nil values with an em dash', (code) => { + render(); + + expect(screen.getByText('— lbs')).toBeInTheDocument(); + expect(screen.getByText('estimated weight')).toBeInTheDocument(); + }); +}); + describe('ServiceItemDetails Crating Rejected', () => { it('renders the rejection reason field when it is populated with information', () => { render( diff --git a/src/components/Office/ShipmentServiceItemsTable/ShipmentServiceItemsTable.test.jsx b/src/components/Office/ShipmentServiceItemsTable/ShipmentServiceItemsTable.test.jsx index 592ae52467a..d896ad62a6f 100644 --- a/src/components/Office/ShipmentServiceItemsTable/ShipmentServiceItemsTable.test.jsx +++ b/src/components/Office/ShipmentServiceItemsTable/ShipmentServiceItemsTable.test.jsx @@ -11,14 +11,14 @@ const reServiceItemResponse = [ isAutoApproved: true, marketCode: 'i', serviceCode: 'POEFSC', - serviceName: 'International POE Fuel Surcharge', + serviceName: 'International POE fuel surcharge', shipmentType: 'UNACCOMPANIED_BAGGAGE', }, { isAutoApproved: true, marketCode: 'i', serviceCode: 'PODFSC', - serviceName: 'International POD Fuel Surcharge', + serviceName: 'International POD fuel surcharge', shipmentType: 'UNACCOMPANIED_BAGGAGE', }, { @@ -46,21 +46,21 @@ const reServiceItemResponse = [ isAutoApproved: true, marketCode: 'i', serviceCode: 'POEFSC', - serviceName: 'International POE Fuel Surcharge', + serviceName: 'International POE fuel surcharge', shipmentType: 'HHG', }, { isAutoApproved: true, marketCode: 'i', serviceCode: 'PODFSC', - serviceName: 'International POD Fuel Surcharge', + serviceName: 'International POD fuel surcharge', shipmentType: 'HHG', }, { isAutoApproved: true, marketCode: 'i', serviceCode: 'ISLH', - serviceName: 'International Shipping & Linehaul', + serviceName: 'International shipping & linehaul', shipmentType: 'HHG', }, { @@ -200,25 +200,25 @@ const reServiceItemResponse = [ { marketCode: 'i', serviceCode: 'IOSFSC', - serviceName: 'International Origin SIT Fuel Surcharge', + serviceName: 'International origin SIT fuel surcharge', shipmentType: 'HHG', }, { marketCode: 'i', serviceCode: 'IDSFSC', - serviceName: 'International Destination SIT Fuel Surcharge', + serviceName: 'International destination SIT fuel surcharge', shipmentType: 'HHG', }, { marketCode: 'i', serviceCode: 'IOSFSC', - serviceName: 'International Origin SIT Fuel Surcharge', + serviceName: 'International origin SIT fuel surcharge', shipmentType: 'UNACCOMPANIED_BAGGAGE', }, { marketCode: 'i', serviceCode: 'IDSFSC', - serviceName: 'International Destination SIT Fuel Surcharge', + serviceName: 'International destination SIT fuel furcharge', shipmentType: 'UNACCOMPANIED_BAGGAGE', }, ]; @@ -371,7 +371,7 @@ describe('Shipment Service Items Table', () => { describe('renders the intl UB shipment type (CONUS -> OCONUS) with service items', () => { it.each([ ['International UB price'], - ['International POE Fuel Surcharge'], + ['International POE fuel surcharge'], ['International UB pack'], ['International UB unpack'], ])('expects %s to be in the document', async (serviceItem) => { @@ -386,7 +386,7 @@ describe('Shipment Service Items Table', () => { describe('renders the intl UB shipment type (OCONUS -> CONUS) with service items', () => { it.each([ ['International UB price'], - ['International POD Fuel Surcharge'], + ['International POD fuel surcharge'], ['International UB pack'], ['International UB unpack'], ])('expects %s to be in the document', async (serviceItem) => { diff --git a/src/components/PrimeUI/CreateShipmentServiceItemForm/CreateShipmentServiceItemForm.jsx b/src/components/PrimeUI/CreateShipmentServiceItemForm/CreateShipmentServiceItemForm.jsx index b351755d15b..1bb2f9c517d 100644 --- a/src/components/PrimeUI/CreateShipmentServiceItemForm/CreateShipmentServiceItemForm.jsx +++ b/src/components/PrimeUI/CreateShipmentServiceItemForm/CreateShipmentServiceItemForm.jsx @@ -20,7 +20,7 @@ const CreateShipmentServiceItemForm = ({ shipment, createServiceItemMutation }) const { MTOServiceItemOriginSIT, MTOServiceItemDestSIT, - MTOServiceItemShuttle, + MTOServiceItemDomesticShuttle, MTOServiceItemDomesticCrating, MTOServiceItemInternationalCrating, MTOServiceItemInternationalShuttle, @@ -49,7 +49,7 @@ const CreateShipmentServiceItemForm = ({ shipment, createServiceItemMutation }) <> - + {enableAlaskaFeatureFlag && } @@ -61,7 +61,7 @@ const CreateShipmentServiceItemForm = ({ shipment, createServiceItemMutation }) {selectedServiceItemType === MTOServiceItemDestSIT && ( )} - {selectedServiceItemType === MTOServiceItemShuttle && ( + {selectedServiceItemType === MTOServiceItemDomesticShuttle && ( )} {selectedServiceItemType === MTOServiceItemInternationalShuttle && ( diff --git a/src/components/PrimeUI/CreateShipmentServiceItemForm/CreateShipmentServiceItemForm.test.jsx b/src/components/PrimeUI/CreateShipmentServiceItemForm/CreateShipmentServiceItemForm.test.jsx index f30c0b9f459..59c136325ce 100644 --- a/src/components/PrimeUI/CreateShipmentServiceItemForm/CreateShipmentServiceItemForm.test.jsx +++ b/src/components/PrimeUI/CreateShipmentServiceItemForm/CreateShipmentServiceItemForm.test.jsx @@ -88,7 +88,7 @@ describe('CreateShipmentServiceItemForm component', () => { it.each([ ['originSITServiceItemForm', createServiceItemModelTypes.MTOServiceItemOriginSIT], ['destinationSITServiceItemForm', createServiceItemModelTypes.MTOServiceItemDestSIT], - ['shuttleSITServiceItemForm', createServiceItemModelTypes.MTOServiceItemShuttle], + ['shuttleSITServiceItemForm', createServiceItemModelTypes.MTOServiceItemDomesticShuttle], ['DomesticCratingForm', createServiceItemModelTypes.MTOServiceItemDomesticCrating], ['InternationalCratingForm', createServiceItemModelTypes.MTOServiceItemInternationalCrating], ['InternationalShuttleServiceItemForm', createServiceItemModelTypes.MTOServiceItemInternationalShuttle], diff --git a/src/components/PrimeUI/CreateShipmentServiceItemForm/ShuttleSITServiceItemForm.jsx b/src/components/PrimeUI/CreateShipmentServiceItemForm/ShuttleSITServiceItemForm.jsx index 0bb6d6a3747..97dd8e8f128 100644 --- a/src/components/PrimeUI/CreateShipmentServiceItemForm/ShuttleSITServiceItemForm.jsx +++ b/src/components/PrimeUI/CreateShipmentServiceItemForm/ShuttleSITServiceItemForm.jsx @@ -9,7 +9,7 @@ import MaskedTextField from 'components/form/fields/MaskedTextField/MaskedTextFi import { Form } from 'components/form/Form'; import { ShipmentShape } from 'types/shipment'; import { DropdownInput } from 'components/form/fields'; -import { shuttleServiceItemCodeOptions, createServiceItemModelTypes } from 'constants/prime'; +import { domesticShuttleServiceItemCodeOptions, createServiceItemModelTypes } from 'constants/prime'; const shuttleSITValidationSchema = Yup.object().shape({ reServiceCode: Yup.string().required('Required'), @@ -20,7 +20,7 @@ const ShuttleSITServiceItemForm = ({ shipment, submission }) => { const initialValues = { moveTaskOrderID: shipment.moveTaskOrderID, mtoShipmentID: shipment.id, - modelType: createServiceItemModelTypes.MTOServiceItemShuttle, + modelType: createServiceItemModelTypes.MTOServiceItemDomesticShuttle, reason: '', estimatedWeight: null, actualWeight: null, @@ -44,7 +44,7 @@ const ShuttleSITServiceItemForm = ({ shipment, submission }) => { name="reServiceCode" id="reServiceCode" required - options={shuttleServiceItemCodeOptions} + options={domesticShuttleServiceItemCodeOptions} /> { const [isPageReload, setIsPageReload] = useState(true); useEffect(() => { @@ -205,7 +206,6 @@ const TableQueue = ({ if (isLoading || (title === 'Move history' && data.length <= 0 && !isError)) return ; if (isError) return ; - const isDateFilterValue = (value) => { return !Number.isNaN(Date.parse(value)); }; @@ -321,14 +321,18 @@ const TableQueue = ({
{isBulkAssignModalVisible && ( - + )}

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

{isSupervisor && isBulkAssignmentFFEnabled && ( - )} diff --git a/src/components/Table/TableQueue.module.scss b/src/components/Table/TableQueue.module.scss index 0da067b63ac..47a1912d639 100644 --- a/src/components/Table/TableQueue.module.scss +++ b/src/components/Table/TableQueue.module.scss @@ -38,6 +38,11 @@ cursor: pointer; } + .bulkModal { + padding: 10.787px; + cursor: pointer; + } + .tableContainer { flex: auto; @include u-margin-top(1); diff --git a/src/constants/MoveHistory/EventTemplates/UpdateMTOServiceItem/updateServiceItemPricingAndWeights.test.jsx b/src/constants/MoveHistory/EventTemplates/UpdateMTOServiceItem/updateServiceItemPricingAndWeights.test.jsx index eae1e8fe630..cfc63966876 100644 --- a/src/constants/MoveHistory/EventTemplates/UpdateMTOServiceItem/updateServiceItemPricingAndWeights.test.jsx +++ b/src/constants/MoveHistory/EventTemplates/UpdateMTOServiceItem/updateServiceItemPricingAndWeights.test.jsx @@ -8,7 +8,7 @@ import t from 'constants/MoveHistory/Database/Tables'; describe('when given an UpdateMTOServiceItem history record with pricing/weight changes', () => { const context = [ { - name: 'International Shipping & Linehaul', + name: 'International shipping & linehaul', shipment_type: 'HHG', shipment_locator: 'RQ38D4-01', shipment_id_abbr: 'a1b2c', @@ -31,7 +31,7 @@ describe('when given an UpdateMTOServiceItem history record with pricing/weight render(template.getDetails(historyRecord)); expect(screen.getByText('Service item')).toBeInTheDocument(); - expect(screen.getByText(/International Shipping & Linehaul/)).toBeInTheDocument(); + expect(screen.getByText(/International shipping & linehaul/)).toBeInTheDocument(); expect(screen.getByText('Estimated Price')).toBeInTheDocument(); expect(screen.getByText(/\$1,500\.00/)).toBeInTheDocument(); }); @@ -52,7 +52,7 @@ describe('when given an UpdateMTOServiceItem history record with pricing/weight render(template.getDetails(historyRecord)); expect(screen.getByText('Service item')).toBeInTheDocument(); - expect(screen.getByText(/International Shipping & Linehaul/)).toBeInTheDocument(); + expect(screen.getByText(/International shipping & linehaul/)).toBeInTheDocument(); expect(screen.getByText('Estimated weight')).toBeInTheDocument(); expect(screen.getByText(/1,000 lbs/)).toBeInTheDocument(); }); diff --git a/src/constants/prime.js b/src/constants/prime.js index 267156031b9..358ea7508de 100644 --- a/src/constants/prime.js +++ b/src/constants/prime.js @@ -4,13 +4,13 @@ import { serviceItemCodes } from 'content/serviceItems'; export const createServiceItemModelTypes = { MTOServiceItemOriginSIT: 'MTOServiceItemOriginSIT', MTOServiceItemDestSIT: 'MTOServiceItemDestSIT', - MTOServiceItemShuttle: 'MTOServiceItemShuttle', + MTOServiceItemDomesticShuttle: 'MTOServiceItemDomesticShuttle', MTOServiceItemDomesticCrating: 'MTOServiceItemDomesticCrating', MTOServiceItemInternationalCrating: 'MTOServiceItemInternationalCrating', MTOServiceItemInternationalShuttle: 'MTOServiceItemInternationalShuttle', }; -export const shuttleServiceItemCodeOptions = [ +export const domesticShuttleServiceItemCodeOptions = [ { value: serviceItemCodes.DOSHUT, key: SERVICE_ITEM_CODES.DOSHUT }, { value: serviceItemCodes.DDSHUT, key: SERVICE_ITEM_CODES.DDSHUT }, ]; diff --git a/src/constants/routes.js b/src/constants/routes.js index 1c4121aef59..a018cef31ea 100644 --- a/src/constants/routes.js +++ b/src/constants/routes.js @@ -4,8 +4,8 @@ export const generalRoutes = { REQUEST_ACCOUNT: '/request-account', PRIVACY_SECURITY_POLICY_PATH: '/privacy-and-security-policy', ACCESSIBILITY_PATH: '/accessibility', - QUEUE_SEARCH_PATH: 'Search', - BASE_QUEUE_SEARCH_PATH: '/Search', + QUEUE_SEARCH_PATH: 'search', + BASE_QUEUE_SEARCH_PATH: '/search', }; export const customerRoutes = { @@ -120,6 +120,8 @@ export const tooRoutes = { BASE_SHIPMENT_EDIT_PATH: `${BASE_MOVES_PATH}/shipments/:shipmentId`, MOVE_QUEUE: `move-queue`, BASE_MOVE_QUEUE: `/move-queue`, + BASE_DESTINATION_REQUESTS_QUEUE: `/destination-requests`, + DESTINATION_REQUESTS_QUEUE: `destination-requests`, SHIPMENT_EDIT_PATH: 'shipments/:shipmentId', BASE_MOVE_VIEW_PATH: `${BASE_MOVES_PATH}/details`, MOVE_VIEW_PATH: 'details', diff --git a/src/constants/serviceItems.js b/src/constants/serviceItems.js index 213afb45f38..a5b7e850b3c 100644 --- a/src/constants/serviceItems.js +++ b/src/constants/serviceItems.js @@ -17,6 +17,7 @@ const SERVICE_ITEM_PARAM_KEYS = { DistanceZipSITDest: 'DistanceZipSITDest', DistanceZipSITOrigin: 'DistanceZipSITOrigin', EIAFuelPrice: 'EIAFuelPrice', + ExternalCrate: 'ExternalCrate', FSCPriceDifferenceInCents: 'FSCPriceDifferenceInCents', EscalationCompounded: 'EscalationCompounded', FSCWeightBasedDistanceMultiplier: 'FSCWeightBasedDistanceMultiplier', @@ -54,13 +55,17 @@ const SERVICE_ITEM_PARAM_KEYS = { StandaloneCrate: 'StandaloneCrate', StandaloneCrateCap: 'StandaloneCrateCap', UncappedRequestTotal: 'UncappedRequestTotal', + MarketOrigin: 'MarketOrigin', + MarketDest: 'MarketDest', }; const SERVICE_ITEM_CALCULATION_LABELS = { [SERVICE_ITEM_PARAM_KEYS.ActualPickupDate]: 'Pickup date', [SERVICE_ITEM_PARAM_KEYS.ContractYearName]: 'Base year', + [SERVICE_ITEM_PARAM_KEYS.CubicFeetCrating]: 'Actual size', [SERVICE_ITEM_PARAM_KEYS.DestinationPrice]: 'Destination price', [SERVICE_ITEM_PARAM_KEYS.EIAFuelPrice]: 'EIA diesel', + [SERVICE_ITEM_PARAM_KEYS.ExternalCrate]: 'External crate', [SERVICE_ITEM_PARAM_KEYS.FSCPriceDifferenceInCents]: 'Baseline rate difference', [SERVICE_ITEM_PARAM_KEYS.FSCWeightBasedDistanceMultiplier]: 'Weight-based distance multiplier', // Domestic non-peak or Domestic peak @@ -102,7 +107,9 @@ const SERVICE_ITEM_CALCULATION_LABELS = { Domestic: 'Domestic', FuelSurchargePrice: 'Mileage factor', InternationalShippingAndLinehaul: 'ISLH price', + Market: 'Market', Mileage: 'Mileage', + MinSizeCrateApplied: 'Minimum crating size applied', MileageIntoSIT: 'Mileage into SIT', MileageOutOfSIT: 'Mileage out of SIT', NTSPackingFactor: 'NTS packing factor', @@ -121,8 +128,8 @@ const SERVICE_ITEM_CALCULATION_LABELS = { UncratingDate: 'Uncrating date', UncratingPrice: 'Uncrating price (per cu ft)', SITFuelSurchargePrice: 'SIT mileage factor', - StandaloneCrate: 'Standalone Crate Cap', - UncappedRequestTotal: 'Uncapped Request Total', + StandaloneCrate: 'Standalone crate cap', + UncappedRequestTotal: 'Uncapped request total', Total: 'Total', }; @@ -185,6 +192,8 @@ const SERVICE_ITEMS_ALLOWED_UPDATE = [ SERVICE_ITEM_CODES.DDFSIT, SERVICE_ITEM_CODES.DOSFSC, SERVICE_ITEM_CODES.DDSFSC, + SERVICE_ITEM_CODES.IDSHUT, + SERVICE_ITEM_CODES.IOSHUT, SERVICE_ITEM_CODES.PODFSC, SERVICE_ITEM_CODES.POEFSC, ]; @@ -219,12 +228,14 @@ const allowedServiceItemCalculations = [ SERVICE_ITEM_CODES.DOP, SERVICE_ITEM_CODES.DOPSIT, SERVICE_ITEM_CODES.DOSHUT, + SERVICE_ITEM_CODES.IOSHUT, SERVICE_ITEM_CODES.DPK, SERVICE_ITEM_CODES.DNPK, SERVICE_ITEM_CODES.DSH, SERVICE_ITEM_CODES.DUPK, SERVICE_ITEM_CODES.FSC, SERVICE_ITEM_CODES.DDSHUT, + SERVICE_ITEM_CODES.IDSHUT, SERVICE_ITEM_CODES.DCRT, SERVICE_ITEM_CODES.DUCRT, SERVICE_ITEM_CODES.DOSFSC, @@ -234,8 +245,12 @@ const allowedServiceItemCalculations = [ SERVICE_ITEM_CODES.ISLH, SERVICE_ITEM_CODES.POEFSC, SERVICE_ITEM_CODES.PODFSC, + SERVICE_ITEM_CODES.ICRT, + SERVICE_ITEM_CODES.IUCRT, ]; +const EXTERNAL_CRATE_MIN_CUBIC_FT = '4.00'; + export default SERVICE_ITEM_STATUSES; export { @@ -247,4 +262,5 @@ export { SERVICE_ITEM_STATUSES, SERVICE_ITEMS_ALLOWED_WEIGHT_BILLED_PARAM, SERVICE_ITEMS_ALLOWED_UPDATE, + EXTERNAL_CRATE_MIN_CUBIC_FT, }; diff --git a/src/hooks/queries.js b/src/hooks/queries.js index 17ccfe30f5a..1129a3e7901 100644 --- a/src/hooks/queries.js +++ b/src/hooks/queries.js @@ -34,6 +34,7 @@ import { getPPMActualWeight, searchCustomers, getGBLOCs, + getDestinationRequestsQueue, getBulkAssignmentData, } from 'services/ghcApi'; import { getLoggedInUserQueries } from 'services/internalApi'; @@ -587,6 +588,28 @@ export const useMovesQueueQueries = ({ }; }; +export const useDestinationRequestsQueueQueries = ({ + sort, + order, + filters = [], + currentPage = PAGINATION_PAGE_DEFAULT, + currentPageSize = PAGINATION_PAGE_SIZE_DEFAULT, + viewAsGBLOC, +}) => { + const { data = {}, ...movesQueueQuery } = useQuery( + [MOVES_QUEUE, { sort, order, filters, currentPage, currentPageSize, viewAsGBLOC }], + ({ queryKey }) => getDestinationRequestsQueue(...queryKey), + ); + const { isLoading, isError, isSuccess } = movesQueueQuery; + const { queueMoves, ...dataProps } = data; + return { + queueResult: { data: queueMoves, ...dataProps }, + isLoading, + isError, + isSuccess, + }; +}; + export const useServicesCounselingQueuePPMQueries = ({ sort, order, diff --git a/src/pages/Office/MoveAllowances/MoveAllowances.jsx b/src/pages/Office/MoveAllowances/MoveAllowances.jsx index 5e7057c752f..37352825844 100644 --- a/src/pages/Office/MoveAllowances/MoveAllowances.jsx +++ b/src/pages/Office/MoveAllowances/MoveAllowances.jsx @@ -44,10 +44,17 @@ const validationSchema = Yup.object({ .transform((value) => (Number.isNaN(value) ? 0 : value)) .notRequired(), weightRestriction: Yup.number() - .min(1, 'Weight restriction must be greater than 0') - .max(18000, 'Weight restriction cannot exceed 18,000 lbs') .transform((value) => (Number.isNaN(value) ? 0 : value)) - .notRequired(), + .when('adminRestrictedWeightLocation', { + is: true, + then: (schema) => + schema + .min(1, 'Weight restriction must be greater than 0') + .max(18000, 'Weight restriction cannot exceed 18,000 lbs') + .required('Weight restriction is required when Admin Restricted Weight Location is enabled'), + otherwise: (schema) => schema.notRequired().nullable(), + }), + adminRestrictedWeightLocation: Yup.boolean().notRequired(), }); const MoveAllowances = () => { @@ -103,11 +110,13 @@ const MoveAllowances = () => { organizationalClothingAndIndividualEquipment, storageInTransit, gunSafe, + adminRestrictedWeightLocation, weightRestriction, accompaniedTour, dependentsTwelveAndOver, dependentsUnderTwelve, } = values; + const body = { issueDate: order.date_issued, newDutyLocationId: order.destinationDutyLocation.id, @@ -124,11 +133,12 @@ const MoveAllowances = () => { organizationalClothingAndIndividualEquipment, storageInTransit: Number(storageInTransit), gunSafe, - weightRestriction: Number(weightRestriction), + weightRestriction: adminRestrictedWeightLocation && weightRestriction ? Number(weightRestriction) : null, accompaniedTour, dependentsTwelveAndOver: Number(dependentsTwelveAndOver), dependentsUnderTwelve: Number(dependentsUnderTwelve), }; + mutateOrders({ orderID: orderId, ifMatchETag: order.eTag, body }); }; @@ -156,7 +166,8 @@ const MoveAllowances = () => { requiredMedicalEquipmentWeight: `${requiredMedicalEquipmentWeight}`, organizationalClothingAndIndividualEquipment, gunSafe, - weightRestriction: `${weightRestriction}`, + adminRestrictedWeightLocation: weightRestriction > 0, + weightRestriction: weightRestriction ? `${weightRestriction}` : '0', storageInTransit: `${storageInTransit}`, accompaniedTour, dependentsUnderTwelve: `${dependentsUnderTwelve}`, @@ -206,7 +217,7 @@ const MoveAllowances = () => {
-
-