diff --git a/.circleci/config.yml b/.circleci/config.yml index 29253d95fed..17376e24ded 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -746,6 +746,7 @@ commands: export OKTA_TENANT_ORG_URL=test-milmove.okta.mil export OKTA_API_KEY=notrealapikey export OKTA_OFFICE_GROUP_ID=notrealgroupId + export OKTA_CUSTOMER_GROUP_ID=notrealcustomergroupId make server_test @@ -777,6 +778,7 @@ commands: OKTA_OFFICE_CLIENT_ID: 9f9f9s8s90gig9 OKTA_API_KEY: notrealapikey8675309 OKTA_OFFICE_GROUP_ID: notrealgroupId + OKTA_CUSTOMER_GROUP_ID: notrealcustomergroupId # run playwright tests without using setup_remote_docker # the remote docker resources are not configurable and thus are # SLOOOOOOW @@ -845,6 +847,7 @@ commands: export OKTA_TENANT_ORG_URL=test-milmove.okta.mil export OKTA_API_KEY=notrealapikey export OKTA_OFFICE_GROUP_ID=notrealgroupId + export OKTA_CUSTOMER_GROUP_ID=notrealcustomergroupId export SERVE_API_PRIME=false export SERVE_API_SUPPORT=true export SERVE_PRIME_SIMULATOR=true @@ -954,6 +957,7 @@ commands: export OKTA_TENANT_ORG_URL=test-milmove.okta.mil export OKTA_API_KEY=notrealapikey export OKTA_OFFICE_GROUP_ID=notrealgroupId + export OKTA_CUSTOMER_GROUP_ID=notrealcustomergroupId export SERVE_API_SUPPORT=true export SERVE_PRIME_SIMULATOR=true export DEVLOCAL_CA=$PWD/config/tls/devlocal-ca.pem @@ -1041,6 +1045,7 @@ commands: export OKTA_TENANT_ORG_URL=test-milmove.okta.mil export OKTA_API_KEY=notrealapikey export OKTA_OFFICE_GROUP_ID=notrealgroupId + export OKTA_CUSTOMER_GROUP_ID=notrealcustomergroupId export SERVE_API_SUPPORT=true export MUTUAL_TLS_ENABLED=true export SERVE_PRIME_SIMULATOR=true diff --git a/.envrc b/.envrc index 6de70ae94d9..c0775edaaff 100644 --- a/.envrc +++ b/.envrc @@ -137,6 +137,7 @@ require OKTA_API_KEY "See 'DISABLE_AWS_VAULT_WRAPPER=1 AWS_REGION=us-gov-west-1 require OKTA_CUSTOMER_SECRET_KEY "See 'DISABLE_AWS_VAULT_WRAPPER=1 AWS_REGION=us-gov-west-1 aws-vault exec transcom-gov-dev -- chamber read app-devlocal okta_customer_secret_key'" require OKTA_CUSTOMER_CLIENT_ID "See 'DISABLE_AWS_VAULT_WRAPPER=1 AWS_REGION=us-gov-west-1 aws-vault exec transcom-gov-dev -- chamber read app-devlocal okta_customer_client_id'" require OKTA_CUSTOMER_CALLBACK_URL "See 'DISABLE_AWS_VAULT_WRAPPER=1 AWS_REGION=us-gov-west-1 aws-vault exec transcom-gov-dev -- chamber read app-devlocal okta_customer_callback_url'" +require OKTA_CUSTOMER_GROUP_ID "See 'DISABLE_AWS_VAULT_WRAPPER=1 AWS_REGION=us-gov-west-1 aws-vault exec transcom-gov-dev -- chamber read app-devlocal okta_customer_group_id'" # Office require OKTA_OFFICE_SECRET_KEY "See 'DISABLE_AWS_VAULT_WRAPPER=1 AWS_REGION=us-gov-west-1 aws-vault exec transcom-gov-dev -- chamber read app-devlocal okta_office_secret_key'" diff --git a/migrations/app/migrations_manifest.txt b/migrations/app/migrations_manifest.txt index 0020ff6018a..6bb4ebedb38 100644 --- a/migrations/app/migrations_manifest.txt +++ b/migrations/app/migrations_manifest.txt @@ -913,3 +913,4 @@ 20240227180121_add_status_edipi_other_unique_id_and_rej_reason_to_office_users_table.up.sql 20240229203552_add_to_ppm_advance_status_datatype.up.sql 20240308181906_add_has_secondary_pickup_address_and_has_secondary_delivery_address_to_ppm_shipments.up.sql +20240319214733_remove_sit_authorized_end_date.up.sql diff --git a/migrations/app/schema/20240319214733_remove_sit_authorized_end_date.up.sql b/migrations/app/schema/20240319214733_remove_sit_authorized_end_date.up.sql new file mode 100644 index 00000000000..932b0f425e7 --- /dev/null +++ b/migrations/app/schema/20240319214733_remove_sit_authorized_end_date.up.sql @@ -0,0 +1 @@ +ALTER TABLE mto_service_items DROP COLUMN IF EXISTS sit_authorized_end_date; \ No newline at end of file diff --git a/pkg/apperror/errors.go b/pkg/apperror/errors.go index b8b4e937c5e..9747f0b8380 100644 --- a/pkg/apperror/errors.go +++ b/pkg/apperror/errors.go @@ -75,6 +75,35 @@ func (e *NotFoundError) Unwrap() error { return e.err } +// UpdateError is returned when a service cannot be updated +type UpdateError struct { + id uuid.UUID + message string + err error +} + +// NewUpdateError returns an error for when a service cannot be updated +func NewUpdateError(id uuid.UUID, message string) UpdateError { + return UpdateError{ + id: id, + message: message, + } +} + +func (e UpdateError) Error() string { + return fmt.Sprintf("Update Error %s", e.message) +} + +// Wrap lets the caller add an error to be wrapped in the NewUpdateError +func (e *UpdateError) Wrap(err error) { + e.err = err +} + +// Unwrap returns the wrapped error, could be nil +func (e *UpdateError) Unwrap() error { + return e.err +} + type PPMNotReadyForCloseoutError struct { id uuid.UUID message string diff --git a/pkg/assets/paperwork/formtemplates/SSWPDFTemplate.pdf b/pkg/assets/paperwork/formtemplates/SSWPDFTemplate.pdf index 2b28ea323eb..134d51b0be1 100644 Binary files a/pkg/assets/paperwork/formtemplates/SSWPDFTemplate.pdf and b/pkg/assets/paperwork/formtemplates/SSWPDFTemplate.pdf differ diff --git a/pkg/cli/auth.go b/pkg/cli/auth.go index 82a378d30e7..0bd5566fa5d 100644 --- a/pkg/cli/auth.go +++ b/pkg/cli/auth.go @@ -86,8 +86,9 @@ const ( // RA Validator: leodis.f.scott.civ@mail.mil // RA Modified Severity: CAT III // #nosec G101 - OktaAdminSecretKeyFlag string = "okta-admin-secret-key" - OktaOfficeGroupIDFlag string = "okta-office-group-id" + OktaAdminSecretKeyFlag string = "okta-admin-secret-key" + OktaOfficeGroupIDFlag string = "okta-office-group-id" + OktaCustomerGroupIDFlag string = "okta-customer-group-id" ) // InitAuthFlags initializes Auth command line flags @@ -108,6 +109,7 @@ func InitAuthFlags(flag *pflag.FlagSet) { flag.String(OktaAdminCallbackURL, "", "The callback URL from logging in to the admin Okta app back to MilMove.") flag.String(OktaAdminSecretKeyFlag, "", "The secret key for the miltiary Admin app, aka 'my'.") flag.String(OktaOfficeGroupIDFlag, "", "The office group id for the Office app, aka 'office'.") + flag.String(OktaCustomerGroupIDFlag, "", "The customer group id for the Customer app.") } // CheckAuth validates Auth command line flags @@ -136,6 +138,7 @@ func CheckAuth(v *viper.Viper) error { groupIDVars := []string{ OktaOfficeGroupIDFlag, + OktaCustomerGroupIDFlag, } for _, c := range clientIDVars { diff --git a/pkg/gen/ghcapi/configure_mymove.go b/pkg/gen/ghcapi/configure_mymove.go index decd590b61f..26e56164ad5 100644 --- a/pkg/gen/ghcapi/configure_mymove.go +++ b/pkg/gen/ghcapi/configure_mymove.go @@ -102,6 +102,11 @@ func configureAPI(api *ghcoperations.MymoveAPI) http.Handler { return middleware.NotImplemented("operation customer_support_remarks.CreateCustomerSupportRemarkForMove has not yet been implemented") }) } + if api.CustomerCreateCustomerWithOktaOptionHandler == nil { + api.CustomerCreateCustomerWithOktaOptionHandler = customer.CreateCustomerWithOktaOptionHandlerFunc(func(params customer.CreateCustomerWithOktaOptionParams) middleware.Responder { + return middleware.NotImplemented("operation customer.CreateCustomerWithOktaOption has not yet been implemented") + }) + } if api.EvaluationReportsCreateEvaluationReportHandler == nil { api.EvaluationReportsCreateEvaluationReportHandler = evaluation_reports.CreateEvaluationReportHandlerFunc(func(params evaluation_reports.CreateEvaluationReportParams) middleware.Responder { return middleware.NotImplemented("operation evaluation_reports.CreateEvaluationReport has not yet been implemented") diff --git a/pkg/gen/ghcapi/embedded_spec.go b/pkg/gen/ghcapi/embedded_spec.go index 092b400e878..41563f50b12 100644 --- a/pkg/gen/ghcapi/embedded_spec.go +++ b/pkg/gen/ghcapi/embedded_spec.go @@ -166,6 +166,61 @@ func init() { } ] }, + "/customer": { + "post": { + "description": "Creates a customer with option to create an Okta profile account", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "customer" + ], + "summary": "Creates a customer with Okta option", + "operationId": "createCustomerWithOktaOption", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/CreateCustomerPayload" + } + } + ], + "responses": { + "200": { + "description": "successfully created the customer", + "schema": { + "$ref": "#/definitions/CreatedCustomer" + } + }, + "400": { + "$ref": "#/responses/InvalidRequest" + }, + "401": { + "$ref": "#/responses/PermissionDenied" + }, + "403": { + "$ref": "#/responses/PermissionDenied" + }, + "404": { + "$ref": "#/responses/NotFound" + }, + "412": { + "$ref": "#/responses/PreconditionFailed" + }, + "422": { + "$ref": "#/responses/UnprocessableEntity" + }, + "500": { + "$ref": "#/responses/ServerError" + } + } + } + }, "/customer-support-remarks/{customerSupportRemarkID}": { "delete": { "description": "Soft deletes a customer support remark by ID", @@ -1577,11 +1632,6 @@ func init() { "format": "date-time", "x-nullable": true }, - "deliveryDate": { - "type": "string", - "format": "date-time", - "x-nullable": true - }, "destinationPostalCode": { "type": "string", "x-nullable": true @@ -1624,11 +1674,6 @@ func init() { "format": "date-time", "x-nullable": true }, - "pickupDate": { - "type": "string", - "format": "date-time", - "x-nullable": true - }, "shipmentsCount": { "type": "integer", "x-nullable": true @@ -1654,16 +1699,12 @@ func init() { "items": { "type": "string", "enum": [ - "DRAFT", "DRAFT", "SUBMITTED", "APPROVALS REQUESTED", "APPROVED", "NEEDS SERVICE COUNSELING", "SERVICE COUNSELING COMPLETED" - "APPROVED", - "NEEDS SERVICE COUNSELING", - "SERVICE COUNSELING COMPLETED" ] } } @@ -2762,58 +2803,6 @@ func init() { } ] }, - "/ppm-shipments/{ppmShipmentId}/aoa-packet": { - "get": { - "description": "### Functionality\nThis endpoint downloads all uploaded move order documentation combined with the Shipment Summary Worksheet into a single PDF.\n### Errors\n* The PPMShipment must have requested an AOA.\n* The PPMShipment AOA Request must have been approved.\n", - "produces": [ - "application/pdf" - ], - "tags": [ - "ppm" - ], - "summary": "Downloads AOA Packet form PPMShipment as a PDF", - "operationId": "showAOAPacket", - "responses": { - "200": { - "description": "AOA PDF", - "schema": { - "type": "file", - "format": "binary" - }, - "headers": { - "Content-Disposition": { - "type": "string", - "description": "File name to download" - } - } - }, - "400": { - "$ref": "#/responses/InvalidRequest" - }, - "403": { - "$ref": "#/responses/PermissionDenied" - }, - "404": { - "$ref": "#/responses/NotFound" - }, - "422": { - "$ref": "#/responses/UnprocessableEntity" - }, - "500": { - "$ref": "#/responses/ServerError" - } - } - }, - "parameters": [ - { - "type": "string", - "description": "the id for the ppmshipment with aoa to be downloaded", - "name": "ppmShipmentId", - "in": "path", - "required": true - } - ] - }, "/ppm-shipments/{ppmShipmentId}/closeout": { "get": { "description": "Retrieves the closeout calculations for the specified PPM shipment.\n", @@ -3356,12 +3345,6 @@ func init() { "name": "closeoutLocation", "in": "query" }, - { - "type": "string", - "description": "order type", - "name": "orderType", - "in": "query" - }, { "type": "string", "description": "order type", @@ -3493,12 +3476,6 @@ func init() { "name": "status", "in": "query" }, - { - "type": "string", - "description": "order type", - "name": "orderType", - "in": "query" - }, { "type": "string", "description": "order type", @@ -3631,12 +3608,6 @@ func init() { "name": "status", "in": "query" }, - { - "type": "string", - "description": "order type", - "name": "orderType", - "in": "query" - }, { "type": "string", "description": "order type", @@ -5115,7 +5086,7 @@ func init() { "email": { "type": "string", "format": "x-email", - "pattern": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$" + "example": "backupContact@mail.com" }, "name": { "type": "string" @@ -5323,6 +5294,81 @@ func init() { } } }, + "CreateCustomerPayload": { + "type": "object", + "properties": { + "affiliation": { + "$ref": "#/definitions/Affiliation" + }, + "backupContact": { + "$ref": "#/definitions/BackupContact" + }, + "backupMailingAddress": { + "allOf": [ + { + "$ref": "#/definitions/Address" + } + ] + }, + "createOktaAccount": { + "type": "boolean" + }, + "edipi": { + "type": "string", + "x-nullable": true, + "example": "John" + }, + "emailIsPreferred": { + "type": "boolean" + }, + "firstName": { + "type": "string", + "example": "John" + }, + "lastName": { + "type": "string", + "example": "Doe" + }, + "middleName": { + "type": "string", + "x-nullable": true, + "example": "David" + }, + "personalEmail": { + "type": "string", + "format": "x-email", + "pattern": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", + "example": "personalEmail@email.com" + }, + "phoneIsPreferred": { + "type": "boolean" + }, + "residentialAddress": { + "allOf": [ + { + "$ref": "#/definitions/Address" + } + ] + }, + "secondaryTelephone": { + "type": "string", + "format": "telephone", + "pattern": "^[2-9]\\d{2}-\\d{3}-\\d{4}$", + "x-nullable": true + }, + "suffix": { + "type": "string", + "x-nullable": true, + "example": "Jr." + }, + "telephone": { + "type": "string", + "format": "telephone", + "pattern": "^[2-9]\\d{2}-\\d{3}-\\d{4}$", + "x-nullable": true + } + } + }, "CreateCustomerSupportRemark": { "description": "A text remark written by an customer support user that is associated with a specific move.", "type": "object", @@ -5557,44 +5603,27 @@ func init() { } } }, - "Customer": { + "CreatedCustomer": { "type": "object", "properties": { - "agency": { + "affiliation": { "type": "string", - "title": "Agency customer is affilated with" + "title": "Branch of service customer is affilated with" }, "backupAddress": { "$ref": "#/definitions/Address" }, - "backupAddress": { - "$ref": "#/definitions/Address" - }, - "backup_contact": { + "backupContact": { "$ref": "#/definitions/BackupContact" }, - "current_address": { - "$ref": "#/definitions/Address" - }, - "dodID": { - "type": "string" - }, - "eTag": { - "type": "string" - }, - "email": { + "edipi": { "type": "string", - "format": "x-email", - "pattern": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", "x-nullable": true }, "emailIsPreferred": { "type": "boolean" }, - "emailIsPreferred": { - "type": "boolean" - }, - "first_name": { + "firstName": { "type": "string", "example": "John" }, @@ -5603,33 +5632,32 @@ func init() { "format": "uuid", "example": "c56a4180-65aa-42ec-a945-5fd21dec0538" }, - "last_name": { + "lastName": { "type": "string", "example": "Doe" }, - "middle_name": { + "middleName": { "type": "string", "x-nullable": true, "example": "David" }, - "phone": { - "type": "string", - "format": "telephone", - "pattern": "^[2-9]\\d{2}-\\d{3}-\\d{4}$", - "x-nullable": true + "oktaEmail": { + "type": "string" }, - "phoneIsPreferred": { - "type": "boolean" + "oktaID": { + "type": "string" }, - "secondaryTelephone": { + "personalEmail": { "type": "string", - "format": "telephone", - "pattern": "^[2-9]\\d{2}-\\d{3}-\\d{4}$", - "x-nullable": true + "format": "x-email", + "pattern": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$" }, "phoneIsPreferred": { "type": "boolean" }, + "residentialAddress": { + "$ref": "#/definitions/Address" + }, "secondaryTelephone": { "type": "string", "format": "telephone", @@ -5641,6 +5669,12 @@ func init() { "x-nullable": true, "example": "Jr." }, + "telephone": { + "type": "string", + "format": "telephone", + "pattern": "^[2-9]\\d{2}-\\d{3}-\\d{4}$", + "x-nullable": true + }, "userID": { "type": "string", "format": "uuid", @@ -5648,16 +5682,92 @@ func init() { } } }, - "CustomerContactType": { - "description": "Describes a customer contact type for a MTOServiceItem of type domestic destination SIT.", - "type": "string", - "enum": [ - "FIRST", - "SECOND" - ] - }, - "CustomerSupportRemark": { - "description": "A text remark written by an office user that is associated with a specific move.", + "Customer": { + "type": "object", + "properties": { + "agency": { + "type": "string", + "title": "Agency customer is affilated with" + }, + "backupAddress": { + "$ref": "#/definitions/Address" + }, + "backup_contact": { + "$ref": "#/definitions/BackupContact" + }, + "current_address": { + "$ref": "#/definitions/Address" + }, + "dodID": { + "type": "string" + }, + "eTag": { + "type": "string" + }, + "email": { + "type": "string", + "format": "x-email", + "pattern": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", + "x-nullable": true + }, + "emailIsPreferred": { + "type": "boolean" + }, + "first_name": { + "type": "string", + "example": "John" + }, + "id": { + "type": "string", + "format": "uuid", + "example": "c56a4180-65aa-42ec-a945-5fd21dec0538" + }, + "last_name": { + "type": "string", + "example": "Doe" + }, + "middle_name": { + "type": "string", + "x-nullable": true, + "example": "David" + }, + "phone": { + "type": "string", + "format": "telephone", + "pattern": "^[2-9]\\d{2}-\\d{3}-\\d{4}$", + "x-nullable": true + }, + "phoneIsPreferred": { + "type": "boolean" + }, + "secondaryTelephone": { + "type": "string", + "format": "telephone", + "pattern": "^[2-9]\\d{2}-\\d{3}-\\d{4}$", + "x-nullable": true + }, + "suffix": { + "type": "string", + "x-nullable": true, + "example": "Jr." + }, + "userID": { + "type": "string", + "format": "uuid", + "example": "c56a4180-65aa-42ec-a945-5fd21dec0538" + } + } + }, + "CustomerContactType": { + "description": "Describes a customer contact type for a MTOServiceItem of type domestic destination SIT.", + "type": "string", + "enum": [ + "FIRST", + "SECOND" + ] + }, + "CustomerSupportRemark": { + "description": "A text remark written by an office user that is associated with a specific move.", "type": "object", "required": [ "id", @@ -6267,9 +6377,6 @@ func init() { "orderType": { "type": "string" }, - "orderType": { - "type": "string" - }, "ppmType": { "type": "string", "enum": [ @@ -6511,6 +6618,10 @@ func init() { "format": "date", "x-nullable": true }, + "sitDeliveryMiles": { + "type": "integer", + "x-nullable": true + }, "sitDepartureDate": { "type": "string", "format": "date-time", @@ -6527,6 +6638,12 @@ func init() { "format": "date-time", "x-nullable": true }, + "sitOriginHHGActualAddress": { + "$ref": "#/definitions/Address" + }, + "sitOriginHHGOriginalAddress": { + "$ref": "#/definitions/Address" + }, "sitRequestedDelivery": { "type": "string", "format": "date", @@ -6827,11 +6944,6 @@ func init() { "x-nullable": true, "example": 500 }, - "distance": { - "type": "integer", - "x-nullable": true, - "example": 500 - }, "diversion": { "type": "boolean", "example": true @@ -7707,9 +7819,6 @@ func init() { "originDutyLocationGBLOC": { "$ref": "#/definitions/GBLOC" }, - "originDutyLocationGBLOC": { - "$ref": "#/definitions/GBLOC" - }, "packingAndShippingInstructions": { "type": "string" }, @@ -7763,7 +7872,6 @@ func init() { "BLUEBARK" ], "x-display-value": { - "BLUEBARK": "BLUEBARK", "BLUEBARK": "BLUEBARK", "LOCAL_MOVE": "Local Move", "PERMANENT_CHANGE_OF_STATION": "Permanent Change Of Station", @@ -7796,7 +7904,6 @@ func init() { "x-nullable": true }, "PPMAdvanceStatus": { - "description": "Indicates whether an advance status has been accepted, rejected, or edited, or a prime counseled PPM has been received or not received", "description": "Indicates whether an advance status has been accepted, rejected, or edited, or a prime counseled PPM has been received or not received", "type": "string", "title": "PPM Advance Status", @@ -7806,9 +7913,6 @@ func init() { "EDITED", "RECEIVED", "NOT_RECEIVED" - "EDITED", - "RECEIVED", - "NOT_RECEIVED" ], "x-nullable": true }, @@ -7836,7 +7940,6 @@ func init() { "type": "integer", "x-nullable": true, "x-omitempty": false, - "x-omitempty": false, "example": 2000 }, "aoa": { @@ -7937,7 +8040,6 @@ func init() { "x-nullable": true, "x-omitempty": false }, - "remainingIncentive": { "remainingIncentive": { "description": "The remaining reimbursement amount that is still owed to the customer.", "type": "integer", @@ -8065,9 +8167,6 @@ func init() { "destinationAddress": { "$ref": "#/definitions/Address" }, - "destinationAddress": { - "$ref": "#/definitions/Address" - }, "destinationPostalCode": { "description": "The postal code of the destination location where goods are being delivered to.", "type": "string", @@ -8136,16 +8235,6 @@ func init() { "x-nullable": true, "x-omitempty": false }, - "hasSecondaryDestinationAddress": { - "type": "boolean", - "x-nullable": true, - "x-omitempty": false - }, - "hasSecondaryPickupAddress": { - "type": "boolean", - "x-nullable": true, - "x-omitempty": false - }, "id": { "description": "Primary auto-generated unique identifier of the PPM shipment object", "type": "string", @@ -8163,9 +8252,6 @@ func init() { "pickupAddress": { "$ref": "#/definitions/Address" }, - "pickupAddress": { - "$ref": "#/definitions/Address" - }, "pickupPostalCode": { "description": "The postal code of the origin location where goods are being moved from.", "type": "string", @@ -8207,19 +8293,6 @@ func init() { } ] }, - "secondaryDestinationAddress": { - "allOf": [ - { - "$ref": "#/definitions/Address" - }, - { - "x-nullable": true - }, - { - "x-omitempty": false - } - ] - }, "secondaryDestinationPostalCode": { "description": "An optional secondary location near the destination where goods will be dropped off.", "type": "string", @@ -8243,19 +8316,6 @@ func init() { } ] }, - "secondaryPickupAddress": { - "allOf": [ - { - "$ref": "#/definitions/Address" - }, - { - "x-nullable": true - }, - { - "x-omitempty": false - } - ] - }, "secondaryPickupPostalCode": { "type": "string", "format": "An optional secondary pickup location near the origin where additional goods exist.", @@ -8796,10 +8856,6 @@ func init() { "type": "string", "x-nullable": true }, - "orderType": { - "type": "string", - "x-nullable": true - }, "originDutyLocation": { "$ref": "#/definitions/DutyLocation" }, @@ -8884,10 +8940,6 @@ func init() { "type": "string", "x-nullable": true }, - "orderType": { - "type": "string", - "x-nullable": true - }, "originDutyLocation": { "$ref": "#/definitions/DutyLocation" }, @@ -9212,11 +9264,6 @@ func init() { "format": "date", "x-nullable": true }, - "sitAuthorizedEndDate": { - "type": "string", - "format": "date", - "x-nullable": true - }, "sitCustomerContacted": { "type": "string", "format": "date", @@ -9266,9 +9313,6 @@ func init() { "destinationGBLOC": { "$ref": "#/definitions/GBLOC" }, - "destinationGBLOC": { - "$ref": "#/definitions/GBLOC" - }, "dodID": { "type": "string", "x-nullable": true, @@ -9294,9 +9338,6 @@ func init() { "orderType": { "type": "string" }, - "orderType": { - "type": "string" - }, "originDutyLocationPostalCode": { "type": "string", "format": "zip", @@ -9317,19 +9358,6 @@ func init() { "format": "date", "x-nullable": true }, - "originGBLOC": { - "$ref": "#/definitions/GBLOC" - }, - "requestedDeliveryDate": { - "type": "string", - "format": "date", - "x-nullable": true - }, - "requestedPickupDate": { - "type": "string", - "format": "date", - "x-nullable": true - }, "shipmentsCount": { "type": "integer" }, @@ -9880,13 +9908,6 @@ func init() { "UpdateCustomerPayload": { "type": "object", "properties": { - "backupAddress": { - "allOf": [ - { - "$ref": "#/definitions/Address" - } - ] - }, "backupAddress": { "allOf": [ { @@ -9913,9 +9934,6 @@ func init() { "emailIsPreferred": { "type": "boolean" }, - "emailIsPreferred": { - "type": "boolean" - }, "first_name": { "type": "string", "example": "John" @@ -9944,15 +9962,6 @@ func init() { "pattern": "^[2-9]\\d{2}-\\d{3}-\\d{4}$", "x-nullable": true }, - "phoneIsPreferred": { - "type": "boolean" - }, - "secondaryTelephone": { - "type": "string", - "format": "telephone", - "pattern": "^[2-9]\\d{2}-\\d{3}-\\d{4}$", - "x-nullable": true - }, "suffix": { "type": "string", "x-nullable": true, @@ -11007,20 +11016,36 @@ func init() { } ] }, - "/customer-support-remarks/{customerSupportRemarkID}": { - "delete": { - "description": "Soft deletes a customer support remark by ID", + "/customer": { + "post": { + "description": "Creates a customer with option to create an Okta profile account", + "consumes": [ + "application/json" + ], "produces": [ "application/json" ], "tags": [ - "customerSupportRemarks" + "customer" + ], + "summary": "Creates a customer with Okta option", + "operationId": "createCustomerWithOktaOption", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/CreateCustomerPayload" + } + } ], - "summary": "Soft deletes a customer support remark by ID", - "operationId": "deleteCustomerSupportRemark", "responses": { - "204": { - "description": "Successfully soft deleted the shipment" + "200": { + "description": "successfully created the customer", + "schema": { + "$ref": "#/definitions/CreatedCustomer" + } }, "400": { "description": "The request payload is invalid", @@ -11028,6 +11053,12 @@ func init() { "$ref": "#/definitions/Error" } }, + "401": { + "description": "The request was denied", + "schema": { + "$ref": "#/definitions/Error" + } + }, "403": { "description": "The request was denied", "schema": { @@ -11040,8 +11071,8 @@ func init() { "$ref": "#/definitions/Error" } }, - "409": { - "description": "Conflict error", + "412": { + "description": "Precondition failed", "schema": { "$ref": "#/definitions/Error" } @@ -11059,25 +11090,79 @@ func init() { } } } - }, - "patch": { - "description": "Updates a customer support remark for a move", - "consumes": [ - "application/json" - ], + } + }, + "/customer-support-remarks/{customerSupportRemarkID}": { + "delete": { + "description": "Soft deletes a customer support remark by ID", "produces": [ "application/json" ], "tags": [ "customerSupportRemarks" ], - "summary": "Updates a customer support remark for a move", - "operationId": "updateCustomerSupportRemarkForMove", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, + "summary": "Soft deletes a customer support remark by ID", + "operationId": "deleteCustomerSupportRemark", + "responses": { + "204": { + "description": "Successfully soft deleted the shipment" + }, + "400": { + "description": "The request payload is invalid", + "schema": { + "$ref": "#/definitions/Error" + } + }, + "403": { + "description": "The request was denied", + "schema": { + "$ref": "#/definitions/Error" + } + }, + "404": { + "description": "The requested resource wasn't found", + "schema": { + "$ref": "#/definitions/Error" + } + }, + "409": { + "description": "Conflict error", + "schema": { + "$ref": "#/definitions/Error" + } + }, + "422": { + "description": "The payload was unprocessable.", + "schema": { + "$ref": "#/definitions/ValidationError" + } + }, + "500": { + "description": "A server error occurred", + "schema": { + "$ref": "#/definitions/Error" + } + } + } + }, + "patch": { + "description": "Updates a customer support remark for a move", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "customerSupportRemarks" + ], + "summary": "Updates a customer support remark for a move", + "operationId": "updateCustomerSupportRemarkForMove", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, "schema": { "$ref": "#/definitions/UpdateCustomerSupportRemarkPayload" } @@ -12847,11 +12932,6 @@ func init() { "format": "date-time", "x-nullable": true }, - "deliveryDate": { - "type": "string", - "format": "date-time", - "x-nullable": true - }, "destinationPostalCode": { "type": "string", "x-nullable": true @@ -12894,11 +12974,6 @@ func init() { "format": "date-time", "x-nullable": true }, - "pickupDate": { - "type": "string", - "format": "date-time", - "x-nullable": true - }, "shipmentsCount": { "type": "integer", "x-nullable": true @@ -12924,16 +12999,12 @@ func init() { "items": { "type": "string", "enum": [ - "DRAFT", "DRAFT", "SUBMITTED", "APPROVALS REQUESTED", "APPROVED", "NEEDS SERVICE COUNSELING", "SERVICE COUNSELING COMPLETED" - "APPROVED", - "NEEDS SERVICE COUNSELING", - "SERVICE COUNSELING COMPLETED" ] } } @@ -14327,73 +14398,6 @@ func init() { } ] }, - "/ppm-shipments/{ppmShipmentId}/aoa-packet": { - "get": { - "description": "### Functionality\nThis endpoint downloads all uploaded move order documentation combined with the Shipment Summary Worksheet into a single PDF.\n### Errors\n* The PPMShipment must have requested an AOA.\n* The PPMShipment AOA Request must have been approved.\n", - "produces": [ - "application/pdf" - ], - "tags": [ - "ppm" - ], - "summary": "Downloads AOA Packet form PPMShipment as a PDF", - "operationId": "showAOAPacket", - "responses": { - "200": { - "description": "AOA PDF", - "schema": { - "type": "file", - "format": "binary" - }, - "headers": { - "Content-Disposition": { - "type": "string", - "description": "File name to download" - } - } - }, - "400": { - "description": "The request payload is invalid", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "403": { - "description": "The request was denied", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "The requested resource wasn't found", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "422": { - "description": "The payload was unprocessable.", - "schema": { - "$ref": "#/definitions/ValidationError" - } - }, - "500": { - "description": "A server error occurred", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - }, - "parameters": [ - { - "type": "string", - "description": "the id for the ppmshipment with aoa to be downloaded", - "name": "ppmShipmentId", - "in": "path", - "required": true - } - ] - }, "/ppm-shipments/{ppmShipmentId}/closeout": { "get": { "description": "Retrieves the closeout calculations for the specified PPM shipment.\n", @@ -15102,12 +15106,6 @@ func init() { "name": "closeoutLocation", "in": "query" }, - { - "type": "string", - "description": "order type", - "name": "orderType", - "in": "query" - }, { "type": "string", "description": "order type", @@ -15245,12 +15243,6 @@ func init() { "name": "status", "in": "query" }, - { - "type": "string", - "description": "order type", - "name": "orderType", - "in": "query" - }, { "type": "string", "description": "order type", @@ -15389,12 +15381,6 @@ func init() { "name": "status", "in": "query" }, - { - "type": "string", - "description": "order type", - "name": "orderType", - "in": "query" - }, { "type": "string", "description": "order type", @@ -17206,7 +17192,7 @@ func init() { "email": { "type": "string", "format": "x-email", - "pattern": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$" + "example": "backupContact@mail.com" }, "name": { "type": "string" @@ -17418,6 +17404,81 @@ func init() { } } }, + "CreateCustomerPayload": { + "type": "object", + "properties": { + "affiliation": { + "$ref": "#/definitions/Affiliation" + }, + "backupContact": { + "$ref": "#/definitions/BackupContact" + }, + "backupMailingAddress": { + "allOf": [ + { + "$ref": "#/definitions/Address" + } + ] + }, + "createOktaAccount": { + "type": "boolean" + }, + "edipi": { + "type": "string", + "x-nullable": true, + "example": "John" + }, + "emailIsPreferred": { + "type": "boolean" + }, + "firstName": { + "type": "string", + "example": "John" + }, + "lastName": { + "type": "string", + "example": "Doe" + }, + "middleName": { + "type": "string", + "x-nullable": true, + "example": "David" + }, + "personalEmail": { + "type": "string", + "format": "x-email", + "pattern": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", + "example": "personalEmail@email.com" + }, + "phoneIsPreferred": { + "type": "boolean" + }, + "residentialAddress": { + "allOf": [ + { + "$ref": "#/definitions/Address" + } + ] + }, + "secondaryTelephone": { + "type": "string", + "format": "telephone", + "pattern": "^[2-9]\\d{2}-\\d{3}-\\d{4}$", + "x-nullable": true + }, + "suffix": { + "type": "string", + "x-nullable": true, + "example": "Jr." + }, + "telephone": { + "type": "string", + "format": "telephone", + "pattern": "^[2-9]\\d{2}-\\d{3}-\\d{4}$", + "x-nullable": true + } + } + }, "CreateCustomerSupportRemark": { "description": "A text remark written by an customer support user that is associated with a specific move.", "type": "object", @@ -17652,16 +17713,92 @@ func init() { } } }, - "Customer": { + "CreatedCustomer": { "type": "object", "properties": { - "agency": { + "affiliation": { "type": "string", - "title": "Agency customer is affilated with" + "title": "Branch of service customer is affilated with" }, "backupAddress": { "$ref": "#/definitions/Address" }, + "backupContact": { + "$ref": "#/definitions/BackupContact" + }, + "edipi": { + "type": "string", + "x-nullable": true + }, + "emailIsPreferred": { + "type": "boolean" + }, + "firstName": { + "type": "string", + "example": "John" + }, + "id": { + "type": "string", + "format": "uuid", + "example": "c56a4180-65aa-42ec-a945-5fd21dec0538" + }, + "lastName": { + "type": "string", + "example": "Doe" + }, + "middleName": { + "type": "string", + "x-nullable": true, + "example": "David" + }, + "oktaEmail": { + "type": "string" + }, + "oktaID": { + "type": "string" + }, + "personalEmail": { + "type": "string", + "format": "x-email", + "pattern": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$" + }, + "phoneIsPreferred": { + "type": "boolean" + }, + "residentialAddress": { + "$ref": "#/definitions/Address" + }, + "secondaryTelephone": { + "type": "string", + "format": "telephone", + "pattern": "^[2-9]\\d{2}-\\d{3}-\\d{4}$", + "x-nullable": true + }, + "suffix": { + "type": "string", + "x-nullable": true, + "example": "Jr." + }, + "telephone": { + "type": "string", + "format": "telephone", + "pattern": "^[2-9]\\d{2}-\\d{3}-\\d{4}$", + "x-nullable": true + }, + "userID": { + "type": "string", + "format": "uuid", + "example": "c56a4180-65aa-42ec-a945-5fd21dec0538" + } + } + }, + "Customer": { + "type": "object", + "properties": { + "agency": { + "type": "string", + "title": "Agency customer is affilated with" + }, "backupAddress": { "$ref": "#/definitions/Address" }, @@ -17686,9 +17823,6 @@ func init() { "emailIsPreferred": { "type": "boolean" }, - "emailIsPreferred": { - "type": "boolean" - }, "first_name": { "type": "string", "example": "John" @@ -17722,15 +17856,6 @@ func init() { "pattern": "^[2-9]\\d{2}-\\d{3}-\\d{4}$", "x-nullable": true }, - "phoneIsPreferred": { - "type": "boolean" - }, - "secondaryTelephone": { - "type": "string", - "format": "telephone", - "pattern": "^[2-9]\\d{2}-\\d{3}-\\d{4}$", - "x-nullable": true - }, "suffix": { "type": "string", "x-nullable": true, @@ -18362,9 +18487,6 @@ func init() { "orderType": { "type": "string" }, - "orderType": { - "type": "string" - }, "ppmType": { "type": "string", "enum": [ @@ -18606,6 +18728,10 @@ func init() { "format": "date", "x-nullable": true }, + "sitDeliveryMiles": { + "type": "integer", + "x-nullable": true + }, "sitDepartureDate": { "type": "string", "format": "date-time", @@ -18622,6 +18748,12 @@ func init() { "format": "date-time", "x-nullable": true }, + "sitOriginHHGActualAddress": { + "$ref": "#/definitions/Address" + }, + "sitOriginHHGOriginalAddress": { + "$ref": "#/definitions/Address" + }, "sitRequestedDelivery": { "type": "string", "format": "date", @@ -18922,11 +19054,6 @@ func init() { "x-nullable": true, "example": 500 }, - "distance": { - "type": "integer", - "x-nullable": true, - "example": 500 - }, "diversion": { "type": "boolean", "example": true @@ -19802,9 +19929,6 @@ func init() { "originDutyLocationGBLOC": { "$ref": "#/definitions/GBLOC" }, - "originDutyLocationGBLOC": { - "$ref": "#/definitions/GBLOC" - }, "packingAndShippingInstructions": { "type": "string" }, @@ -19858,7 +19982,6 @@ func init() { "BLUEBARK" ], "x-display-value": { - "BLUEBARK": "BLUEBARK", "BLUEBARK": "BLUEBARK", "LOCAL_MOVE": "Local Move", "PERMANENT_CHANGE_OF_STATION": "Permanent Change Of Station", @@ -19891,7 +20014,6 @@ func init() { "x-nullable": true }, "PPMAdvanceStatus": { - "description": "Indicates whether an advance status has been accepted, rejected, or edited, or a prime counseled PPM has been received or not received", "description": "Indicates whether an advance status has been accepted, rejected, or edited, or a prime counseled PPM has been received or not received", "type": "string", "title": "PPM Advance Status", @@ -19901,9 +20023,6 @@ func init() { "EDITED", "RECEIVED", "NOT_RECEIVED" - "EDITED", - "RECEIVED", - "NOT_RECEIVED" ], "x-nullable": true }, @@ -19931,7 +20050,6 @@ func init() { "type": "integer", "x-nullable": true, "x-omitempty": false, - "x-omitempty": false, "example": 2000 }, "aoa": { @@ -20033,7 +20151,6 @@ func init() { "x-nullable": true, "x-omitempty": false }, - "remainingIncentive": { "remainingIncentive": { "description": "The remaining reimbursement amount that is still owed to the customer.", "type": "integer", @@ -20161,9 +20278,6 @@ func init() { "destinationAddress": { "$ref": "#/definitions/Address" }, - "destinationAddress": { - "$ref": "#/definitions/Address" - }, "destinationPostalCode": { "description": "The postal code of the destination location where goods are being delivered to.", "type": "string", @@ -20232,16 +20346,6 @@ func init() { "x-nullable": true, "x-omitempty": false }, - "hasSecondaryDestinationAddress": { - "type": "boolean", - "x-nullable": true, - "x-omitempty": false - }, - "hasSecondaryPickupAddress": { - "type": "boolean", - "x-nullable": true, - "x-omitempty": false - }, "id": { "description": "Primary auto-generated unique identifier of the PPM shipment object", "type": "string", @@ -20259,9 +20363,6 @@ func init() { "pickupAddress": { "$ref": "#/definitions/Address" }, - "pickupAddress": { - "$ref": "#/definitions/Address" - }, "pickupPostalCode": { "description": "The postal code of the origin location where goods are being moved from.", "type": "string", @@ -20303,19 +20404,6 @@ func init() { } ] }, - "secondaryDestinationAddress": { - "allOf": [ - { - "$ref": "#/definitions/Address" - }, - { - "x-nullable": true - }, - { - "x-omitempty": false - } - ] - }, "secondaryDestinationPostalCode": { "description": "An optional secondary location near the destination where goods will be dropped off.", "type": "string", @@ -20339,19 +20427,6 @@ func init() { } ] }, - "secondaryPickupAddress": { - "allOf": [ - { - "$ref": "#/definitions/Address" - }, - { - "x-nullable": true - }, - { - "x-omitempty": false - } - ] - }, "secondaryPickupPostalCode": { "type": "string", "format": "An optional secondary pickup location near the origin where additional goods exist.", @@ -20893,10 +20968,6 @@ func init() { "type": "string", "x-nullable": true }, - "orderType": { - "type": "string", - "x-nullable": true - }, "originDutyLocation": { "$ref": "#/definitions/DutyLocation" }, @@ -20981,10 +21052,6 @@ func init() { "type": "string", "x-nullable": true }, - "orderType": { - "type": "string", - "x-nullable": true - }, "originDutyLocation": { "$ref": "#/definitions/DutyLocation" }, @@ -21312,11 +21379,6 @@ func init() { "format": "date", "x-nullable": true }, - "sitAuthorizedEndDate": { - "type": "string", - "format": "date", - "x-nullable": true - }, "sitCustomerContacted": { "type": "string", "format": "date", @@ -21375,11 +21437,6 @@ func init() { "format": "date", "x-nullable": true }, - "sitAuthorizedEndDate": { - "type": "string", - "format": "date", - "x-nullable": true - }, "sitCustomerContacted": { "type": "string", "format": "date", @@ -21418,9 +21475,6 @@ func init() { "destinationGBLOC": { "$ref": "#/definitions/GBLOC" }, - "destinationGBLOC": { - "$ref": "#/definitions/GBLOC" - }, "dodID": { "type": "string", "x-nullable": true, @@ -21446,9 +21500,6 @@ func init() { "orderType": { "type": "string" }, - "orderType": { - "type": "string" - }, "originDutyLocationPostalCode": { "type": "string", "format": "zip", @@ -21469,19 +21520,6 @@ func init() { "format": "date", "x-nullable": true }, - "originGBLOC": { - "$ref": "#/definitions/GBLOC" - }, - "requestedDeliveryDate": { - "type": "string", - "format": "date", - "x-nullable": true - }, - "requestedPickupDate": { - "type": "string", - "format": "date", - "x-nullable": true - }, "shipmentsCount": { "type": "integer" }, @@ -22038,13 +22076,6 @@ func init() { "UpdateCustomerPayload": { "type": "object", "properties": { - "backupAddress": { - "allOf": [ - { - "$ref": "#/definitions/Address" - } - ] - }, "backupAddress": { "allOf": [ { @@ -22071,9 +22102,6 @@ func init() { "emailIsPreferred": { "type": "boolean" }, - "emailIsPreferred": { - "type": "boolean" - }, "first_name": { "type": "string", "example": "John" @@ -22102,15 +22130,6 @@ func init() { "pattern": "^[2-9]\\d{2}-\\d{3}-\\d{4}$", "x-nullable": true }, - "phoneIsPreferred": { - "type": "boolean" - }, - "secondaryTelephone": { - "type": "string", - "format": "telephone", - "pattern": "^[2-9]\\d{2}-\\d{3}-\\d{4}$", - "x-nullable": true - }, "suffix": { "type": "string", "x-nullable": true, diff --git a/pkg/gen/ghcapi/ghcoperations/customer/create_customer_with_okta_option.go b/pkg/gen/ghcapi/ghcoperations/customer/create_customer_with_okta_option.go new file mode 100644 index 00000000000..a3c98881961 --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/customer/create_customer_with_okta_option.go @@ -0,0 +1,58 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package customer + +// 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" +) + +// CreateCustomerWithOktaOptionHandlerFunc turns a function with the right signature into a create customer with okta option handler +type CreateCustomerWithOktaOptionHandlerFunc func(CreateCustomerWithOktaOptionParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn CreateCustomerWithOktaOptionHandlerFunc) Handle(params CreateCustomerWithOktaOptionParams) middleware.Responder { + return fn(params) +} + +// CreateCustomerWithOktaOptionHandler interface for that can handle valid create customer with okta option params +type CreateCustomerWithOktaOptionHandler interface { + Handle(CreateCustomerWithOktaOptionParams) middleware.Responder +} + +// NewCreateCustomerWithOktaOption creates a new http.Handler for the create customer with okta option operation +func NewCreateCustomerWithOktaOption(ctx *middleware.Context, handler CreateCustomerWithOktaOptionHandler) *CreateCustomerWithOktaOption { + return &CreateCustomerWithOktaOption{Context: ctx, Handler: handler} +} + +/* + CreateCustomerWithOktaOption swagger:route POST /customer customer createCustomerWithOktaOption + +# Creates a customer with Okta option + +Creates a customer with option to create an Okta profile account +*/ +type CreateCustomerWithOktaOption struct { + Context *middleware.Context + Handler CreateCustomerWithOktaOptionHandler +} + +func (o *CreateCustomerWithOktaOption) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewCreateCustomerWithOktaOptionParams() + 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/customer/create_customer_with_okta_option_parameters.go b/pkg/gen/ghcapi/ghcoperations/customer/create_customer_with_okta_option_parameters.go new file mode 100644 index 00000000000..d05da4adc8b --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/customer/create_customer_with_okta_option_parameters.go @@ -0,0 +1,84 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package customer + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "io" + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/validate" + + "github.com/transcom/mymove/pkg/gen/ghcmessages" +) + +// NewCreateCustomerWithOktaOptionParams creates a new CreateCustomerWithOktaOptionParams object +// +// There are no default values defined in the spec. +func NewCreateCustomerWithOktaOptionParams() CreateCustomerWithOktaOptionParams { + + return CreateCustomerWithOktaOptionParams{} +} + +// CreateCustomerWithOktaOptionParams contains all the bound params for the create customer with okta option operation +// typically these are obtained from a http.Request +// +// swagger:parameters createCustomerWithOktaOption +type CreateCustomerWithOktaOptionParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /* + Required: true + In: body + */ + Body *ghcmessages.CreateCustomerPayload +} + +// 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 NewCreateCustomerWithOktaOptionParams() beforehand. +func (o *CreateCustomerWithOktaOptionParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + if runtime.HasBody(r) { + defer r.Body.Close() + var body ghcmessages.CreateCustomerPayload + if err := route.Consumer.Consume(r.Body, &body); err != nil { + if err == io.EOF { + res = append(res, errors.Required("body", "body", "")) + } else { + res = append(res, errors.NewParseError("body", "body", "", err)) + } + } else { + // validate body object + if err := body.Validate(route.Formats); err != nil { + res = append(res, err) + } + + ctx := validate.WithOperationRequest(r.Context()) + if err := body.ContextValidate(ctx, route.Formats); err != nil { + res = append(res, err) + } + + if len(res) == 0 { + o.Body = &body + } + } + } else { + res = append(res, errors.Required("body", "body", "")) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/pkg/gen/ghcapi/ghcoperations/customer/create_customer_with_okta_option_responses.go b/pkg/gen/ghcapi/ghcoperations/customer/create_customer_with_okta_option_responses.go new file mode 100644 index 00000000000..dd9ccd4fdc2 --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/customer/create_customer_with_okta_option_responses.go @@ -0,0 +1,374 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package customer + +// 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" +) + +// CreateCustomerWithOktaOptionOKCode is the HTTP code returned for type CreateCustomerWithOktaOptionOK +const CreateCustomerWithOktaOptionOKCode int = 200 + +/* +CreateCustomerWithOktaOptionOK successfully created the customer + +swagger:response createCustomerWithOktaOptionOK +*/ +type CreateCustomerWithOktaOptionOK struct { + + /* + In: Body + */ + Payload *ghcmessages.CreatedCustomer `json:"body,omitempty"` +} + +// NewCreateCustomerWithOktaOptionOK creates CreateCustomerWithOktaOptionOK with default headers values +func NewCreateCustomerWithOktaOptionOK() *CreateCustomerWithOktaOptionOK { + + return &CreateCustomerWithOktaOptionOK{} +} + +// WithPayload adds the payload to the create customer with okta option o k response +func (o *CreateCustomerWithOktaOptionOK) WithPayload(payload *ghcmessages.CreatedCustomer) *CreateCustomerWithOktaOptionOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the create customer with okta option o k response +func (o *CreateCustomerWithOktaOptionOK) SetPayload(payload *ghcmessages.CreatedCustomer) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *CreateCustomerWithOktaOptionOK) 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 + } + } +} + +// CreateCustomerWithOktaOptionBadRequestCode is the HTTP code returned for type CreateCustomerWithOktaOptionBadRequest +const CreateCustomerWithOktaOptionBadRequestCode int = 400 + +/* +CreateCustomerWithOktaOptionBadRequest The request payload is invalid + +swagger:response createCustomerWithOktaOptionBadRequest +*/ +type CreateCustomerWithOktaOptionBadRequest struct { + + /* + In: Body + */ + Payload *ghcmessages.Error `json:"body,omitempty"` +} + +// NewCreateCustomerWithOktaOptionBadRequest creates CreateCustomerWithOktaOptionBadRequest with default headers values +func NewCreateCustomerWithOktaOptionBadRequest() *CreateCustomerWithOktaOptionBadRequest { + + return &CreateCustomerWithOktaOptionBadRequest{} +} + +// WithPayload adds the payload to the create customer with okta option bad request response +func (o *CreateCustomerWithOktaOptionBadRequest) WithPayload(payload *ghcmessages.Error) *CreateCustomerWithOktaOptionBadRequest { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the create customer with okta option bad request response +func (o *CreateCustomerWithOktaOptionBadRequest) SetPayload(payload *ghcmessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *CreateCustomerWithOktaOptionBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(400) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// CreateCustomerWithOktaOptionUnauthorizedCode is the HTTP code returned for type CreateCustomerWithOktaOptionUnauthorized +const CreateCustomerWithOktaOptionUnauthorizedCode int = 401 + +/* +CreateCustomerWithOktaOptionUnauthorized The request was denied + +swagger:response createCustomerWithOktaOptionUnauthorized +*/ +type CreateCustomerWithOktaOptionUnauthorized struct { + + /* + In: Body + */ + Payload *ghcmessages.Error `json:"body,omitempty"` +} + +// NewCreateCustomerWithOktaOptionUnauthorized creates CreateCustomerWithOktaOptionUnauthorized with default headers values +func NewCreateCustomerWithOktaOptionUnauthorized() *CreateCustomerWithOktaOptionUnauthorized { + + return &CreateCustomerWithOktaOptionUnauthorized{} +} + +// WithPayload adds the payload to the create customer with okta option unauthorized response +func (o *CreateCustomerWithOktaOptionUnauthorized) WithPayload(payload *ghcmessages.Error) *CreateCustomerWithOktaOptionUnauthorized { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the create customer with okta option unauthorized response +func (o *CreateCustomerWithOktaOptionUnauthorized) SetPayload(payload *ghcmessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *CreateCustomerWithOktaOptionUnauthorized) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(401) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// CreateCustomerWithOktaOptionForbiddenCode is the HTTP code returned for type CreateCustomerWithOktaOptionForbidden +const CreateCustomerWithOktaOptionForbiddenCode int = 403 + +/* +CreateCustomerWithOktaOptionForbidden The request was denied + +swagger:response createCustomerWithOktaOptionForbidden +*/ +type CreateCustomerWithOktaOptionForbidden struct { + + /* + In: Body + */ + Payload *ghcmessages.Error `json:"body,omitempty"` +} + +// NewCreateCustomerWithOktaOptionForbidden creates CreateCustomerWithOktaOptionForbidden with default headers values +func NewCreateCustomerWithOktaOptionForbidden() *CreateCustomerWithOktaOptionForbidden { + + return &CreateCustomerWithOktaOptionForbidden{} +} + +// WithPayload adds the payload to the create customer with okta option forbidden response +func (o *CreateCustomerWithOktaOptionForbidden) WithPayload(payload *ghcmessages.Error) *CreateCustomerWithOktaOptionForbidden { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the create customer with okta option forbidden response +func (o *CreateCustomerWithOktaOptionForbidden) SetPayload(payload *ghcmessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *CreateCustomerWithOktaOptionForbidden) 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 + } + } +} + +// CreateCustomerWithOktaOptionNotFoundCode is the HTTP code returned for type CreateCustomerWithOktaOptionNotFound +const CreateCustomerWithOktaOptionNotFoundCode int = 404 + +/* +CreateCustomerWithOktaOptionNotFound The requested resource wasn't found + +swagger:response createCustomerWithOktaOptionNotFound +*/ +type CreateCustomerWithOktaOptionNotFound struct { + + /* + In: Body + */ + Payload *ghcmessages.Error `json:"body,omitempty"` +} + +// NewCreateCustomerWithOktaOptionNotFound creates CreateCustomerWithOktaOptionNotFound with default headers values +func NewCreateCustomerWithOktaOptionNotFound() *CreateCustomerWithOktaOptionNotFound { + + return &CreateCustomerWithOktaOptionNotFound{} +} + +// WithPayload adds the payload to the create customer with okta option not found response +func (o *CreateCustomerWithOktaOptionNotFound) WithPayload(payload *ghcmessages.Error) *CreateCustomerWithOktaOptionNotFound { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the create customer with okta option not found response +func (o *CreateCustomerWithOktaOptionNotFound) SetPayload(payload *ghcmessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *CreateCustomerWithOktaOptionNotFound) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(404) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// CreateCustomerWithOktaOptionPreconditionFailedCode is the HTTP code returned for type CreateCustomerWithOktaOptionPreconditionFailed +const CreateCustomerWithOktaOptionPreconditionFailedCode int = 412 + +/* +CreateCustomerWithOktaOptionPreconditionFailed Precondition failed + +swagger:response createCustomerWithOktaOptionPreconditionFailed +*/ +type CreateCustomerWithOktaOptionPreconditionFailed struct { + + /* + In: Body + */ + Payload *ghcmessages.Error `json:"body,omitempty"` +} + +// NewCreateCustomerWithOktaOptionPreconditionFailed creates CreateCustomerWithOktaOptionPreconditionFailed with default headers values +func NewCreateCustomerWithOktaOptionPreconditionFailed() *CreateCustomerWithOktaOptionPreconditionFailed { + + return &CreateCustomerWithOktaOptionPreconditionFailed{} +} + +// WithPayload adds the payload to the create customer with okta option precondition failed response +func (o *CreateCustomerWithOktaOptionPreconditionFailed) WithPayload(payload *ghcmessages.Error) *CreateCustomerWithOktaOptionPreconditionFailed { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the create customer with okta option precondition failed response +func (o *CreateCustomerWithOktaOptionPreconditionFailed) SetPayload(payload *ghcmessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *CreateCustomerWithOktaOptionPreconditionFailed) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(412) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// CreateCustomerWithOktaOptionUnprocessableEntityCode is the HTTP code returned for type CreateCustomerWithOktaOptionUnprocessableEntity +const CreateCustomerWithOktaOptionUnprocessableEntityCode int = 422 + +/* +CreateCustomerWithOktaOptionUnprocessableEntity The payload was unprocessable. + +swagger:response createCustomerWithOktaOptionUnprocessableEntity +*/ +type CreateCustomerWithOktaOptionUnprocessableEntity struct { + + /* + In: Body + */ + Payload *ghcmessages.ValidationError `json:"body,omitempty"` +} + +// NewCreateCustomerWithOktaOptionUnprocessableEntity creates CreateCustomerWithOktaOptionUnprocessableEntity with default headers values +func NewCreateCustomerWithOktaOptionUnprocessableEntity() *CreateCustomerWithOktaOptionUnprocessableEntity { + + return &CreateCustomerWithOktaOptionUnprocessableEntity{} +} + +// WithPayload adds the payload to the create customer with okta option unprocessable entity response +func (o *CreateCustomerWithOktaOptionUnprocessableEntity) WithPayload(payload *ghcmessages.ValidationError) *CreateCustomerWithOktaOptionUnprocessableEntity { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the create customer with okta option unprocessable entity response +func (o *CreateCustomerWithOktaOptionUnprocessableEntity) SetPayload(payload *ghcmessages.ValidationError) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *CreateCustomerWithOktaOptionUnprocessableEntity) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(422) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// CreateCustomerWithOktaOptionInternalServerErrorCode is the HTTP code returned for type CreateCustomerWithOktaOptionInternalServerError +const CreateCustomerWithOktaOptionInternalServerErrorCode int = 500 + +/* +CreateCustomerWithOktaOptionInternalServerError A server error occurred + +swagger:response createCustomerWithOktaOptionInternalServerError +*/ +type CreateCustomerWithOktaOptionInternalServerError struct { + + /* + In: Body + */ + Payload *ghcmessages.Error `json:"body,omitempty"` +} + +// NewCreateCustomerWithOktaOptionInternalServerError creates CreateCustomerWithOktaOptionInternalServerError with default headers values +func NewCreateCustomerWithOktaOptionInternalServerError() *CreateCustomerWithOktaOptionInternalServerError { + + return &CreateCustomerWithOktaOptionInternalServerError{} +} + +// WithPayload adds the payload to the create customer with okta option internal server error response +func (o *CreateCustomerWithOktaOptionInternalServerError) WithPayload(payload *ghcmessages.Error) *CreateCustomerWithOktaOptionInternalServerError { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the create customer with okta option internal server error response +func (o *CreateCustomerWithOktaOptionInternalServerError) SetPayload(payload *ghcmessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *CreateCustomerWithOktaOptionInternalServerError) 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/customer/create_customer_with_okta_option_urlbuilder.go b/pkg/gen/ghcapi/ghcoperations/customer/create_customer_with_okta_option_urlbuilder.go new file mode 100644 index 00000000000..a7aba59aa7d --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/customer/create_customer_with_okta_option_urlbuilder.go @@ -0,0 +1,87 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package customer + +// 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" +) + +// CreateCustomerWithOktaOptionURL generates an URL for the create customer with okta option operation +type CreateCustomerWithOktaOptionURL struct { + _basePath string +} + +// 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 *CreateCustomerWithOktaOptionURL) WithBasePath(bp string) *CreateCustomerWithOktaOptionURL { + 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 *CreateCustomerWithOktaOptionURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *CreateCustomerWithOktaOptionURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/customer" + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/ghc/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *CreateCustomerWithOktaOptionURL) 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 *CreateCustomerWithOktaOptionURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *CreateCustomerWithOktaOptionURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on CreateCustomerWithOktaOptionURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on CreateCustomerWithOktaOptionURL") + } + + 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 *CreateCustomerWithOktaOptionURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/pkg/gen/ghcapi/ghcoperations/mymove_api.go b/pkg/gen/ghcapi/ghcoperations/mymove_api.go index b2be38c144a..341b0535c30 100644 --- a/pkg/gen/ghcapi/ghcoperations/mymove_api.go +++ b/pkg/gen/ghcapi/ghcoperations/mymove_api.go @@ -90,6 +90,9 @@ func NewMymoveAPI(spec *loads.Document) *MymoveAPI { CustomerSupportRemarksCreateCustomerSupportRemarkForMoveHandler: customer_support_remarks.CreateCustomerSupportRemarkForMoveHandlerFunc(func(params customer_support_remarks.CreateCustomerSupportRemarkForMoveParams) middleware.Responder { return middleware.NotImplemented("operation customer_support_remarks.CreateCustomerSupportRemarkForMove has not yet been implemented") }), + CustomerCreateCustomerWithOktaOptionHandler: customer.CreateCustomerWithOktaOptionHandlerFunc(func(params customer.CreateCustomerWithOktaOptionParams) middleware.Responder { + return middleware.NotImplemented("operation customer.CreateCustomerWithOktaOption has not yet been implemented") + }), EvaluationReportsCreateEvaluationReportHandler: evaluation_reports.CreateEvaluationReportHandlerFunc(func(params evaluation_reports.CreateEvaluationReportParams) middleware.Responder { return middleware.NotImplemented("operation evaluation_reports.CreateEvaluationReport has not yet been implemented") }), @@ -355,6 +358,8 @@ type MymoveAPI struct { ShipmentCreateApprovedSITDurationUpdateHandler shipment.CreateApprovedSITDurationUpdateHandler // CustomerSupportRemarksCreateCustomerSupportRemarkForMoveHandler sets the operation handler for the create customer support remark for move operation CustomerSupportRemarksCreateCustomerSupportRemarkForMoveHandler customer_support_remarks.CreateCustomerSupportRemarkForMoveHandler + // CustomerCreateCustomerWithOktaOptionHandler sets the operation handler for the create customer with okta option operation + CustomerCreateCustomerWithOktaOptionHandler customer.CreateCustomerWithOktaOptionHandler // EvaluationReportsCreateEvaluationReportHandler sets the operation handler for the create evaluation report operation EvaluationReportsCreateEvaluationReportHandler evaluation_reports.CreateEvaluationReportHandler // MtoShipmentCreateMTOShipmentHandler sets the operation handler for the create m t o shipment operation @@ -598,6 +603,9 @@ func (o *MymoveAPI) Validate() error { if o.CustomerSupportRemarksCreateCustomerSupportRemarkForMoveHandler == nil { unregistered = append(unregistered, "customer_support_remarks.CreateCustomerSupportRemarkForMoveHandler") } + if o.CustomerCreateCustomerWithOktaOptionHandler == nil { + unregistered = append(unregistered, "customer.CreateCustomerWithOktaOptionHandler") + } if o.EvaluationReportsCreateEvaluationReportHandler == nil { unregistered = append(unregistered, "evaluation_reports.CreateEvaluationReportHandler") } @@ -931,6 +939,10 @@ func (o *MymoveAPI) initHandlerCache() { if o.handlers["POST"] == nil { o.handlers["POST"] = make(map[string]http.Handler) } + o.handlers["POST"]["/customer"] = customer.NewCreateCustomerWithOktaOption(o.context, o.CustomerCreateCustomerWithOktaOptionHandler) + if o.handlers["POST"] == nil { + o.handlers["POST"] = make(map[string]http.Handler) + } o.handlers["POST"]["/moves/{locator}/evaluation-reports"] = evaluation_reports.NewCreateEvaluationReport(o.context, o.EvaluationReportsCreateEvaluationReportHandler) if o.handlers["POST"] == nil { o.handlers["POST"] = make(map[string]http.Handler) diff --git a/pkg/gen/ghcmessages/backup_contact.go b/pkg/gen/ghcmessages/backup_contact.go index 8cf2cd0d49c..904ed892cff 100644 --- a/pkg/gen/ghcmessages/backup_contact.go +++ b/pkg/gen/ghcmessages/backup_contact.go @@ -20,8 +20,8 @@ import ( type BackupContact struct { // email + // Example: backupContact@mail.com // Required: true - // Pattern: ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ Email *string `json:"email"` // name @@ -62,10 +62,6 @@ func (m *BackupContact) validateEmail(formats strfmt.Registry) error { return err } - if err := validate.Pattern("email", "body", *m.Email, `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`); err != nil { - return err - } - return nil } diff --git a/pkg/gen/ghcmessages/create_customer_payload.go b/pkg/gen/ghcmessages/create_customer_payload.go new file mode 100644 index 00000000000..bc44ff9183f --- /dev/null +++ b/pkg/gen/ghcmessages/create_customer_payload.go @@ -0,0 +1,303 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package ghcmessages + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// CreateCustomerPayload create customer payload +// +// swagger:model CreateCustomerPayload +type CreateCustomerPayload struct { + + // affiliation + Affiliation *Affiliation `json:"affiliation,omitempty"` + + // backup contact + BackupContact *BackupContact `json:"backupContact,omitempty"` + + // backup mailing address + BackupMailingAddress struct { + Address + } `json:"backupMailingAddress,omitempty"` + + // create okta account + CreateOktaAccount bool `json:"createOktaAccount,omitempty"` + + // edipi + // Example: John + Edipi *string `json:"edipi,omitempty"` + + // email is preferred + EmailIsPreferred bool `json:"emailIsPreferred,omitempty"` + + // first name + // Example: John + FirstName string `json:"firstName,omitempty"` + + // last name + // Example: Doe + LastName string `json:"lastName,omitempty"` + + // middle name + // Example: David + MiddleName *string `json:"middleName,omitempty"` + + // personal email + // Example: personalEmail@email.com + // Pattern: ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ + PersonalEmail string `json:"personalEmail,omitempty"` + + // phone is preferred + PhoneIsPreferred bool `json:"phoneIsPreferred,omitempty"` + + // residential address + ResidentialAddress struct { + Address + } `json:"residentialAddress,omitempty"` + + // secondary telephone + // Pattern: ^[2-9]\d{2}-\d{3}-\d{4}$ + SecondaryTelephone *string `json:"secondaryTelephone,omitempty"` + + // suffix + // Example: Jr. + Suffix *string `json:"suffix,omitempty"` + + // telephone + // Pattern: ^[2-9]\d{2}-\d{3}-\d{4}$ + Telephone *string `json:"telephone,omitempty"` +} + +// Validate validates this create customer payload +func (m *CreateCustomerPayload) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateAffiliation(formats); err != nil { + res = append(res, err) + } + + if err := m.validateBackupContact(formats); err != nil { + res = append(res, err) + } + + if err := m.validateBackupMailingAddress(formats); err != nil { + res = append(res, err) + } + + if err := m.validatePersonalEmail(formats); err != nil { + res = append(res, err) + } + + if err := m.validateResidentialAddress(formats); err != nil { + res = append(res, err) + } + + if err := m.validateSecondaryTelephone(formats); err != nil { + res = append(res, err) + } + + if err := m.validateTelephone(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *CreateCustomerPayload) validateAffiliation(formats strfmt.Registry) error { + if swag.IsZero(m.Affiliation) { // not required + return nil + } + + if m.Affiliation != nil { + if err := m.Affiliation.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("affiliation") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("affiliation") + } + return err + } + } + + return nil +} + +func (m *CreateCustomerPayload) validateBackupContact(formats strfmt.Registry) error { + if swag.IsZero(m.BackupContact) { // not required + return nil + } + + if m.BackupContact != nil { + if err := m.BackupContact.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("backupContact") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("backupContact") + } + return err + } + } + + return nil +} + +func (m *CreateCustomerPayload) validateBackupMailingAddress(formats strfmt.Registry) error { + if swag.IsZero(m.BackupMailingAddress) { // not required + return nil + } + + return nil +} + +func (m *CreateCustomerPayload) validatePersonalEmail(formats strfmt.Registry) error { + if swag.IsZero(m.PersonalEmail) { // not required + return nil + } + + if err := validate.Pattern("personalEmail", "body", m.PersonalEmail, `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`); err != nil { + return err + } + + return nil +} + +func (m *CreateCustomerPayload) validateResidentialAddress(formats strfmt.Registry) error { + if swag.IsZero(m.ResidentialAddress) { // not required + return nil + } + + return nil +} + +func (m *CreateCustomerPayload) validateSecondaryTelephone(formats strfmt.Registry) error { + if swag.IsZero(m.SecondaryTelephone) { // not required + return nil + } + + if err := validate.Pattern("secondaryTelephone", "body", *m.SecondaryTelephone, `^[2-9]\d{2}-\d{3}-\d{4}$`); err != nil { + return err + } + + return nil +} + +func (m *CreateCustomerPayload) validateTelephone(formats strfmt.Registry) error { + if swag.IsZero(m.Telephone) { // not required + return nil + } + + if err := validate.Pattern("telephone", "body", *m.Telephone, `^[2-9]\d{2}-\d{3}-\d{4}$`); err != nil { + return err + } + + return nil +} + +// ContextValidate validate this create customer payload based on the context it is used +func (m *CreateCustomerPayload) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateAffiliation(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateBackupContact(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateBackupMailingAddress(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateResidentialAddress(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *CreateCustomerPayload) contextValidateAffiliation(ctx context.Context, formats strfmt.Registry) error { + + if m.Affiliation != nil { + + if swag.IsZero(m.Affiliation) { // not required + return nil + } + + if err := m.Affiliation.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("affiliation") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("affiliation") + } + return err + } + } + + return nil +} + +func (m *CreateCustomerPayload) contextValidateBackupContact(ctx context.Context, formats strfmt.Registry) error { + + if m.BackupContact != nil { + + if swag.IsZero(m.BackupContact) { // not required + return nil + } + + if err := m.BackupContact.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("backupContact") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("backupContact") + } + return err + } + } + + return nil +} + +func (m *CreateCustomerPayload) contextValidateBackupMailingAddress(ctx context.Context, formats strfmt.Registry) error { + + return nil +} + +func (m *CreateCustomerPayload) contextValidateResidentialAddress(ctx context.Context, formats strfmt.Registry) error { + + return nil +} + +// MarshalBinary interface implementation +func (m *CreateCustomerPayload) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *CreateCustomerPayload) UnmarshalBinary(b []byte) error { + var res CreateCustomerPayload + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/pkg/gen/ghcmessages/created_customer.go b/pkg/gen/ghcmessages/created_customer.go new file mode 100644 index 00000000000..e9fda9bb23c --- /dev/null +++ b/pkg/gen/ghcmessages/created_customer.go @@ -0,0 +1,348 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package ghcmessages + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// CreatedCustomer created customer +// +// swagger:model CreatedCustomer +type CreatedCustomer struct { + + // Branch of service customer is affilated with + Affiliation string `json:"affiliation,omitempty"` + + // backup address + BackupAddress *Address `json:"backupAddress,omitempty"` + + // backup contact + BackupContact *BackupContact `json:"backupContact,omitempty"` + + // edipi + Edipi *string `json:"edipi,omitempty"` + + // email is preferred + EmailIsPreferred bool `json:"emailIsPreferred,omitempty"` + + // first name + // Example: John + FirstName string `json:"firstName,omitempty"` + + // id + // Example: c56a4180-65aa-42ec-a945-5fd21dec0538 + // Format: uuid + ID strfmt.UUID `json:"id,omitempty"` + + // last name + // Example: Doe + LastName string `json:"lastName,omitempty"` + + // middle name + // Example: David + MiddleName *string `json:"middleName,omitempty"` + + // okta email + OktaEmail string `json:"oktaEmail,omitempty"` + + // okta ID + OktaID string `json:"oktaID,omitempty"` + + // personal email + // Pattern: ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ + PersonalEmail string `json:"personalEmail,omitempty"` + + // phone is preferred + PhoneIsPreferred bool `json:"phoneIsPreferred,omitempty"` + + // residential address + ResidentialAddress *Address `json:"residentialAddress,omitempty"` + + // secondary telephone + // Pattern: ^[2-9]\d{2}-\d{3}-\d{4}$ + SecondaryTelephone *string `json:"secondaryTelephone,omitempty"` + + // suffix + // Example: Jr. + Suffix *string `json:"suffix,omitempty"` + + // telephone + // Pattern: ^[2-9]\d{2}-\d{3}-\d{4}$ + Telephone *string `json:"telephone,omitempty"` + + // user ID + // Example: c56a4180-65aa-42ec-a945-5fd21dec0538 + // Format: uuid + UserID strfmt.UUID `json:"userID,omitempty"` +} + +// Validate validates this created customer +func (m *CreatedCustomer) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateBackupAddress(formats); err != nil { + res = append(res, err) + } + + if err := m.validateBackupContact(formats); err != nil { + res = append(res, err) + } + + if err := m.validateID(formats); err != nil { + res = append(res, err) + } + + if err := m.validatePersonalEmail(formats); err != nil { + res = append(res, err) + } + + if err := m.validateResidentialAddress(formats); err != nil { + res = append(res, err) + } + + if err := m.validateSecondaryTelephone(formats); err != nil { + res = append(res, err) + } + + if err := m.validateTelephone(formats); err != nil { + res = append(res, err) + } + + if err := m.validateUserID(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *CreatedCustomer) validateBackupAddress(formats strfmt.Registry) error { + if swag.IsZero(m.BackupAddress) { // not required + return nil + } + + if m.BackupAddress != nil { + if err := m.BackupAddress.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("backupAddress") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("backupAddress") + } + return err + } + } + + return nil +} + +func (m *CreatedCustomer) validateBackupContact(formats strfmt.Registry) error { + if swag.IsZero(m.BackupContact) { // not required + return nil + } + + if m.BackupContact != nil { + if err := m.BackupContact.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("backupContact") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("backupContact") + } + return err + } + } + + return nil +} + +func (m *CreatedCustomer) 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 *CreatedCustomer) validatePersonalEmail(formats strfmt.Registry) error { + if swag.IsZero(m.PersonalEmail) { // not required + return nil + } + + if err := validate.Pattern("personalEmail", "body", m.PersonalEmail, `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`); err != nil { + return err + } + + return nil +} + +func (m *CreatedCustomer) validateResidentialAddress(formats strfmt.Registry) error { + if swag.IsZero(m.ResidentialAddress) { // not required + return nil + } + + if m.ResidentialAddress != nil { + if err := m.ResidentialAddress.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("residentialAddress") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("residentialAddress") + } + return err + } + } + + return nil +} + +func (m *CreatedCustomer) validateSecondaryTelephone(formats strfmt.Registry) error { + if swag.IsZero(m.SecondaryTelephone) { // not required + return nil + } + + if err := validate.Pattern("secondaryTelephone", "body", *m.SecondaryTelephone, `^[2-9]\d{2}-\d{3}-\d{4}$`); err != nil { + return err + } + + return nil +} + +func (m *CreatedCustomer) validateTelephone(formats strfmt.Registry) error { + if swag.IsZero(m.Telephone) { // not required + return nil + } + + if err := validate.Pattern("telephone", "body", *m.Telephone, `^[2-9]\d{2}-\d{3}-\d{4}$`); err != nil { + return err + } + + return nil +} + +func (m *CreatedCustomer) validateUserID(formats strfmt.Registry) error { + if swag.IsZero(m.UserID) { // not required + return nil + } + + if err := validate.FormatOf("userID", "body", "uuid", m.UserID.String(), formats); err != nil { + return err + } + + return nil +} + +// ContextValidate validate this created customer based on the context it is used +func (m *CreatedCustomer) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateBackupAddress(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateBackupContact(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateResidentialAddress(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *CreatedCustomer) contextValidateBackupAddress(ctx context.Context, formats strfmt.Registry) error { + + if m.BackupAddress != nil { + + if swag.IsZero(m.BackupAddress) { // not required + return nil + } + + if err := m.BackupAddress.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("backupAddress") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("backupAddress") + } + return err + } + } + + return nil +} + +func (m *CreatedCustomer) contextValidateBackupContact(ctx context.Context, formats strfmt.Registry) error { + + if m.BackupContact != nil { + + if swag.IsZero(m.BackupContact) { // not required + return nil + } + + if err := m.BackupContact.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("backupContact") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("backupContact") + } + return err + } + } + + return nil +} + +func (m *CreatedCustomer) contextValidateResidentialAddress(ctx context.Context, formats strfmt.Registry) error { + + if m.ResidentialAddress != nil { + + if swag.IsZero(m.ResidentialAddress) { // not required + return nil + } + + if err := m.ResidentialAddress.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("residentialAddress") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("residentialAddress") + } + return err + } + } + + return nil +} + +// MarshalBinary interface implementation +func (m *CreatedCustomer) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *CreatedCustomer) UnmarshalBinary(b []byte) error { + var res CreatedCustomer + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/pkg/gen/ghcmessages/m_t_o_service_item.go b/pkg/gen/ghcmessages/m_t_o_service_item.go index 1256e256e72..e2c69984b79 100644 --- a/pkg/gen/ghcmessages/m_t_o_service_item.go +++ b/pkg/gen/ghcmessages/m_t_o_service_item.go @@ -123,6 +123,9 @@ type MTOServiceItem struct { // Format: date SitCustomerContacted *strfmt.Date `json:"sitCustomerContacted,omitempty"` + // sit delivery miles + SitDeliveryMiles *int64 `json:"sitDeliveryMiles,omitempty"` + // sit departure date // Format: date-time SitDepartureDate *strfmt.DateTime `json:"sitDepartureDate,omitempty"` @@ -137,6 +140,12 @@ type MTOServiceItem struct { // Format: date-time SitEntryDate *strfmt.DateTime `json:"sitEntryDate,omitempty"` + // sit origin h h g actual address + SitOriginHHGActualAddress *Address `json:"sitOriginHHGActualAddress,omitempty"` + + // sit origin h h g original address + SitOriginHHGOriginalAddress *Address `json:"sitOriginHHGOriginalAddress,omitempty"` + // sit requested delivery // Format: date SitRequestedDelivery *strfmt.Date `json:"sitRequestedDelivery,omitempty"` @@ -243,6 +252,14 @@ func (m *MTOServiceItem) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validateSitOriginHHGActualAddress(formats); err != nil { + res = append(res, err) + } + + if err := m.validateSitOriginHHGOriginalAddress(formats); err != nil { + res = append(res, err) + } + if err := m.validateSitRequestedDelivery(formats); err != nil { res = append(res, err) } @@ -572,6 +589,44 @@ func (m *MTOServiceItem) validateSitEntryDate(formats strfmt.Registry) error { return nil } +func (m *MTOServiceItem) validateSitOriginHHGActualAddress(formats strfmt.Registry) error { + if swag.IsZero(m.SitOriginHHGActualAddress) { // not required + return nil + } + + if m.SitOriginHHGActualAddress != nil { + if err := m.SitOriginHHGActualAddress.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("sitOriginHHGActualAddress") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("sitOriginHHGActualAddress") + } + return err + } + } + + return nil +} + +func (m *MTOServiceItem) validateSitOriginHHGOriginalAddress(formats strfmt.Registry) error { + if swag.IsZero(m.SitOriginHHGOriginalAddress) { // not required + return nil + } + + if m.SitOriginHHGOriginalAddress != nil { + if err := m.SitOriginHHGOriginalAddress.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("sitOriginHHGOriginalAddress") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("sitOriginHHGOriginalAddress") + } + return err + } + } + + return nil +} + func (m *MTOServiceItem) validateSitRequestedDelivery(formats strfmt.Registry) error { if swag.IsZero(m.SitRequestedDelivery) { // not required return nil @@ -657,6 +712,14 @@ func (m *MTOServiceItem) ContextValidate(ctx context.Context, formats strfmt.Reg res = append(res, err) } + if err := m.contextValidateSitOriginHHGActualAddress(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateSitOriginHHGOriginalAddress(ctx, formats); err != nil { + res = append(res, err) + } + if err := m.contextValidateStatus(ctx, formats); err != nil { res = append(res, err) } @@ -774,6 +837,48 @@ func (m *MTOServiceItem) contextValidateSitDestinationOriginalAddress(ctx contex return nil } +func (m *MTOServiceItem) contextValidateSitOriginHHGActualAddress(ctx context.Context, formats strfmt.Registry) error { + + if m.SitOriginHHGActualAddress != nil { + + if swag.IsZero(m.SitOriginHHGActualAddress) { // not required + return nil + } + + if err := m.SitOriginHHGActualAddress.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("sitOriginHHGActualAddress") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("sitOriginHHGActualAddress") + } + return err + } + } + + return nil +} + +func (m *MTOServiceItem) contextValidateSitOriginHHGOriginalAddress(ctx context.Context, formats strfmt.Registry) error { + + if m.SitOriginHHGOriginalAddress != nil { + + if swag.IsZero(m.SitOriginHHGOriginalAddress) { // not required + return nil + } + + if err := m.SitOriginHHGOriginalAddress.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("sitOriginHHGOriginalAddress") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("sitOriginHHGOriginalAddress") + } + return err + } + } + + return nil +} + func (m *MTOServiceItem) contextValidateStatus(ctx context.Context, formats strfmt.Registry) error { if swag.IsZero(m.Status) { // not required diff --git a/pkg/gen/ghcmessages/s_i_t_status.go b/pkg/gen/ghcmessages/s_i_t_status.go index d9dbeb70f46..ef4fbc1fb7d 100644 --- a/pkg/gen/ghcmessages/s_i_t_status.go +++ b/pkg/gen/ghcmessages/s_i_t_status.go @@ -233,10 +233,6 @@ type SITStatusCurrentSIT struct { // Format: date SitAllowanceEndDate *strfmt.Date `json:"sitAllowanceEndDate,omitempty"` - // sit authorized end date - // Format: date - SitAuthorizedEndDate *strfmt.Date `json:"sitAuthorizedEndDate,omitempty"` - // sit customer contacted // Format: date SitCustomerContacted *strfmt.Date `json:"sitCustomerContacted,omitempty"` @@ -270,10 +266,6 @@ func (m *SITStatusCurrentSIT) Validate(formats strfmt.Registry) error { res = append(res, err) } - if err := m.validateSitAuthorizedEndDate(formats); err != nil { - res = append(res, err) - } - if err := m.validateSitCustomerContacted(formats); err != nil { res = append(res, err) } @@ -332,18 +324,6 @@ func (m *SITStatusCurrentSIT) validateSitAllowanceEndDate(formats strfmt.Registr return nil } -func (m *SITStatusCurrentSIT) validateSitAuthorizedEndDate(formats strfmt.Registry) error { - if swag.IsZero(m.SitAuthorizedEndDate) { // not required - return nil - } - - if err := validate.FormatOf("currentSIT"+"."+"sitAuthorizedEndDate", "body", "date", m.SitAuthorizedEndDate.String(), formats); err != nil { - return err - } - - return nil -} - func (m *SITStatusCurrentSIT) validateSitCustomerContacted(formats strfmt.Registry) error { if swag.IsZero(m.SitCustomerContacted) { // not required return nil diff --git a/pkg/gen/internalapi/configure_mymove.go b/pkg/gen/internalapi/configure_mymove.go index 7ec8d23c9c3..59a7b9bec7a 100644 --- a/pkg/gen/internalapi/configure_mymove.go +++ b/pkg/gen/internalapi/configure_mymove.go @@ -297,16 +297,6 @@ func configureAPI(api *internaloperations.MymoveAPI) http.Handler { return middleware.NotImplemented("operation orders.ShowOrders has not yet been implemented") }) } - if api.PpmShowPPMEstimateHandler == nil { - api.PpmShowPPMEstimateHandler = ppm.ShowPPMEstimateHandlerFunc(func(params ppm.ShowPPMEstimateParams) middleware.Responder { - return middleware.NotImplemented("operation ppm.ShowPPMEstimate has not yet been implemented") - }) - } - if api.PpmShowPPMSitEstimateHandler == nil { - api.PpmShowPPMSitEstimateHandler = ppm.ShowPPMSitEstimateHandlerFunc(func(params ppm.ShowPPMSitEstimateParams) middleware.Responder { - return middleware.NotImplemented("operation ppm.ShowPPMSitEstimate has not yet been implemented") - }) - } if api.PpmShowPaymentPacketHandler == nil { api.PpmShowPaymentPacketHandler = ppm.ShowPaymentPacketHandlerFunc(func(params ppm.ShowPaymentPacketParams) middleware.Responder { return middleware.NotImplemented("operation ppm.ShowPaymentPacket has not yet been implemented") diff --git a/pkg/gen/internalapi/embedded_spec.go b/pkg/gen/internalapi/embedded_spec.go index 342cdd8eaf8..fa7370a7f7b 100644 --- a/pkg/gen/internalapi/embedded_spec.go +++ b/pkg/gen/internalapi/embedded_spec.go @@ -431,162 +431,6 @@ func init() { } } }, - "/estimates/ppm": { - "get": { - "description": "Calculates a reimbursement range for a PPM move (excluding SIT)", - "tags": [ - "ppm" - ], - "summary": "Return a PPM cost estimate", - "operationId": "showPPMEstimate", - "parameters": [ - { - "type": "string", - "format": "date", - "name": "original_move_date", - "in": "query", - "required": true - }, - { - "pattern": "^(\\d{5}([\\-]\\d{4})?)$", - "type": "string", - "format": "zip", - "name": "origin_zip", - "in": "query", - "required": true - }, - { - "pattern": "^(\\d{5}([\\-]\\d{4})?)$", - "type": "string", - "format": "zip", - "name": "origin_duty_location_zip", - "in": "query", - "required": true - }, - { - "type": "string", - "format": "uuid", - "name": "orders_id", - "in": "query", - "required": true - }, - { - "type": "integer", - "name": "weight_estimate", - "in": "query", - "required": true - } - ], - "responses": { - "200": { - "description": "Made estimate of PPM cost range", - "schema": { - "$ref": "#/definitions/PPMEstimateRange" - } - }, - "400": { - "description": "invalid request" - }, - "401": { - "description": "request requires user authentication" - }, - "403": { - "description": "user is not authorized" - }, - "404": { - "description": "ppm discount not found for provided postal codes and original move date" - }, - "409": { - "description": "distance is less than 50 miles (no short haul moves)" - }, - "422": { - "description": "cannot process request with given information" - }, - "500": { - "description": "internal server error" - } - } - } - }, - "/estimates/ppm_sit": { - "get": { - "description": "Calculates a reimbursment for a PPM move's SIT", - "tags": [ - "ppm" - ], - "summary": "Return a PPM move's SIT cost estimate", - "operationId": "showPPMSitEstimate", - "parameters": [ - { - "type": "string", - "format": "uuid", - "name": "personally_procured_move_id", - "in": "query", - "required": true - }, - { - "type": "string", - "format": "date", - "name": "original_move_date", - "in": "query", - "required": true - }, - { - "type": "integer", - "name": "days_in_storage", - "in": "query", - "required": true - }, - { - "pattern": "^(\\d{5}([\\-]\\d{4})?)$", - "type": "string", - "format": "zip", - "name": "origin_zip", - "in": "query", - "required": true - }, - { - "type": "string", - "format": "uuid", - "name": "orders_id", - "in": "query", - "required": true - }, - { - "type": "integer", - "name": "weight_estimate", - "in": "query", - "required": true - } - ], - "responses": { - "200": { - "description": "show PPM SIT estimate", - "schema": { - "$ref": "#/definitions/PPMSitEstimate" - } - }, - "400": { - "description": "invalid request" - }, - "401": { - "description": "request requires user authentication" - }, - "403": { - "description": "user is not authorized" - }, - "409": { - "description": "distance is less than 50 miles (no short haul moves)" - }, - "422": { - "description": "the payload was unprocessable" - }, - "500": { - "description": "internal server error" - } - } - } - }, "/feature-flags/user-boolean/{key}": { "post": { "description": "Determines if a user has a feature flag enabled. The flagContext contains context used to determine if this flag applies to the logged in user.", @@ -4241,6 +4085,7 @@ func init() { "submittedAt": { "type": "string", "format": "date-time", + "x-nullable": true, "readOnly": true }, "updatedAt": { @@ -5385,7 +5230,7 @@ func init() { "x-nullable": true, "$ref": "#/definitions/DutyLocationPayload" }, - "provides_services_counseling": { + "providesServicesCounseling": { "type": "boolean", "x-omitempty": false }, @@ -5864,18 +5709,6 @@ func init() { ], "readOnly": true }, - "PPMSitEstimate": { - "type": "object", - "required": [ - "estimate" - ], - "properties": { - "estimate": { - "type": "integer", - "title": "Value in cents of SIT estimate for PPM" - } - } - }, "PatchMovePayload": { "type": "object", "required": [ @@ -7868,162 +7701,6 @@ func init() { } } }, - "/estimates/ppm": { - "get": { - "description": "Calculates a reimbursement range for a PPM move (excluding SIT)", - "tags": [ - "ppm" - ], - "summary": "Return a PPM cost estimate", - "operationId": "showPPMEstimate", - "parameters": [ - { - "type": "string", - "format": "date", - "name": "original_move_date", - "in": "query", - "required": true - }, - { - "pattern": "^(\\d{5}([\\-]\\d{4})?)$", - "type": "string", - "format": "zip", - "name": "origin_zip", - "in": "query", - "required": true - }, - { - "pattern": "^(\\d{5}([\\-]\\d{4})?)$", - "type": "string", - "format": "zip", - "name": "origin_duty_location_zip", - "in": "query", - "required": true - }, - { - "type": "string", - "format": "uuid", - "name": "orders_id", - "in": "query", - "required": true - }, - { - "type": "integer", - "name": "weight_estimate", - "in": "query", - "required": true - } - ], - "responses": { - "200": { - "description": "Made estimate of PPM cost range", - "schema": { - "$ref": "#/definitions/PPMEstimateRange" - } - }, - "400": { - "description": "invalid request" - }, - "401": { - "description": "request requires user authentication" - }, - "403": { - "description": "user is not authorized" - }, - "404": { - "description": "ppm discount not found for provided postal codes and original move date" - }, - "409": { - "description": "distance is less than 50 miles (no short haul moves)" - }, - "422": { - "description": "cannot process request with given information" - }, - "500": { - "description": "internal server error" - } - } - } - }, - "/estimates/ppm_sit": { - "get": { - "description": "Calculates a reimbursment for a PPM move's SIT", - "tags": [ - "ppm" - ], - "summary": "Return a PPM move's SIT cost estimate", - "operationId": "showPPMSitEstimate", - "parameters": [ - { - "type": "string", - "format": "uuid", - "name": "personally_procured_move_id", - "in": "query", - "required": true - }, - { - "type": "string", - "format": "date", - "name": "original_move_date", - "in": "query", - "required": true - }, - { - "type": "integer", - "name": "days_in_storage", - "in": "query", - "required": true - }, - { - "pattern": "^(\\d{5}([\\-]\\d{4})?)$", - "type": "string", - "format": "zip", - "name": "origin_zip", - "in": "query", - "required": true - }, - { - "type": "string", - "format": "uuid", - "name": "orders_id", - "in": "query", - "required": true - }, - { - "type": "integer", - "name": "weight_estimate", - "in": "query", - "required": true - } - ], - "responses": { - "200": { - "description": "show PPM SIT estimate", - "schema": { - "$ref": "#/definitions/PPMSitEstimate" - } - }, - "400": { - "description": "invalid request" - }, - "401": { - "description": "request requires user authentication" - }, - "403": { - "description": "user is not authorized" - }, - "409": { - "description": "distance is less than 50 miles (no short haul moves)" - }, - "422": { - "description": "the payload was unprocessable" - }, - "500": { - "description": "internal server error" - } - } - } - }, "/feature-flags/user-boolean/{key}": { "post": { "description": "Determines if a user has a feature flag enabled. The flagContext contains context used to determine if this flag applies to the logged in user.", @@ -12112,6 +11789,7 @@ func init() { "submittedAt": { "type": "string", "format": "date-time", + "x-nullable": true, "readOnly": true }, "updatedAt": { @@ -13258,7 +12936,7 @@ func init() { "x-nullable": true, "$ref": "#/definitions/DutyLocationPayload" }, - "provides_services_counseling": { + "providesServicesCounseling": { "type": "boolean", "x-omitempty": false }, @@ -13737,18 +13415,6 @@ func init() { ], "readOnly": true }, - "PPMSitEstimate": { - "type": "object", - "required": [ - "estimate" - ], - "properties": { - "estimate": { - "type": "integer", - "title": "Value in cents of SIT estimate for PPM" - } - } - }, "PatchMovePayload": { "type": "object", "required": [ diff --git a/pkg/gen/internalapi/internaloperations/mymove_api.go b/pkg/gen/internalapi/internaloperations/mymove_api.go index 1db50cef96a..d4af0cf291c 100644 --- a/pkg/gen/internalapi/internaloperations/mymove_api.go +++ b/pkg/gen/internalapi/internaloperations/mymove_api.go @@ -204,12 +204,6 @@ func NewMymoveAPI(spec *loads.Document) *MymoveAPI { OrdersShowOrdersHandler: orders.ShowOrdersHandlerFunc(func(params orders.ShowOrdersParams) middleware.Responder { return middleware.NotImplemented("operation orders.ShowOrders has not yet been implemented") }), - PpmShowPPMEstimateHandler: ppm.ShowPPMEstimateHandlerFunc(func(params ppm.ShowPPMEstimateParams) middleware.Responder { - return middleware.NotImplemented("operation ppm.ShowPPMEstimate has not yet been implemented") - }), - PpmShowPPMSitEstimateHandler: ppm.ShowPPMSitEstimateHandlerFunc(func(params ppm.ShowPPMSitEstimateParams) middleware.Responder { - return middleware.NotImplemented("operation ppm.ShowPPMSitEstimate has not yet been implemented") - }), PpmShowPaymentPacketHandler: ppm.ShowPaymentPacketHandlerFunc(func(params ppm.ShowPaymentPacketParams) middleware.Responder { return middleware.NotImplemented("operation ppm.ShowPaymentPacket has not yet been implemented") }), @@ -406,10 +400,6 @@ type MymoveAPI struct { OktaProfileShowOktaInfoHandler okta_profile.ShowOktaInfoHandler // OrdersShowOrdersHandler sets the operation handler for the show orders operation OrdersShowOrdersHandler orders.ShowOrdersHandler - // PpmShowPPMEstimateHandler sets the operation handler for the show p p m estimate operation - PpmShowPPMEstimateHandler ppm.ShowPPMEstimateHandler - // PpmShowPPMSitEstimateHandler sets the operation handler for the show p p m sit estimate operation - PpmShowPPMSitEstimateHandler ppm.ShowPPMSitEstimateHandler // PpmShowPaymentPacketHandler sets the operation handler for the show payment packet operation PpmShowPaymentPacketHandler ppm.ShowPaymentPacketHandler // QueuesShowQueueHandler sets the operation handler for the show queue operation @@ -669,12 +659,6 @@ func (o *MymoveAPI) Validate() error { if o.OrdersShowOrdersHandler == nil { unregistered = append(unregistered, "orders.ShowOrdersHandler") } - if o.PpmShowPPMEstimateHandler == nil { - unregistered = append(unregistered, "ppm.ShowPPMEstimateHandler") - } - if o.PpmShowPPMSitEstimateHandler == nil { - unregistered = append(unregistered, "ppm.ShowPPMSitEstimateHandler") - } if o.PpmShowPaymentPacketHandler == nil { unregistered = append(unregistered, "ppm.ShowPaymentPacketHandler") } @@ -1011,14 +995,6 @@ func (o *MymoveAPI) initHandlerCache() { if o.handlers["GET"] == nil { o.handlers["GET"] = make(map[string]http.Handler) } - o.handlers["GET"]["/estimates/ppm"] = ppm.NewShowPPMEstimate(o.context, o.PpmShowPPMEstimateHandler) - if o.handlers["GET"] == nil { - o.handlers["GET"] = make(map[string]http.Handler) - } - o.handlers["GET"]["/estimates/ppm_sit"] = ppm.NewShowPPMSitEstimate(o.context, o.PpmShowPPMSitEstimateHandler) - if o.handlers["GET"] == nil { - o.handlers["GET"] = make(map[string]http.Handler) - } o.handlers["GET"]["/ppm-shipments/{ppmShipmentId}/payment-packet"] = ppm.NewShowPaymentPacket(o.context, o.PpmShowPaymentPacketHandler) if o.handlers["GET"] == nil { o.handlers["GET"] = make(map[string]http.Handler) diff --git a/pkg/gen/internalapi/internaloperations/ppm/show_p_p_m_estimate.go b/pkg/gen/internalapi/internaloperations/ppm/show_p_p_m_estimate.go deleted file mode 100644 index 1a57272dadd..00000000000 --- a/pkg/gen/internalapi/internaloperations/ppm/show_p_p_m_estimate.go +++ /dev/null @@ -1,58 +0,0 @@ -// Code generated by go-swagger; DO NOT EDIT. - -package ppm - -// 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" -) - -// ShowPPMEstimateHandlerFunc turns a function with the right signature into a show p p m estimate handler -type ShowPPMEstimateHandlerFunc func(ShowPPMEstimateParams) middleware.Responder - -// Handle executing the request and returning a response -func (fn ShowPPMEstimateHandlerFunc) Handle(params ShowPPMEstimateParams) middleware.Responder { - return fn(params) -} - -// ShowPPMEstimateHandler interface for that can handle valid show p p m estimate params -type ShowPPMEstimateHandler interface { - Handle(ShowPPMEstimateParams) middleware.Responder -} - -// NewShowPPMEstimate creates a new http.Handler for the show p p m estimate operation -func NewShowPPMEstimate(ctx *middleware.Context, handler ShowPPMEstimateHandler) *ShowPPMEstimate { - return &ShowPPMEstimate{Context: ctx, Handler: handler} -} - -/* - ShowPPMEstimate swagger:route GET /estimates/ppm ppm showPPMEstimate - -# Return a PPM cost estimate - -Calculates a reimbursement range for a PPM move (excluding SIT) -*/ -type ShowPPMEstimate struct { - Context *middleware.Context - Handler ShowPPMEstimateHandler -} - -func (o *ShowPPMEstimate) ServeHTTP(rw http.ResponseWriter, r *http.Request) { - route, rCtx, _ := o.Context.RouteInfo(r) - if rCtx != nil { - *r = *rCtx - } - var Params = NewShowPPMEstimateParams() - 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/internalapi/internaloperations/ppm/show_p_p_m_estimate_parameters.go b/pkg/gen/internalapi/internaloperations/ppm/show_p_p_m_estimate_parameters.go deleted file mode 100644 index fa42633ac37..00000000000 --- a/pkg/gen/internalapi/internaloperations/ppm/show_p_p_m_estimate_parameters.go +++ /dev/null @@ -1,280 +0,0 @@ -// Code generated by go-swagger; DO NOT EDIT. - -package ppm - -// This file was generated by the swagger tool. -// Editing this file might prove futile when you re-run the swagger generate command - -import ( - "net/http" - - "github.com/go-openapi/errors" - "github.com/go-openapi/runtime" - "github.com/go-openapi/runtime/middleware" - "github.com/go-openapi/strfmt" - "github.com/go-openapi/swag" - "github.com/go-openapi/validate" -) - -// NewShowPPMEstimateParams creates a new ShowPPMEstimateParams object -// -// There are no default values defined in the spec. -func NewShowPPMEstimateParams() ShowPPMEstimateParams { - - return ShowPPMEstimateParams{} -} - -// ShowPPMEstimateParams contains all the bound params for the show p p m estimate operation -// typically these are obtained from a http.Request -// -// swagger:parameters showPPMEstimate -type ShowPPMEstimateParams struct { - - // HTTP Request Object - HTTPRequest *http.Request `json:"-"` - - /* - Required: true - In: query - */ - OrdersID strfmt.UUID - /* - Required: true - Pattern: ^(\d{5}([\-]\d{4})?)$ - In: query - */ - OriginDutyLocationZip string - /* - Required: true - Pattern: ^(\d{5}([\-]\d{4})?)$ - In: query - */ - OriginZip string - /* - Required: true - In: query - */ - OriginalMoveDate strfmt.Date - /* - Required: true - In: query - */ - WeightEstimate int64 -} - -// 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 NewShowPPMEstimateParams() beforehand. -func (o *ShowPPMEstimateParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { - var res []error - - o.HTTPRequest = r - - qs := runtime.Values(r.URL.Query()) - - qOrdersID, qhkOrdersID, _ := qs.GetOK("orders_id") - if err := o.bindOrdersID(qOrdersID, qhkOrdersID, route.Formats); err != nil { - res = append(res, err) - } - - qOriginDutyLocationZip, qhkOriginDutyLocationZip, _ := qs.GetOK("origin_duty_location_zip") - if err := o.bindOriginDutyLocationZip(qOriginDutyLocationZip, qhkOriginDutyLocationZip, route.Formats); err != nil { - res = append(res, err) - } - - qOriginZip, qhkOriginZip, _ := qs.GetOK("origin_zip") - if err := o.bindOriginZip(qOriginZip, qhkOriginZip, route.Formats); err != nil { - res = append(res, err) - } - - qOriginalMoveDate, qhkOriginalMoveDate, _ := qs.GetOK("original_move_date") - if err := o.bindOriginalMoveDate(qOriginalMoveDate, qhkOriginalMoveDate, route.Formats); err != nil { - res = append(res, err) - } - - qWeightEstimate, qhkWeightEstimate, _ := qs.GetOK("weight_estimate") - if err := o.bindWeightEstimate(qWeightEstimate, qhkWeightEstimate, route.Formats); err != nil { - res = append(res, err) - } - if len(res) > 0 { - return errors.CompositeValidationError(res...) - } - return nil -} - -// bindOrdersID binds and validates parameter OrdersID from query. -func (o *ShowPPMEstimateParams) bindOrdersID(rawData []string, hasKey bool, formats strfmt.Registry) error { - if !hasKey { - return errors.Required("orders_id", "query", rawData) - } - var raw string - if len(rawData) > 0 { - raw = rawData[len(rawData)-1] - } - - // Required: true - // AllowEmptyValue: false - - if err := validate.RequiredString("orders_id", "query", raw); err != nil { - return err - } - - // Format: uuid - value, err := formats.Parse("uuid", raw) - if err != nil { - return errors.InvalidType("orders_id", "query", "strfmt.UUID", raw) - } - o.OrdersID = *(value.(*strfmt.UUID)) - - if err := o.validateOrdersID(formats); err != nil { - return err - } - - return nil -} - -// validateOrdersID carries on validations for parameter OrdersID -func (o *ShowPPMEstimateParams) validateOrdersID(formats strfmt.Registry) error { - - if err := validate.FormatOf("orders_id", "query", "uuid", o.OrdersID.String(), formats); err != nil { - return err - } - return nil -} - -// bindOriginDutyLocationZip binds and validates parameter OriginDutyLocationZip from query. -func (o *ShowPPMEstimateParams) bindOriginDutyLocationZip(rawData []string, hasKey bool, formats strfmt.Registry) error { - if !hasKey { - return errors.Required("origin_duty_location_zip", "query", rawData) - } - var raw string - if len(rawData) > 0 { - raw = rawData[len(rawData)-1] - } - - // Required: true - // AllowEmptyValue: false - - if err := validate.RequiredString("origin_duty_location_zip", "query", raw); err != nil { - return err - } - o.OriginDutyLocationZip = raw - - if err := o.validateOriginDutyLocationZip(formats); err != nil { - return err - } - - return nil -} - -// validateOriginDutyLocationZip carries on validations for parameter OriginDutyLocationZip -func (o *ShowPPMEstimateParams) validateOriginDutyLocationZip(formats strfmt.Registry) error { - - if err := validate.Pattern("origin_duty_location_zip", "query", o.OriginDutyLocationZip, `^(\d{5}([\-]\d{4})?)$`); err != nil { - return err - } - - return nil -} - -// bindOriginZip binds and validates parameter OriginZip from query. -func (o *ShowPPMEstimateParams) bindOriginZip(rawData []string, hasKey bool, formats strfmt.Registry) error { - if !hasKey { - return errors.Required("origin_zip", "query", rawData) - } - var raw string - if len(rawData) > 0 { - raw = rawData[len(rawData)-1] - } - - // Required: true - // AllowEmptyValue: false - - if err := validate.RequiredString("origin_zip", "query", raw); err != nil { - return err - } - o.OriginZip = raw - - if err := o.validateOriginZip(formats); err != nil { - return err - } - - return nil -} - -// validateOriginZip carries on validations for parameter OriginZip -func (o *ShowPPMEstimateParams) validateOriginZip(formats strfmt.Registry) error { - - if err := validate.Pattern("origin_zip", "query", o.OriginZip, `^(\d{5}([\-]\d{4})?)$`); err != nil { - return err - } - - return nil -} - -// bindOriginalMoveDate binds and validates parameter OriginalMoveDate from query. -func (o *ShowPPMEstimateParams) bindOriginalMoveDate(rawData []string, hasKey bool, formats strfmt.Registry) error { - if !hasKey { - return errors.Required("original_move_date", "query", rawData) - } - var raw string - if len(rawData) > 0 { - raw = rawData[len(rawData)-1] - } - - // Required: true - // AllowEmptyValue: false - - if err := validate.RequiredString("original_move_date", "query", raw); err != nil { - return err - } - - // Format: date - value, err := formats.Parse("date", raw) - if err != nil { - return errors.InvalidType("original_move_date", "query", "strfmt.Date", raw) - } - o.OriginalMoveDate = *(value.(*strfmt.Date)) - - if err := o.validateOriginalMoveDate(formats); err != nil { - return err - } - - return nil -} - -// validateOriginalMoveDate carries on validations for parameter OriginalMoveDate -func (o *ShowPPMEstimateParams) validateOriginalMoveDate(formats strfmt.Registry) error { - - if err := validate.FormatOf("original_move_date", "query", "date", o.OriginalMoveDate.String(), formats); err != nil { - return err - } - return nil -} - -// bindWeightEstimate binds and validates parameter WeightEstimate from query. -func (o *ShowPPMEstimateParams) bindWeightEstimate(rawData []string, hasKey bool, formats strfmt.Registry) error { - if !hasKey { - return errors.Required("weight_estimate", "query", rawData) - } - var raw string - if len(rawData) > 0 { - raw = rawData[len(rawData)-1] - } - - // Required: true - // AllowEmptyValue: false - - if err := validate.RequiredString("weight_estimate", "query", raw); err != nil { - return err - } - - value, err := swag.ConvertInt64(raw) - if err != nil { - return errors.InvalidType("weight_estimate", "query", "int64", raw) - } - o.WeightEstimate = value - - return nil -} diff --git a/pkg/gen/internalapi/internaloperations/ppm/show_p_p_m_estimate_responses.go b/pkg/gen/internalapi/internaloperations/ppm/show_p_p_m_estimate_responses.go deleted file mode 100644 index 7551bc0c319..00000000000 --- a/pkg/gen/internalapi/internaloperations/ppm/show_p_p_m_estimate_responses.go +++ /dev/null @@ -1,234 +0,0 @@ -// Code generated by go-swagger; DO NOT EDIT. - -package ppm - -// 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/internalmessages" -) - -// ShowPPMEstimateOKCode is the HTTP code returned for type ShowPPMEstimateOK -const ShowPPMEstimateOKCode int = 200 - -/* -ShowPPMEstimateOK Made estimate of PPM cost range - -swagger:response showPPMEstimateOK -*/ -type ShowPPMEstimateOK struct { - - /* - In: Body - */ - Payload *internalmessages.PPMEstimateRange `json:"body,omitempty"` -} - -// NewShowPPMEstimateOK creates ShowPPMEstimateOK with default headers values -func NewShowPPMEstimateOK() *ShowPPMEstimateOK { - - return &ShowPPMEstimateOK{} -} - -// WithPayload adds the payload to the show p p m estimate o k response -func (o *ShowPPMEstimateOK) WithPayload(payload *internalmessages.PPMEstimateRange) *ShowPPMEstimateOK { - o.Payload = payload - return o -} - -// SetPayload sets the payload to the show p p m estimate o k response -func (o *ShowPPMEstimateOK) SetPayload(payload *internalmessages.PPMEstimateRange) { - o.Payload = payload -} - -// WriteResponse to the client -func (o *ShowPPMEstimateOK) 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 - } - } -} - -// ShowPPMEstimateBadRequestCode is the HTTP code returned for type ShowPPMEstimateBadRequest -const ShowPPMEstimateBadRequestCode int = 400 - -/* -ShowPPMEstimateBadRequest invalid request - -swagger:response showPPMEstimateBadRequest -*/ -type ShowPPMEstimateBadRequest struct { -} - -// NewShowPPMEstimateBadRequest creates ShowPPMEstimateBadRequest with default headers values -func NewShowPPMEstimateBadRequest() *ShowPPMEstimateBadRequest { - - return &ShowPPMEstimateBadRequest{} -} - -// WriteResponse to the client -func (o *ShowPPMEstimateBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { - - rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses - - rw.WriteHeader(400) -} - -// ShowPPMEstimateUnauthorizedCode is the HTTP code returned for type ShowPPMEstimateUnauthorized -const ShowPPMEstimateUnauthorizedCode int = 401 - -/* -ShowPPMEstimateUnauthorized request requires user authentication - -swagger:response showPPMEstimateUnauthorized -*/ -type ShowPPMEstimateUnauthorized struct { -} - -// NewShowPPMEstimateUnauthorized creates ShowPPMEstimateUnauthorized with default headers values -func NewShowPPMEstimateUnauthorized() *ShowPPMEstimateUnauthorized { - - return &ShowPPMEstimateUnauthorized{} -} - -// WriteResponse to the client -func (o *ShowPPMEstimateUnauthorized) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { - - rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses - - rw.WriteHeader(401) -} - -// ShowPPMEstimateForbiddenCode is the HTTP code returned for type ShowPPMEstimateForbidden -const ShowPPMEstimateForbiddenCode int = 403 - -/* -ShowPPMEstimateForbidden user is not authorized - -swagger:response showPPMEstimateForbidden -*/ -type ShowPPMEstimateForbidden struct { -} - -// NewShowPPMEstimateForbidden creates ShowPPMEstimateForbidden with default headers values -func NewShowPPMEstimateForbidden() *ShowPPMEstimateForbidden { - - return &ShowPPMEstimateForbidden{} -} - -// WriteResponse to the client -func (o *ShowPPMEstimateForbidden) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { - - rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses - - rw.WriteHeader(403) -} - -// ShowPPMEstimateNotFoundCode is the HTTP code returned for type ShowPPMEstimateNotFound -const ShowPPMEstimateNotFoundCode int = 404 - -/* -ShowPPMEstimateNotFound ppm discount not found for provided postal codes and original move date - -swagger:response showPPMEstimateNotFound -*/ -type ShowPPMEstimateNotFound struct { -} - -// NewShowPPMEstimateNotFound creates ShowPPMEstimateNotFound with default headers values -func NewShowPPMEstimateNotFound() *ShowPPMEstimateNotFound { - - return &ShowPPMEstimateNotFound{} -} - -// WriteResponse to the client -func (o *ShowPPMEstimateNotFound) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { - - rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses - - rw.WriteHeader(404) -} - -// ShowPPMEstimateConflictCode is the HTTP code returned for type ShowPPMEstimateConflict -const ShowPPMEstimateConflictCode int = 409 - -/* -ShowPPMEstimateConflict distance is less than 50 miles (no short haul moves) - -swagger:response showPPMEstimateConflict -*/ -type ShowPPMEstimateConflict struct { -} - -// NewShowPPMEstimateConflict creates ShowPPMEstimateConflict with default headers values -func NewShowPPMEstimateConflict() *ShowPPMEstimateConflict { - - return &ShowPPMEstimateConflict{} -} - -// WriteResponse to the client -func (o *ShowPPMEstimateConflict) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { - - rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses - - rw.WriteHeader(409) -} - -// ShowPPMEstimateUnprocessableEntityCode is the HTTP code returned for type ShowPPMEstimateUnprocessableEntity -const ShowPPMEstimateUnprocessableEntityCode int = 422 - -/* -ShowPPMEstimateUnprocessableEntity cannot process request with given information - -swagger:response showPPMEstimateUnprocessableEntity -*/ -type ShowPPMEstimateUnprocessableEntity struct { -} - -// NewShowPPMEstimateUnprocessableEntity creates ShowPPMEstimateUnprocessableEntity with default headers values -func NewShowPPMEstimateUnprocessableEntity() *ShowPPMEstimateUnprocessableEntity { - - return &ShowPPMEstimateUnprocessableEntity{} -} - -// WriteResponse to the client -func (o *ShowPPMEstimateUnprocessableEntity) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { - - rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses - - rw.WriteHeader(422) -} - -// ShowPPMEstimateInternalServerErrorCode is the HTTP code returned for type ShowPPMEstimateInternalServerError -const ShowPPMEstimateInternalServerErrorCode int = 500 - -/* -ShowPPMEstimateInternalServerError internal server error - -swagger:response showPPMEstimateInternalServerError -*/ -type ShowPPMEstimateInternalServerError struct { -} - -// NewShowPPMEstimateInternalServerError creates ShowPPMEstimateInternalServerError with default headers values -func NewShowPPMEstimateInternalServerError() *ShowPPMEstimateInternalServerError { - - return &ShowPPMEstimateInternalServerError{} -} - -// WriteResponse to the client -func (o *ShowPPMEstimateInternalServerError) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { - - rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses - - rw.WriteHeader(500) -} diff --git a/pkg/gen/internalapi/internaloperations/ppm/show_p_p_m_estimate_urlbuilder.go b/pkg/gen/internalapi/internaloperations/ppm/show_p_p_m_estimate_urlbuilder.go deleted file mode 100644 index 1b9d8920308..00000000000 --- a/pkg/gen/internalapi/internaloperations/ppm/show_p_p_m_estimate_urlbuilder.go +++ /dev/null @@ -1,127 +0,0 @@ -// Code generated by go-swagger; DO NOT EDIT. - -package ppm - -// 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" -) - -// ShowPPMEstimateURL generates an URL for the show p p m estimate operation -type ShowPPMEstimateURL struct { - OrdersID strfmt.UUID - OriginDutyLocationZip string - OriginZip string - OriginalMoveDate strfmt.Date - WeightEstimate int64 - - _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 *ShowPPMEstimateURL) WithBasePath(bp string) *ShowPPMEstimateURL { - 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 *ShowPPMEstimateURL) SetBasePath(bp string) { - o._basePath = bp -} - -// Build a url path and query string -func (o *ShowPPMEstimateURL) Build() (*url.URL, error) { - var _result url.URL - - var _path = "/estimates/ppm" - - _basePath := o._basePath - if _basePath == "" { - _basePath = "/internal" - } - _result.Path = golangswaggerpaths.Join(_basePath, _path) - - qs := make(url.Values) - - ordersIDQ := o.OrdersID.String() - if ordersIDQ != "" { - qs.Set("orders_id", ordersIDQ) - } - - originDutyLocationZipQ := o.OriginDutyLocationZip - if originDutyLocationZipQ != "" { - qs.Set("origin_duty_location_zip", originDutyLocationZipQ) - } - - originZipQ := o.OriginZip - if originZipQ != "" { - qs.Set("origin_zip", originZipQ) - } - - originalMoveDateQ := o.OriginalMoveDate.String() - if originalMoveDateQ != "" { - qs.Set("original_move_date", originalMoveDateQ) - } - - weightEstimateQ := swag.FormatInt64(o.WeightEstimate) - if weightEstimateQ != "" { - qs.Set("weight_estimate", weightEstimateQ) - } - - _result.RawQuery = qs.Encode() - - return &_result, nil -} - -// Must is a helper function to panic when the url builder returns an error -func (o *ShowPPMEstimateURL) 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 *ShowPPMEstimateURL) String() string { - return o.Must(o.Build()).String() -} - -// BuildFull builds a full url with scheme, host, path and query string -func (o *ShowPPMEstimateURL) BuildFull(scheme, host string) (*url.URL, error) { - if scheme == "" { - return nil, errors.New("scheme is required for a full url on ShowPPMEstimateURL") - } - if host == "" { - return nil, errors.New("host is required for a full url on ShowPPMEstimateURL") - } - - 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 *ShowPPMEstimateURL) StringFull(scheme, host string) string { - return o.Must(o.BuildFull(scheme, host)).String() -} diff --git a/pkg/gen/internalapi/internaloperations/ppm/show_p_p_m_sit_estimate.go b/pkg/gen/internalapi/internaloperations/ppm/show_p_p_m_sit_estimate.go deleted file mode 100644 index 8064eed6f73..00000000000 --- a/pkg/gen/internalapi/internaloperations/ppm/show_p_p_m_sit_estimate.go +++ /dev/null @@ -1,58 +0,0 @@ -// Code generated by go-swagger; DO NOT EDIT. - -package ppm - -// 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" -) - -// ShowPPMSitEstimateHandlerFunc turns a function with the right signature into a show p p m sit estimate handler -type ShowPPMSitEstimateHandlerFunc func(ShowPPMSitEstimateParams) middleware.Responder - -// Handle executing the request and returning a response -func (fn ShowPPMSitEstimateHandlerFunc) Handle(params ShowPPMSitEstimateParams) middleware.Responder { - return fn(params) -} - -// ShowPPMSitEstimateHandler interface for that can handle valid show p p m sit estimate params -type ShowPPMSitEstimateHandler interface { - Handle(ShowPPMSitEstimateParams) middleware.Responder -} - -// NewShowPPMSitEstimate creates a new http.Handler for the show p p m sit estimate operation -func NewShowPPMSitEstimate(ctx *middleware.Context, handler ShowPPMSitEstimateHandler) *ShowPPMSitEstimate { - return &ShowPPMSitEstimate{Context: ctx, Handler: handler} -} - -/* - ShowPPMSitEstimate swagger:route GET /estimates/ppm_sit ppm showPPMSitEstimate - -# Return a PPM move's SIT cost estimate - -Calculates a reimbursment for a PPM move's SIT -*/ -type ShowPPMSitEstimate struct { - Context *middleware.Context - Handler ShowPPMSitEstimateHandler -} - -func (o *ShowPPMSitEstimate) ServeHTTP(rw http.ResponseWriter, r *http.Request) { - route, rCtx, _ := o.Context.RouteInfo(r) - if rCtx != nil { - *r = *rCtx - } - var Params = NewShowPPMSitEstimateParams() - 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/internalapi/internaloperations/ppm/show_p_p_m_sit_estimate_parameters.go b/pkg/gen/internalapi/internaloperations/ppm/show_p_p_m_sit_estimate_parameters.go deleted file mode 100644 index 24e0c96b56d..00000000000 --- a/pkg/gen/internalapi/internaloperations/ppm/show_p_p_m_sit_estimate_parameters.go +++ /dev/null @@ -1,320 +0,0 @@ -// Code generated by go-swagger; DO NOT EDIT. - -package ppm - -// This file was generated by the swagger tool. -// Editing this file might prove futile when you re-run the swagger generate command - -import ( - "net/http" - - "github.com/go-openapi/errors" - "github.com/go-openapi/runtime" - "github.com/go-openapi/runtime/middleware" - "github.com/go-openapi/strfmt" - "github.com/go-openapi/swag" - "github.com/go-openapi/validate" -) - -// NewShowPPMSitEstimateParams creates a new ShowPPMSitEstimateParams object -// -// There are no default values defined in the spec. -func NewShowPPMSitEstimateParams() ShowPPMSitEstimateParams { - - return ShowPPMSitEstimateParams{} -} - -// ShowPPMSitEstimateParams contains all the bound params for the show p p m sit estimate operation -// typically these are obtained from a http.Request -// -// swagger:parameters showPPMSitEstimate -type ShowPPMSitEstimateParams struct { - - // HTTP Request Object - HTTPRequest *http.Request `json:"-"` - - /* - Required: true - In: query - */ - DaysInStorage int64 - /* - Required: true - In: query - */ - OrdersID strfmt.UUID - /* - Required: true - Pattern: ^(\d{5}([\-]\d{4})?)$ - In: query - */ - OriginZip string - /* - Required: true - In: query - */ - OriginalMoveDate strfmt.Date - /* - Required: true - In: query - */ - PersonallyProcuredMoveID strfmt.UUID - /* - Required: true - In: query - */ - WeightEstimate int64 -} - -// 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 NewShowPPMSitEstimateParams() beforehand. -func (o *ShowPPMSitEstimateParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { - var res []error - - o.HTTPRequest = r - - qs := runtime.Values(r.URL.Query()) - - qDaysInStorage, qhkDaysInStorage, _ := qs.GetOK("days_in_storage") - if err := o.bindDaysInStorage(qDaysInStorage, qhkDaysInStorage, route.Formats); err != nil { - res = append(res, err) - } - - qOrdersID, qhkOrdersID, _ := qs.GetOK("orders_id") - if err := o.bindOrdersID(qOrdersID, qhkOrdersID, route.Formats); err != nil { - res = append(res, err) - } - - qOriginZip, qhkOriginZip, _ := qs.GetOK("origin_zip") - if err := o.bindOriginZip(qOriginZip, qhkOriginZip, route.Formats); err != nil { - res = append(res, err) - } - - qOriginalMoveDate, qhkOriginalMoveDate, _ := qs.GetOK("original_move_date") - if err := o.bindOriginalMoveDate(qOriginalMoveDate, qhkOriginalMoveDate, route.Formats); err != nil { - res = append(res, err) - } - - qPersonallyProcuredMoveID, qhkPersonallyProcuredMoveID, _ := qs.GetOK("personally_procured_move_id") - if err := o.bindPersonallyProcuredMoveID(qPersonallyProcuredMoveID, qhkPersonallyProcuredMoveID, route.Formats); err != nil { - res = append(res, err) - } - - qWeightEstimate, qhkWeightEstimate, _ := qs.GetOK("weight_estimate") - if err := o.bindWeightEstimate(qWeightEstimate, qhkWeightEstimate, route.Formats); err != nil { - res = append(res, err) - } - if len(res) > 0 { - return errors.CompositeValidationError(res...) - } - return nil -} - -// bindDaysInStorage binds and validates parameter DaysInStorage from query. -func (o *ShowPPMSitEstimateParams) bindDaysInStorage(rawData []string, hasKey bool, formats strfmt.Registry) error { - if !hasKey { - return errors.Required("days_in_storage", "query", rawData) - } - var raw string - if len(rawData) > 0 { - raw = rawData[len(rawData)-1] - } - - // Required: true - // AllowEmptyValue: false - - if err := validate.RequiredString("days_in_storage", "query", raw); err != nil { - return err - } - - value, err := swag.ConvertInt64(raw) - if err != nil { - return errors.InvalidType("days_in_storage", "query", "int64", raw) - } - o.DaysInStorage = value - - return nil -} - -// bindOrdersID binds and validates parameter OrdersID from query. -func (o *ShowPPMSitEstimateParams) bindOrdersID(rawData []string, hasKey bool, formats strfmt.Registry) error { - if !hasKey { - return errors.Required("orders_id", "query", rawData) - } - var raw string - if len(rawData) > 0 { - raw = rawData[len(rawData)-1] - } - - // Required: true - // AllowEmptyValue: false - - if err := validate.RequiredString("orders_id", "query", raw); err != nil { - return err - } - - // Format: uuid - value, err := formats.Parse("uuid", raw) - if err != nil { - return errors.InvalidType("orders_id", "query", "strfmt.UUID", raw) - } - o.OrdersID = *(value.(*strfmt.UUID)) - - if err := o.validateOrdersID(formats); err != nil { - return err - } - - return nil -} - -// validateOrdersID carries on validations for parameter OrdersID -func (o *ShowPPMSitEstimateParams) validateOrdersID(formats strfmt.Registry) error { - - if err := validate.FormatOf("orders_id", "query", "uuid", o.OrdersID.String(), formats); err != nil { - return err - } - return nil -} - -// bindOriginZip binds and validates parameter OriginZip from query. -func (o *ShowPPMSitEstimateParams) bindOriginZip(rawData []string, hasKey bool, formats strfmt.Registry) error { - if !hasKey { - return errors.Required("origin_zip", "query", rawData) - } - var raw string - if len(rawData) > 0 { - raw = rawData[len(rawData)-1] - } - - // Required: true - // AllowEmptyValue: false - - if err := validate.RequiredString("origin_zip", "query", raw); err != nil { - return err - } - o.OriginZip = raw - - if err := o.validateOriginZip(formats); err != nil { - return err - } - - return nil -} - -// validateOriginZip carries on validations for parameter OriginZip -func (o *ShowPPMSitEstimateParams) validateOriginZip(formats strfmt.Registry) error { - - if err := validate.Pattern("origin_zip", "query", o.OriginZip, `^(\d{5}([\-]\d{4})?)$`); err != nil { - return err - } - - return nil -} - -// bindOriginalMoveDate binds and validates parameter OriginalMoveDate from query. -func (o *ShowPPMSitEstimateParams) bindOriginalMoveDate(rawData []string, hasKey bool, formats strfmt.Registry) error { - if !hasKey { - return errors.Required("original_move_date", "query", rawData) - } - var raw string - if len(rawData) > 0 { - raw = rawData[len(rawData)-1] - } - - // Required: true - // AllowEmptyValue: false - - if err := validate.RequiredString("original_move_date", "query", raw); err != nil { - return err - } - - // Format: date - value, err := formats.Parse("date", raw) - if err != nil { - return errors.InvalidType("original_move_date", "query", "strfmt.Date", raw) - } - o.OriginalMoveDate = *(value.(*strfmt.Date)) - - if err := o.validateOriginalMoveDate(formats); err != nil { - return err - } - - return nil -} - -// validateOriginalMoveDate carries on validations for parameter OriginalMoveDate -func (o *ShowPPMSitEstimateParams) validateOriginalMoveDate(formats strfmt.Registry) error { - - if err := validate.FormatOf("original_move_date", "query", "date", o.OriginalMoveDate.String(), formats); err != nil { - return err - } - return nil -} - -// bindPersonallyProcuredMoveID binds and validates parameter PersonallyProcuredMoveID from query. -func (o *ShowPPMSitEstimateParams) bindPersonallyProcuredMoveID(rawData []string, hasKey bool, formats strfmt.Registry) error { - if !hasKey { - return errors.Required("personally_procured_move_id", "query", rawData) - } - var raw string - if len(rawData) > 0 { - raw = rawData[len(rawData)-1] - } - - // Required: true - // AllowEmptyValue: false - - if err := validate.RequiredString("personally_procured_move_id", "query", raw); err != nil { - return err - } - - // Format: uuid - value, err := formats.Parse("uuid", raw) - if err != nil { - return errors.InvalidType("personally_procured_move_id", "query", "strfmt.UUID", raw) - } - o.PersonallyProcuredMoveID = *(value.(*strfmt.UUID)) - - if err := o.validatePersonallyProcuredMoveID(formats); err != nil { - return err - } - - return nil -} - -// validatePersonallyProcuredMoveID carries on validations for parameter PersonallyProcuredMoveID -func (o *ShowPPMSitEstimateParams) validatePersonallyProcuredMoveID(formats strfmt.Registry) error { - - if err := validate.FormatOf("personally_procured_move_id", "query", "uuid", o.PersonallyProcuredMoveID.String(), formats); err != nil { - return err - } - return nil -} - -// bindWeightEstimate binds and validates parameter WeightEstimate from query. -func (o *ShowPPMSitEstimateParams) bindWeightEstimate(rawData []string, hasKey bool, formats strfmt.Registry) error { - if !hasKey { - return errors.Required("weight_estimate", "query", rawData) - } - var raw string - if len(rawData) > 0 { - raw = rawData[len(rawData)-1] - } - - // Required: true - // AllowEmptyValue: false - - if err := validate.RequiredString("weight_estimate", "query", raw); err != nil { - return err - } - - value, err := swag.ConvertInt64(raw) - if err != nil { - return errors.InvalidType("weight_estimate", "query", "int64", raw) - } - o.WeightEstimate = value - - return nil -} diff --git a/pkg/gen/internalapi/internaloperations/ppm/show_p_p_m_sit_estimate_responses.go b/pkg/gen/internalapi/internaloperations/ppm/show_p_p_m_sit_estimate_responses.go deleted file mode 100644 index acff9c1faa3..00000000000 --- a/pkg/gen/internalapi/internaloperations/ppm/show_p_p_m_sit_estimate_responses.go +++ /dev/null @@ -1,209 +0,0 @@ -// Code generated by go-swagger; DO NOT EDIT. - -package ppm - -// 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/internalmessages" -) - -// ShowPPMSitEstimateOKCode is the HTTP code returned for type ShowPPMSitEstimateOK -const ShowPPMSitEstimateOKCode int = 200 - -/* -ShowPPMSitEstimateOK show PPM SIT estimate - -swagger:response showPPMSitEstimateOK -*/ -type ShowPPMSitEstimateOK struct { - - /* - In: Body - */ - Payload *internalmessages.PPMSitEstimate `json:"body,omitempty"` -} - -// NewShowPPMSitEstimateOK creates ShowPPMSitEstimateOK with default headers values -func NewShowPPMSitEstimateOK() *ShowPPMSitEstimateOK { - - return &ShowPPMSitEstimateOK{} -} - -// WithPayload adds the payload to the show p p m sit estimate o k response -func (o *ShowPPMSitEstimateOK) WithPayload(payload *internalmessages.PPMSitEstimate) *ShowPPMSitEstimateOK { - o.Payload = payload - return o -} - -// SetPayload sets the payload to the show p p m sit estimate o k response -func (o *ShowPPMSitEstimateOK) SetPayload(payload *internalmessages.PPMSitEstimate) { - o.Payload = payload -} - -// WriteResponse to the client -func (o *ShowPPMSitEstimateOK) 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 - } - } -} - -// ShowPPMSitEstimateBadRequestCode is the HTTP code returned for type ShowPPMSitEstimateBadRequest -const ShowPPMSitEstimateBadRequestCode int = 400 - -/* -ShowPPMSitEstimateBadRequest invalid request - -swagger:response showPPMSitEstimateBadRequest -*/ -type ShowPPMSitEstimateBadRequest struct { -} - -// NewShowPPMSitEstimateBadRequest creates ShowPPMSitEstimateBadRequest with default headers values -func NewShowPPMSitEstimateBadRequest() *ShowPPMSitEstimateBadRequest { - - return &ShowPPMSitEstimateBadRequest{} -} - -// WriteResponse to the client -func (o *ShowPPMSitEstimateBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { - - rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses - - rw.WriteHeader(400) -} - -// ShowPPMSitEstimateUnauthorizedCode is the HTTP code returned for type ShowPPMSitEstimateUnauthorized -const ShowPPMSitEstimateUnauthorizedCode int = 401 - -/* -ShowPPMSitEstimateUnauthorized request requires user authentication - -swagger:response showPPMSitEstimateUnauthorized -*/ -type ShowPPMSitEstimateUnauthorized struct { -} - -// NewShowPPMSitEstimateUnauthorized creates ShowPPMSitEstimateUnauthorized with default headers values -func NewShowPPMSitEstimateUnauthorized() *ShowPPMSitEstimateUnauthorized { - - return &ShowPPMSitEstimateUnauthorized{} -} - -// WriteResponse to the client -func (o *ShowPPMSitEstimateUnauthorized) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { - - rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses - - rw.WriteHeader(401) -} - -// ShowPPMSitEstimateForbiddenCode is the HTTP code returned for type ShowPPMSitEstimateForbidden -const ShowPPMSitEstimateForbiddenCode int = 403 - -/* -ShowPPMSitEstimateForbidden user is not authorized - -swagger:response showPPMSitEstimateForbidden -*/ -type ShowPPMSitEstimateForbidden struct { -} - -// NewShowPPMSitEstimateForbidden creates ShowPPMSitEstimateForbidden with default headers values -func NewShowPPMSitEstimateForbidden() *ShowPPMSitEstimateForbidden { - - return &ShowPPMSitEstimateForbidden{} -} - -// WriteResponse to the client -func (o *ShowPPMSitEstimateForbidden) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { - - rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses - - rw.WriteHeader(403) -} - -// ShowPPMSitEstimateConflictCode is the HTTP code returned for type ShowPPMSitEstimateConflict -const ShowPPMSitEstimateConflictCode int = 409 - -/* -ShowPPMSitEstimateConflict distance is less than 50 miles (no short haul moves) - -swagger:response showPPMSitEstimateConflict -*/ -type ShowPPMSitEstimateConflict struct { -} - -// NewShowPPMSitEstimateConflict creates ShowPPMSitEstimateConflict with default headers values -func NewShowPPMSitEstimateConflict() *ShowPPMSitEstimateConflict { - - return &ShowPPMSitEstimateConflict{} -} - -// WriteResponse to the client -func (o *ShowPPMSitEstimateConflict) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { - - rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses - - rw.WriteHeader(409) -} - -// ShowPPMSitEstimateUnprocessableEntityCode is the HTTP code returned for type ShowPPMSitEstimateUnprocessableEntity -const ShowPPMSitEstimateUnprocessableEntityCode int = 422 - -/* -ShowPPMSitEstimateUnprocessableEntity the payload was unprocessable - -swagger:response showPPMSitEstimateUnprocessableEntity -*/ -type ShowPPMSitEstimateUnprocessableEntity struct { -} - -// NewShowPPMSitEstimateUnprocessableEntity creates ShowPPMSitEstimateUnprocessableEntity with default headers values -func NewShowPPMSitEstimateUnprocessableEntity() *ShowPPMSitEstimateUnprocessableEntity { - - return &ShowPPMSitEstimateUnprocessableEntity{} -} - -// WriteResponse to the client -func (o *ShowPPMSitEstimateUnprocessableEntity) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { - - rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses - - rw.WriteHeader(422) -} - -// ShowPPMSitEstimateInternalServerErrorCode is the HTTP code returned for type ShowPPMSitEstimateInternalServerError -const ShowPPMSitEstimateInternalServerErrorCode int = 500 - -/* -ShowPPMSitEstimateInternalServerError internal server error - -swagger:response showPPMSitEstimateInternalServerError -*/ -type ShowPPMSitEstimateInternalServerError struct { -} - -// NewShowPPMSitEstimateInternalServerError creates ShowPPMSitEstimateInternalServerError with default headers values -func NewShowPPMSitEstimateInternalServerError() *ShowPPMSitEstimateInternalServerError { - - return &ShowPPMSitEstimateInternalServerError{} -} - -// WriteResponse to the client -func (o *ShowPPMSitEstimateInternalServerError) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { - - rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses - - rw.WriteHeader(500) -} diff --git a/pkg/gen/internalapi/internaloperations/ppm/show_p_p_m_sit_estimate_urlbuilder.go b/pkg/gen/internalapi/internaloperations/ppm/show_p_p_m_sit_estimate_urlbuilder.go deleted file mode 100644 index 56ec2a3f837..00000000000 --- a/pkg/gen/internalapi/internaloperations/ppm/show_p_p_m_sit_estimate_urlbuilder.go +++ /dev/null @@ -1,133 +0,0 @@ -// Code generated by go-swagger; DO NOT EDIT. - -package ppm - -// 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" -) - -// ShowPPMSitEstimateURL generates an URL for the show p p m sit estimate operation -type ShowPPMSitEstimateURL struct { - DaysInStorage int64 - OrdersID strfmt.UUID - OriginZip string - OriginalMoveDate strfmt.Date - PersonallyProcuredMoveID strfmt.UUID - WeightEstimate int64 - - _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 *ShowPPMSitEstimateURL) WithBasePath(bp string) *ShowPPMSitEstimateURL { - 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 *ShowPPMSitEstimateURL) SetBasePath(bp string) { - o._basePath = bp -} - -// Build a url path and query string -func (o *ShowPPMSitEstimateURL) Build() (*url.URL, error) { - var _result url.URL - - var _path = "/estimates/ppm_sit" - - _basePath := o._basePath - if _basePath == "" { - _basePath = "/internal" - } - _result.Path = golangswaggerpaths.Join(_basePath, _path) - - qs := make(url.Values) - - daysInStorageQ := swag.FormatInt64(o.DaysInStorage) - if daysInStorageQ != "" { - qs.Set("days_in_storage", daysInStorageQ) - } - - ordersIDQ := o.OrdersID.String() - if ordersIDQ != "" { - qs.Set("orders_id", ordersIDQ) - } - - originZipQ := o.OriginZip - if originZipQ != "" { - qs.Set("origin_zip", originZipQ) - } - - originalMoveDateQ := o.OriginalMoveDate.String() - if originalMoveDateQ != "" { - qs.Set("original_move_date", originalMoveDateQ) - } - - personallyProcuredMoveIDQ := o.PersonallyProcuredMoveID.String() - if personallyProcuredMoveIDQ != "" { - qs.Set("personally_procured_move_id", personallyProcuredMoveIDQ) - } - - weightEstimateQ := swag.FormatInt64(o.WeightEstimate) - if weightEstimateQ != "" { - qs.Set("weight_estimate", weightEstimateQ) - } - - _result.RawQuery = qs.Encode() - - return &_result, nil -} - -// Must is a helper function to panic when the url builder returns an error -func (o *ShowPPMSitEstimateURL) 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 *ShowPPMSitEstimateURL) String() string { - return o.Must(o.Build()).String() -} - -// BuildFull builds a full url with scheme, host, path and query string -func (o *ShowPPMSitEstimateURL) BuildFull(scheme, host string) (*url.URL, error) { - if scheme == "" { - return nil, errors.New("scheme is required for a full url on ShowPPMSitEstimateURL") - } - if host == "" { - return nil, errors.New("host is required for a full url on ShowPPMSitEstimateURL") - } - - 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 *ShowPPMSitEstimateURL) StringFull(scheme, host string) string { - return o.Must(o.BuildFull(scheme, host)).String() -} diff --git a/pkg/gen/internalmessages/internal_move.go b/pkg/gen/internalmessages/internal_move.go index bc8877c9cfd..9bdb980106a 100644 --- a/pkg/gen/internalmessages/internal_move.go +++ b/pkg/gen/internalmessages/internal_move.go @@ -59,7 +59,7 @@ type InternalMove struct { // submitted at // Read Only: true // Format: date-time - SubmittedAt strfmt.DateTime `json:"submittedAt,omitempty"` + SubmittedAt *strfmt.DateTime `json:"submittedAt,omitempty"` // updated at // Read Only: true @@ -316,7 +316,7 @@ func (m *InternalMove) contextValidateStatus(ctx context.Context, formats strfmt func (m *InternalMove) contextValidateSubmittedAt(ctx context.Context, formats strfmt.Registry) error { - if err := validate.ReadOnly(ctx, "submittedAt", "body", strfmt.DateTime(m.SubmittedAt)); err != nil { + if err := validate.ReadOnly(ctx, "submittedAt", "body", m.SubmittedAt); err != nil { return err } diff --git a/pkg/gen/internalmessages/orders.go b/pkg/gen/internalmessages/orders.go index de3932165fd..6a789163d32 100644 --- a/pkg/gen/internalmessages/orders.go +++ b/pkg/gen/internalmessages/orders.go @@ -80,7 +80,7 @@ type Orders struct { OriginDutyLocation *DutyLocationPayload `json:"origin_duty_location,omitempty"` // provides services counseling - ProvidesServicesCounseling bool `json:"provides_services_counseling"` + ProvidesServicesCounseling bool `json:"providesServicesCounseling"` // Report by // diff --git a/pkg/gen/internalmessages/p_p_m_sit_estimate.go b/pkg/gen/internalmessages/p_p_m_sit_estimate.go deleted file mode 100644 index ea11ef12b4b..00000000000 --- a/pkg/gen/internalmessages/p_p_m_sit_estimate.go +++ /dev/null @@ -1,71 +0,0 @@ -// Code generated by go-swagger; DO NOT EDIT. - -package internalmessages - -// This file was generated by the swagger tool. -// Editing this file might prove futile when you re-run the swagger generate command - -import ( - "context" - - "github.com/go-openapi/errors" - "github.com/go-openapi/strfmt" - "github.com/go-openapi/swag" - "github.com/go-openapi/validate" -) - -// PPMSitEstimate p p m sit estimate -// -// swagger:model PPMSitEstimate -type PPMSitEstimate struct { - - // Value in cents of SIT estimate for PPM - // Required: true - Estimate *int64 `json:"estimate"` -} - -// Validate validates this p p m sit estimate -func (m *PPMSitEstimate) Validate(formats strfmt.Registry) error { - var res []error - - if err := m.validateEstimate(formats); err != nil { - res = append(res, err) - } - - if len(res) > 0 { - return errors.CompositeValidationError(res...) - } - return nil -} - -func (m *PPMSitEstimate) validateEstimate(formats strfmt.Registry) error { - - if err := validate.Required("estimate", "body", m.Estimate); err != nil { - return err - } - - return nil -} - -// ContextValidate validates this p p m sit estimate based on context it is used -func (m *PPMSitEstimate) ContextValidate(ctx context.Context, formats strfmt.Registry) error { - return nil -} - -// MarshalBinary interface implementation -func (m *PPMSitEstimate) MarshalBinary() ([]byte, error) { - if m == nil { - return nil, nil - } - return swag.WriteJSON(m) -} - -// UnmarshalBinary interface implementation -func (m *PPMSitEstimate) UnmarshalBinary(b []byte) error { - var res PPMSitEstimate - if err := swag.ReadJSON(b, &res); err != nil { - return err - } - *m = res - return nil -} diff --git a/pkg/handlers/adminapi/office_users.go b/pkg/handlers/adminapi/office_users.go index 438bd7bf2b3..573b09950bb 100644 --- a/pkg/handlers/adminapi/office_users.go +++ b/pkg/handlers/adminapi/office_users.go @@ -215,13 +215,6 @@ func (h CreateOfficeUserHandler) Handle(params officeuserop.CreateOfficeUserPara // if the user is being manually created, then we know they will already be approved officeUserStatus := "APPROVED" - updatedPrivileges := privilegesPayloadToModel(payload.Privileges) - if len(updatedPrivileges) == 0 { - err = apperror.NewBadDataError("No privileges were matched from payload") - appCtx.Logger().Error(err.Error()) - return officeuserop.NewCreateOfficeUserUnprocessableEntity(), err - } - officeUser := models.OfficeUser{ LastName: payload.LastName, FirstName: payload.FirstName, @@ -260,6 +253,7 @@ func (h CreateOfficeUserHandler) Handle(params officeuserop.CreateOfficeUserPara return officeuserop.NewUpdateOfficeUserInternalServerError(), err } + updatedPrivileges := privilegesPayloadToModel(payload.Privileges) _, err = h.UserPrivilegeAssociator.UpdateUserPrivileges(appCtx, *createdOfficeUser.UserID, updatedPrivileges) if err != nil { appCtx.Logger().Error("Error updating user privileges", zap.Error(err)) diff --git a/pkg/handlers/authentication/auth.go b/pkg/handlers/authentication/auth.go index 27911711ee2..2826198f7f6 100644 --- a/pkg/handlers/authentication/auth.go +++ b/pkg/handlers/authentication/auth.go @@ -215,8 +215,6 @@ var allowedRoutes = map[string]bool{ "orders.showOrders": true, "orders.updateOrders": true, "postal_codes.validatePostalCodeWithRateData": true, - "ppm.showPPMEstimate": true, - "ppm.showPPMSitEstimate": true, "queues.showQueue": true, "uploads.deleteUpload": true, "users.showLoggedInUser": true, diff --git a/pkg/handlers/authentication/okta/provider.go b/pkg/handlers/authentication/okta/provider.go index 8862cc4fe75..cee6c91623d 100644 --- a/pkg/handlers/authentication/okta/provider.go +++ b/pkg/handlers/authentication/okta/provider.go @@ -280,6 +280,9 @@ func (op *Provider) GetOpenIDConfigURL() string { func (op *Provider) GetUserURL(oktaUserID string) string { return op.orgURL + "/api/v1/users/" + oktaUserID } +func (op *Provider) GetCreateUserURL(activate string) string { + return op.orgURL + "/api/v1/users/?activate=" + url.QueryEscape(activate) +} // TokenURL returns a full URL to retrieve a user token from okta.mil func (op Provider) TokenURL(r *http.Request) string { diff --git a/pkg/handlers/authentication/okta/provider_test.go b/pkg/handlers/authentication/okta/provider_test.go index ac720f497d5..61f46a6f0be 100644 --- a/pkg/handlers/authentication/okta/provider_test.go +++ b/pkg/handlers/authentication/okta/provider_test.go @@ -260,6 +260,25 @@ func TestGetUserURL(t *testing.T) { } } +func TestGetCreateUserURL(t *testing.T) { + // Create a new instance of your Provider with the desired orgURL + orgURL := "https://mock-okta-org-url.com" + callbackURL := "https://mock-callback-url.com" + clientID := "mock-client-ID" + secret := "mock-secret" + provider := okta.NewProvider(orgURL, callbackURL, clientID, secret) + + activate := "true" + + // Call the GetTokenURL function + url := provider.GetUserURL(activate) + + expectedURL := orgURL + "/api/v1/users/" + activate + if url != expectedURL { + t.Errorf("Expected URL to be '%s', got: '%s'", expectedURL, url) + } +} + func TestGenerateNonce(t *testing.T) { nonce := okta.GenerateNonce() diff --git a/pkg/handlers/ghcapi/api.go b/pkg/handlers/ghcapi/api.go index e138fca5fcf..cbfaa59e6e2 100644 --- a/pkg/handlers/ghcapi/api.go +++ b/pkg/handlers/ghcapi/api.go @@ -216,6 +216,9 @@ func NewGhcAPIHandler(handlerConfig handlers.HandlerConfig) *ghcops.MymoveAPI { handlerConfig, customer.NewCustomerUpdater(), } + ghcAPI.CustomerCreateCustomerWithOktaOptionHandler = CreateCustomerWithOktaOptionHandler{ + handlerConfig, + } ghcAPI.OrderGetOrderHandler = GetOrdersHandler{ handlerConfig, order.NewOrderFetcher(), diff --git a/pkg/handlers/ghcapi/customer.go b/pkg/handlers/ghcapi/customer.go index 58f067344f9..c4daa0c598b 100644 --- a/pkg/handlers/ghcapi/customer.go +++ b/pkg/handlers/ghcapi/customer.go @@ -1,22 +1,46 @@ package ghcapi import ( + "bytes" "database/sql" + "encoding/json" + "io" + "net/http" + "strings" "github.com/go-openapi/runtime/middleware" "github.com/gobuffalo/validate/v3" "github.com/gofrs/uuid" + "github.com/spf13/viper" "go.uber.org/zap" "github.com/transcom/mymove/pkg/appcontext" "github.com/transcom/mymove/pkg/apperror" + "github.com/transcom/mymove/pkg/cli" customercodeop "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/customer" "github.com/transcom/mymove/pkg/gen/ghcmessages" "github.com/transcom/mymove/pkg/handlers" + "github.com/transcom/mymove/pkg/handlers/authentication/okta" "github.com/transcom/mymove/pkg/handlers/ghcapi/internal/payloads" + "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/services" ) +func addressModelFromPayload(rawAddress *ghcmessages.Address) *models.Address { + if rawAddress == nil { + return nil + } + return &models.Address{ + StreetAddress1: *rawAddress.StreetAddress1, + StreetAddress2: rawAddress.StreetAddress2, + StreetAddress3: rawAddress.StreetAddress3, + City: *rawAddress.City, + State: *rawAddress.State, + PostalCode: *rawAddress.PostalCode, + Country: rawAddress.Country, + } +} + // GetCustomerHandler fetches the information of a specific customer type GetCustomerHandler struct { handlers.HandlerConfig @@ -86,3 +110,225 @@ func (h UpdateCustomerHandler) Handle(params customercodeop.UpdateCustomerParams return customercodeop.NewUpdateCustomerOK().WithPayload(customerPayload), nil }) } + +type CreateCustomerWithOktaOptionHandler struct { + handlers.HandlerConfig +} + +// Handle creates a customer/serviceMember from a request payload +func (h CreateCustomerWithOktaOptionHandler) Handle(params customercodeop.CreateCustomerWithOktaOptionParams) middleware.Responder { + return h.AuditableAppContextFromRequestWithErrors(params.HTTPRequest, + func(appCtx appcontext.AppContext) (middleware.Responder, error) { + var err error + var newServiceMember models.ServiceMember + var backupContact models.BackupContact + + payload := params.Body + email := payload.PersonalEmail + if email == "" { + badDataError := apperror.NewBadDataError("missing personal email") + payload := payloadForValidationError("Unable to create a customer", badDataError.Error(), h.GetTraceIDFromRequest(params.HTTPRequest), validate.NewErrors()) + return customercodeop.NewCreateCustomerWithOktaOptionUnprocessableEntity().WithPayload(payload), badDataError + } + + // delcaring okta values outside of if statements so we can use them later + var oktaSub string + oktaUser := &models.CreatedOktaUser{} + + // if the office user checked "yes", then we will create an okta account for the user + // this will add the user to the okta customer application and send an activation email + if payload.CreateOktaAccount { + var oktaErr error + oktaUser, oktaErr = createOktaProfile(appCtx, params) + if oktaErr != nil { + appCtx.Logger().Error("error creating okta profile", zap.Error(oktaErr)) + return customercodeop.NewCreateCustomerWithOktaOptionBadRequest(), oktaErr + } + oktaSub = oktaUser.ID + } + + transactionError := appCtx.NewTransaction(func(txnAppCtx appcontext.AppContext) error { + var verrs *validate.Errors + // creating a user and populating okta values (for now these can be null) + user, userErr := models.CreateUser(appCtx.DB(), oktaSub, email) + if userErr != nil { + appCtx.Logger().Error("error creating user", zap.Error(err)) + return userErr + } + + // now we will take all the data we have and build the service member + userID := user.ID + residentialAddress := addressModelFromPayload(&payload.ResidentialAddress.Address) + backupMailingAddress := addressModelFromPayload(&payload.BackupMailingAddress.Address) + var edipi *string + if *payload.Edipi == "" { + edipi = nil + } else { + edipi = payload.Edipi + } + + // Create a new serviceMember using the userID + newServiceMember = models.ServiceMember{ + UserID: userID, + Edipi: edipi, + Affiliation: (*models.ServiceMemberAffiliation)(payload.Affiliation), + FirstName: &payload.FirstName, + MiddleName: payload.MiddleName, + LastName: &payload.LastName, + Suffix: payload.Suffix, + Telephone: payload.Telephone, + SecondaryTelephone: payload.SecondaryTelephone, + PersonalEmail: &payload.PersonalEmail, + PhoneIsPreferred: &payload.PhoneIsPreferred, + EmailIsPreferred: &payload.EmailIsPreferred, + ResidentialAddress: residentialAddress, + BackupMailingAddress: backupMailingAddress, + } + + // create the service member and save to the db + smVerrs, smErr := models.SaveServiceMember(appCtx, &newServiceMember) + if smVerrs.HasAny() || smErr != nil { + appCtx.Logger().Error("error creating service member", zap.Error(err)) + return err + } + + // creating backup contact associated with service member since this is done separately + // default permission of EDIT since we want them to be able to change this info + defaultPermission := models.BackupContactPermissionEDIT + backupContact, verrs, err = newServiceMember.CreateBackupContact(appCtx.DB(), + *payload.BackupContact.Name, + *payload.BackupContact.Email, + payload.BackupContact.Phone, + models.BackupContactPermission(defaultPermission)) + if err != nil || verrs.HasAny() { + appCtx.Logger().Error("error creating backup contact", zap.Error(err)) + return err + } + return nil + }) + + if transactionError != nil { + return nil, transactionError + } + + // covering error returns + if err != nil { + appCtx.Logger().Error("error creating customer", zap.Error(err)) + switch err.(type) { + case apperror.NotFoundError: + return customercodeop.NewCreateCustomerWithOktaOptionNotFound(), err + case apperror.InvalidInputError: + payload := payloadForValidationError("Unable to complete request", err.Error(), h.GetTraceIDFromRequest(params.HTTPRequest), validate.NewErrors()) + return customercodeop.NewCreateCustomerWithOktaOptionUnprocessableEntity().WithPayload(payload), err + case apperror.PreconditionFailedError: + return customercodeop.NewCreateCustomerWithOktaOptionPreconditionFailed().WithPayload(&ghcmessages.Error{Message: handlers.FmtString(err.Error())}), err + default: + return customercodeop.NewUpdateCustomerInternalServerError(), err + } + } + + customerPayload := payloads.CreatedCustomer(&newServiceMember, oktaUser, &backupContact) + + return customercodeop.NewCreateCustomerWithOktaOptionOK().WithPayload(customerPayload), nil + }) +} + +// createOktaProfile sends a request to the Okta Users API +// this creates a user in Okta assigned to the customer group (allowing access to the customer application) +func createOktaProfile(appCtx appcontext.AppContext, params customercodeop.CreateCustomerWithOktaOptionParams) (*models.CreatedOktaUser, error) { + // setting viper so we can access the api key in the env vars + v := viper.New() + v.SetEnvKeyReplacer(strings.NewReplacer("-", "_")) + v.AutomaticEnv() + apiKey := v.GetString(cli.OktaAPIKeyFlag) + customerGroupID := v.GetString(cli.OktaCustomerGroupIDFlag) + + // taking all the data that we'll need for the okta profile creation + payload := params.Body + oktaEmail := payload.PersonalEmail + oktaFirstName := payload.FirstName + oktaLastName := payload.LastName + oktaPhone := payload.Telephone + + // Creating the Profile struct + profile := models.Profile{ + FirstName: oktaFirstName, + LastName: oktaLastName, + Email: oktaEmail, + Login: oktaEmail, + MobilePhone: *oktaPhone, + } + + // Creating the OktaUserPayload struct + oktaPayload := models.OktaUserPayload{ + Profile: profile, + GroupIds: []string{customerGroupID}, + } + + // getting okta domain url for request + provider, err := okta.GetOktaProviderForRequest(params.HTTPRequest) + if err != nil { + return nil, err + } + + // getting the api call url from provider.go + activate := "true" + baseURL := provider.GetCreateUserURL(activate) + + body, err := json.Marshal(oktaPayload) + if err != nil { + appCtx.Logger().Error("error marshaling payload", zap.Error(err)) + return nil, err + } + + // making HTTP request to Okta Users API to create a user + // this is done via a POST request for creating a user that sends an activation email (when activate=true) + // https://developer.okta.com/docs/reference/api/users/#create-user-without-credentials + req, err := http.NewRequest("POST", baseURL, bytes.NewReader(body)) + if err != nil { + appCtx.Logger().Error("could not execute request", zap.Error(err)) + return nil, err + } + h := req.Header + h.Add("Authorization", "SSWS "+apiKey) + h.Add("Accept", "application/json") + h.Add("Content-Type", "application/json") + + // now let the client send the request + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + appCtx.Logger().Error("could not execute request", zap.Error(err)) + return nil, err + } + + // if all is well, should have a 200 response + response, err := io.ReadAll(resp.Body) + if err != nil { + appCtx.Logger().Error("could not read response body", zap.Error(err)) + return nil, err + } + if resp.StatusCode != http.StatusOK { + if resp.StatusCode == http.StatusInternalServerError { + return nil, err + } + if resp.StatusCode == http.StatusBadRequest { + return nil, err + } + if resp.StatusCode == http.StatusForbidden { + return nil, err + } + } + + // now we will take the response and parse it into our Go struct + user := models.CreatedOktaUser{} + err = json.Unmarshal(response, &user) + if err != nil { + appCtx.Logger().Error("could not unmarshal body", zap.Error(err)) + return nil, err + } + + defer resp.Body.Close() + + return &user, nil +} diff --git a/pkg/handlers/ghcapi/customer_test.go b/pkg/handlers/ghcapi/customer_test.go index 5c1fa6e697f..40baf780cd5 100644 --- a/pkg/handlers/ghcapi/customer_test.go +++ b/pkg/handlers/ghcapi/customer_test.go @@ -1,19 +1,25 @@ package ghcapi import ( + "fmt" "net/http/httptest" "github.com/go-openapi/strfmt" + "github.com/jarcoal/httpmock" + "github.com/markbates/goth" "github.com/transcom/mymove/pkg/etag" "github.com/transcom/mymove/pkg/factory" customerops "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/customer" "github.com/transcom/mymove/pkg/gen/ghcmessages" "github.com/transcom/mymove/pkg/handlers" + "github.com/transcom/mymove/pkg/handlers/authentication/okta" "github.com/transcom/mymove/pkg/models/roles" customerservice "github.com/transcom/mymove/pkg/services/office_user/customer" ) +const officeProviderName = "officeProvider" + func (suite *HandlerSuite) TestGetCustomerHandlerIntegration() { customer := factory.BuildServiceMember(suite.DB(), nil, nil) @@ -114,3 +120,111 @@ func (suite *HandlerSuite) TestUpdateCustomerHandler() { suite.Equal(body.BackupContact.Phone, updateCustomerPayload.BackupContact.Phone) suite.Equal(body.BackupContact.Email, updateCustomerPayload.BackupContact.Email) } + +func (suite *HandlerSuite) TestCreateCustomerWithOktaOptionHandler() { + // in order to call the endpoint, we need to be an authenticated office user that's a SC + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) + officeUser.User.Roles = append(officeUser.User.Roles, roles.Role{ + RoleType: roles.RoleTypeServicesCounselor, + }) + + // Build provider + provider, err := factory.BuildOktaProvider(officeProviderName) + suite.NoError(err) + + mockAndActivateOktaEndpoints(provider) + + residentialAddress := ghcmessages.Address{ + StreetAddress1: handlers.FmtString("123 New Street"), + City: handlers.FmtString("Newcity"), + State: handlers.FmtString("MA"), + PostalCode: handlers.FmtString("12345"), + } + + backupAddress := ghcmessages.Address{ + StreetAddress1: handlers.FmtString("123 Backup Street"), + City: handlers.FmtString("Backupcity"), + State: handlers.FmtString("MA"), + PostalCode: handlers.FmtString("67890"), + } + + affiliation := ghcmessages.AffiliationARMY + + body := &ghcmessages.CreateCustomerPayload{ + LastName: "Last", + FirstName: "First", + Telephone: handlers.FmtString("223-455-3399"), + Affiliation: &affiliation, + Edipi: handlers.FmtString(""), + PersonalEmail: *handlers.FmtString("email@email.com"), + BackupContact: &ghcmessages.BackupContact{ + Name: handlers.FmtString("New Backup Contact"), + Phone: handlers.FmtString("445-345-1212"), + Email: handlers.FmtString("newbackup@mail.com"), + }, + ResidentialAddress: struct { + ghcmessages.Address + }{ + Address: residentialAddress, + }, + BackupMailingAddress: struct { + ghcmessages.Address + }{ + Address: backupAddress, + }, + CreateOktaAccount: true, + } + + defer goth.ClearProviders() + goth.UseProviders(provider) + + request := httptest.NewRequest("POST", "/customer", nil) + request = suite.AuthenticateOfficeRequest(request, officeUser) + params := customerops.CreateCustomerWithOktaOptionParams{ + HTTPRequest: request, + Body: body, + } + handlerConfig := suite.HandlerConfig() + handler := CreateCustomerWithOktaOptionHandler{ + handlerConfig, + } + + suite.NoError(params.Body.Validate(strfmt.Default)) + + response := handler.Handle(params) + suite.IsNotErrResponse(response) + + suite.Assertions.IsType(&customerops.CreateCustomerWithOktaOptionOK{}, response) + createdCustomerResponse := response.(*customerops.CreateCustomerWithOktaOptionOK) + createdCustomerPayload := createdCustomerResponse.Payload + + suite.NoError(createdCustomerPayload.Validate(strfmt.Default)) + + suite.Equal(body.FirstName, createdCustomerPayload.FirstName) + suite.Equal(body.LastName, createdCustomerPayload.LastName) + suite.Equal(body.Telephone, createdCustomerPayload.Telephone) + suite.Equal(body.BackupContact.Name, createdCustomerPayload.BackupContact.Name) + suite.Equal(body.BackupContact.Phone, createdCustomerPayload.BackupContact.Phone) + suite.Equal(body.BackupContact.Email, createdCustomerPayload.BackupContact.Email) +} + +// Generate and activate Okta endpoints that will be using during the auth handlers. +func mockAndActivateOktaEndpoints(provider *okta.Provider) { + + activate := "true" + createUserEndpoint := provider.GetCreateUserURL(activate) + oktaID := "fakeSub" + + httpmock.RegisterResponder("POST", createUserEndpoint, + httpmock.NewStringResponder(200, fmt.Sprintf(`{ + "id": "%s", + "profile": { + "firstName": "First", + "lastName": "Last", + "email": "email@email.com", + "login": "email@email.com" + } + }`, oktaID))) + + httpmock.Activate() +} diff --git a/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go b/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go index b3576b1d9de..97abdab0037 100644 --- a/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go +++ b/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go @@ -462,6 +462,40 @@ func Customer(customer *models.ServiceMember) *ghcmessages.Customer { return &payload } +func CreatedCustomer(sm *models.ServiceMember, oktaUser *models.CreatedOktaUser, backupContact *models.BackupContact) *ghcmessages.CreatedCustomer { + if sm == nil || oktaUser == nil || backupContact == nil { + return nil + } + + bc := &ghcmessages.BackupContact{ + Name: &backupContact.Name, + Email: &backupContact.Email, + Phone: backupContact.Phone, + } + + payload := ghcmessages.CreatedCustomer{ + ID: strfmt.UUID(sm.ID.String()), + UserID: strfmt.UUID(sm.UserID.String()), + OktaID: oktaUser.ID, + OktaEmail: oktaUser.Profile.Email, + Affiliation: swag.StringValue((*string)(sm.Affiliation)), + Edipi: sm.Edipi, + FirstName: swag.StringValue(sm.FirstName), + MiddleName: sm.MiddleName, + LastName: swag.StringValue(sm.LastName), + Suffix: sm.Suffix, + ResidentialAddress: Address(sm.ResidentialAddress), + BackupAddress: Address(sm.BackupMailingAddress), + PersonalEmail: *sm.PersonalEmail, + Telephone: sm.Telephone, + SecondaryTelephone: sm.SecondaryTelephone, + PhoneIsPreferred: swag.BoolValue(sm.PhoneIsPreferred), + EmailIsPreferred: swag.BoolValue(sm.EmailIsPreferred), + BackupContact: bc, + } + return &payload +} + // Order payload func Order(order *models.Order) *ghcmessages.Order { if order == nil { @@ -721,7 +755,6 @@ func currentSIT(currentSIT *services.CurrentSIT) *ghcmessages.SITStatusCurrentSI SitEntryDate: handlers.FmtDate(currentSIT.SITEntryDate), SitDepartureDate: handlers.FmtDatePtr(currentSIT.SITDepartureDate), SitAllowanceEndDate: handlers.FmtDate(currentSIT.SITAllowanceEndDate), - SitAuthorizedEndDate: handlers.FmtDatePtr(currentSIT.SITAuthorizedEndDate), SitCustomerContacted: handlers.FmtDatePtr(currentSIT.SITCustomerContacted), SitRequestedDelivery: handlers.FmtDatePtr(currentSIT.SITRequestedDelivery), } @@ -1404,6 +1437,8 @@ func MTOServiceItemModel(s *models.MTOServiceItem, storer storage.FileStorer) *g Dimensions: MTOServiceItemDimensions(s.Dimensions), CustomerContacts: MTOServiceItemCustomerContacts(s.CustomerContacts), SitAddressUpdates: SITAddressUpdates(s.SITAddressUpdates), + SitOriginHHGOriginalAddress: Address(s.SITOriginHHGOriginalAddress), + SitOriginHHGActualAddress: Address(s.SITOriginHHGActualAddress), SitDestinationOriginalAddress: Address(s.SITDestinationOriginalAddress), SitDestinationFinalAddress: Address(s.SITDestinationFinalAddress), EstimatedWeight: handlers.FmtPoundPtr(s.EstimatedWeight), @@ -1414,6 +1449,7 @@ func MTOServiceItemModel(s *models.MTOServiceItem, storer storage.FileStorer) *g ServiceRequestDocuments: serviceRequestDocs, ConvertToCustomerExpense: *handlers.FmtBool(s.CustomerExpense), CustomerExpenseReason: handlers.FmtStringPtr(s.CustomerExpenseReason), + SitDeliveryMiles: handlers.FmtIntPtrToInt64(s.SITDeliveryMiles), } } 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 c9f16ba8a63..09cf031cf45 100644 --- a/pkg/handlers/ghcapi/internal/payloads/model_to_payload_test.go +++ b/pkg/handlers/ghcapi/internal/payloads/model_to_payload_test.go @@ -222,3 +222,68 @@ func (suite *PayloadsSuite) TestProofOfServiceDoc() { suite.IsType(returnedProofOfServiceDoc, &ghcmessages.ProofOfServiceDoc{}) }) } + +func (suite *PayloadsSuite) TestCreateCustomer() { + id, _ := uuid.NewV4() + id2, _ := uuid.NewV4() + oktaID := "thisIsNotARealID" + + oktaUser := models.CreatedOktaUser{ + ID: oktaID, + Profile: struct { + FirstName string `json:"firstName"` + LastName string `json:"lastName"` + MobilePhone string `json:"mobilePhone"` + SecondEmail string `json:"secondEmail"` + Login string `json:"login"` + Email string `json:"email"` + }{ + Email: "john.doe@example.com", + }, + } + + residentialAddress := models.Address{ + StreetAddress1: "123 New St", + City: "Beverly Hills", + State: "CA", + PostalCode: "89503", + Country: models.StringPointer("United States"), + } + + backupAddress := models.Address{ + StreetAddress1: "123 Old St", + City: "Beverly Hills", + State: "CA", + PostalCode: "89502", + Country: models.StringPointer("United States"), + } + + phone := "444-555-6677" + backupContact := models.BackupContact{ + Name: "Billy Bob", + Email: "billBob@mail.mil", + Phone: &phone, + } + + firstName := "First" + lastName := "Last" + affiliation := models.AffiliationARMY + email := "dontEmailMe@gmail.com" + sm := models.ServiceMember{ + ID: id, + UserID: id2, + FirstName: &firstName, + LastName: &lastName, + Affiliation: &affiliation, + PersonalEmail: &email, + Telephone: &phone, + ResidentialAddress: &residentialAddress, + BackupMailingAddress: &backupAddress, + } + + suite.Run("Success - Returns a ghcmessages Upload payload from Upload Struct", func() { + returnedShipmentAddressUpdate := CreatedCustomer(&sm, &oktaUser, &backupContact) + + suite.IsType(returnedShipmentAddressUpdate, &ghcmessages.CreatedCustomer{}) + }) +} diff --git a/pkg/handlers/ghcapi/mto_service_items.go b/pkg/handlers/ghcapi/mto_service_items.go index adbac2dad7e..02911997862 100644 --- a/pkg/handlers/ghcapi/mto_service_items.go +++ b/pkg/handlers/ghcapi/mto_service_items.go @@ -279,6 +279,8 @@ func (h ListMTOServiceItemsHandler) Handle(params mtoserviceitemop.ListMTOServic query.NewQueryAssociation("Dimensions"), query.NewQueryAssociation("SITDestinationOriginalAddress"), query.NewQueryAssociation("SITDestinationFinalAddress"), + query.NewQueryAssociation("SITOriginHHGOriginalAddress"), + query.NewQueryAssociation("SITOriginHHGActualAddress"), }) var serviceItems models.MTOServiceItems diff --git a/pkg/handlers/internalapi/moves.go b/pkg/handlers/internalapi/moves.go index abb9f7d17a9..c35c9858125 100644 --- a/pkg/handlers/internalapi/moves.go +++ b/pkg/handlers/internalapi/moves.go @@ -95,6 +95,7 @@ func payloadForInternalMove(storer storage.FileStorer, list models.Moves) []*int MoveCode: move.Locator, Orders: orders, CloseoutOffice: &closeOutOffice, + SubmittedAt: handlers.FmtDateTimePtr(move.SubmittedAt), } convertedCurrentMovesList = append(convertedCurrentMovesList, currentMove) diff --git a/pkg/handlers/internalapi/mto_shipment.go b/pkg/handlers/internalapi/mto_shipment.go index c28c67bb030..fa703606a72 100644 --- a/pkg/handlers/internalapi/mto_shipment.go +++ b/pkg/handlers/internalapi/mto_shipment.go @@ -160,6 +160,13 @@ func (h UpdateMTOShipmentHandler) Handle(params mtoshipmentops.UpdateMTOShipment h.GetTraceIDFromRequest(params.HTTPRequest), ), ), err + case apperror.UpdateError: + return mtoshipmentops.NewUpdateMTOShipmentBadRequest().WithPayload( + payloads.ClientError(handlers.BadRequestErrMessage, + err.Error(), + h.GetTraceIDFromRequest(params.HTTPRequest), + ), + ), err case apperror.InvalidInputError: return mtoshipmentops. NewUpdateMTOShipmentUnprocessableEntity(). diff --git a/pkg/handlers/primeapi/mto_service_item.go b/pkg/handlers/primeapi/mto_service_item.go index 853fe71e035..5292e313adf 100644 --- a/pkg/handlers/primeapi/mto_service_item.go +++ b/pkg/handlers/primeapi/mto_service_item.go @@ -129,8 +129,7 @@ func (h UpdateMTOServiceItemHandler) Handle(params mtoserviceitemops.UpdateMTOSe verrs.Error(), h.GetTraceIDFromRequest(params.HTTPRequest), verrs)), verrs } - // We need to get the shipment in case we need to update the Authorized End Date and Required Delivery Dates - // for an Origin or Destination SIT service item + // We need to get the shipment in case we need to update the Required Delivery Date for an Origin SIT eagerAssociations := []string{"MoveTaskOrder", "PickupAddress", "DestinationAddress", diff --git a/pkg/handlers/primeapi/mto_shipment.go b/pkg/handlers/primeapi/mto_shipment.go index 815ce2fd8d0..feff91d9653 100644 --- a/pkg/handlers/primeapi/mto_shipment.go +++ b/pkg/handlers/primeapi/mto_shipment.go @@ -180,6 +180,14 @@ func (h UpdateMTOShipmentHandler) Handle(params mtoshipmentops.UpdateMTOShipment payloads.ClientError(handlers.NotFoundMessage, err.Error(), h.GetTraceIDFromRequest(params.HTTPRequest))), err } + if dbShipment.Status == models.MTOShipmentStatusApproved && + (params.Body.DestinationAddress.City != nil || + params.Body.DestinationAddress.State != nil || + params.Body.DestinationAddress.PostalCode != nil) { + return mtoshipmentops.NewUpdateMTOShipmentUnprocessableEntity().WithPayload(payloads.ValidationError( + "This shipment is approved, please use the updateShipmentDestinationAddress endpoint to update the destination address", h.GetTraceIDFromRequest(params.HTTPRequest), nil)), err + } + var agents []models.MTOAgent err = appCtx.DB().Scope(utilities.ExcludeDeletedScope()).Where("mto_shipment_id = ?", mtoShipment.ID).All(&agents) if err != nil { diff --git a/pkg/handlers/primeapi/mto_shipment_address.go b/pkg/handlers/primeapi/mto_shipment_address.go index e71084b30a9..2f050cb947a 100644 --- a/pkg/handlers/primeapi/mto_shipment_address.go +++ b/pkg/handlers/primeapi/mto_shipment_address.go @@ -10,7 +10,9 @@ import ( mtoshipmentops "github.com/transcom/mymove/pkg/gen/primeapi/primeoperations/mto_shipment" "github.com/transcom/mymove/pkg/handlers" "github.com/transcom/mymove/pkg/handlers/primeapi/payloads" + "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/services" + mtoshipment "github.com/transcom/mymove/pkg/services/mto_shipment" ) // UpdateMTOShipmentAddressHandler is the handler to update an address @@ -30,6 +32,18 @@ func (h UpdateMTOShipmentAddressHandler) Handle(params mtoshipmentops.UpdateMTOS mtoShipmentID := uuid.FromStringOrNil(params.MtoShipmentID.String()) addressID := uuid.FromStringOrNil(params.AddressID.String()) + dbShipment, err := mtoshipment.FindShipment(appCtx, mtoShipmentID, "DestinationAddress") + if err != nil { + return mtoshipmentops.NewUpdateMTOShipmentAddressNotFound().WithPayload( + payloads.ClientError(handlers.NotFoundMessage, err.Error(), h.GetTraceIDFromRequest(params.HTTPRequest))), err + } + + if dbShipment.Status == models.MTOShipmentStatusApproved && + (*dbShipment.DestinationAddressID == addressID) { + return mtoshipmentops.NewUpdateMTOShipmentAddressUnprocessableEntity().WithPayload(payloads.ValidationError( + "This shipment is approved, please use the updateShipmentDestinationAddress endpoint to update the destination address of an approved shipment", h.GetTraceIDFromRequest(params.HTTPRequest), nil)), err + } + // Get the new address model newAddress := payloads.AddressModel(payload) newAddress.ID = addressID diff --git a/pkg/handlers/primeapi/mto_shipment_address_test.go b/pkg/handlers/primeapi/mto_shipment_address_test.go index 1849e725582..e6d7128e7c4 100644 --- a/pkg/handlers/primeapi/mto_shipment_address_test.go +++ b/pkg/handlers/primeapi/mto_shipment_address_test.go @@ -253,4 +253,52 @@ func (suite *HandlerSuite) TestUpdateMTOShipmentAddressHandler() { // Validate outgoing payload suite.NoError(responsePayload.Validate(strfmt.Default)) }) + + suite.Run("Fail - Unprocessable due to dest address being updated for approved shipment", func() { + // Testcase: destination address is updated on a shipment, but shipment is approved + // Expected: UnprocessableEntity error is returned + // Under Test: UpdateMTOShipmentAddress handler + handler, availableMove := setupTestData() + destAddress := factory.BuildAddress(suite.DB(), nil, []factory.Trait{factory.GetTraitAddress2}) + address := models.Address{ + ID: destAddress.ID, + StreetAddress1: "7 Q St", + City: "Framington", + State: "MA", + PostalCode: "94055", + } + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: availableMove, + LinkOnly: true, + }, + { + Model: models.MTOShipment{ + Status: models.MTOShipmentStatusApproved, + }, + }, + { + Model: destAddress, + LinkOnly: true, + Type: &factory.Addresses.DeliveryAddress, + }, + }, nil) + // Try to update destination address for approved shipment + payload := payloads.Address(&address) + req := httptest.NewRequest("PUT", fmt.Sprintf("/mto-shipments/%s/addresses/%s", shipment.ID.String(), shipment.ID.String()), nil) + params := mtoshipmentops.UpdateMTOShipmentAddressParams{ + HTTPRequest: req, + AddressID: *handlers.FmtUUID(destAddress.ID), + MtoShipmentID: *handlers.FmtUUID(shipment.ID), + Body: payload, + IfMatch: etag.GenerateEtag(shipment.DestinationAddress.UpdatedAt), + } + + // Validate incoming payload + suite.NoError(params.Body.Validate(strfmt.Default)) + + // Run handler and check response + response := handler.Handle(params) + suite.IsType(&mtoshipmentops.UpdateMTOShipmentAddressUnprocessableEntity{}, response) + }) } diff --git a/pkg/handlers/primeapi/mto_shipment_test.go b/pkg/handlers/primeapi/mto_shipment_test.go index 3348b3d429e..80e890a72cf 100644 --- a/pkg/handlers/primeapi/mto_shipment_test.go +++ b/pkg/handlers/primeapi/mto_shipment_test.go @@ -2045,6 +2045,56 @@ func (suite *HandlerSuite) TestUpdateMTOShipmentDateLogic() { } }) + suite.Run("PATCH sends back unprocessable response when dest address is updated for approved shipment", func() { + handler, move := setupTestData() + + // Create shipment with populated estimated weight and scheduled date + tenDaysFromNow := now.AddDate(0, 0, 11) + pickupAddress := factory.BuildAddress(suite.DB(), nil, []factory.Trait{factory.GetTraitAddress2}) + + // setting shipment status to approved + oldShipment := factory.BuildMTOShipmentMinimal(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + { + Model: models.MTOShipment{ + Status: models.MTOShipmentStatusApproved, + ApprovedDate: &now, + PrimeEstimatedWeight: &primeEstimatedWeight, + ScheduledPickupDate: &tenDaysFromNow, + }, + }, + { + Model: pickupAddress, + LinkOnly: true, + Type: &factory.Addresses.PickupAddress, + }, + }, nil) + + // adding destination address to update to get back error + update := primemessages.UpdateMTOShipment{ + DestinationAddress: getFakeAddress(), + } + req := httptest.NewRequest("PATCH", fmt.Sprintf("/mto_shipments/%s", oldShipment.ID.String()), nil) + params := mtoshipmentops.UpdateMTOShipmentParams{ + HTTPRequest: req, + MtoShipmentID: *handlers.FmtUUID(oldShipment.ID), + Body: &update, + IfMatch: etag.GenerateEtag(oldShipment.UpdatedAt), + } + + // Validate incoming payload + suite.NoError(params.Body.Validate(strfmt.Default)) + + response := handler.Handle(params) + + // CHECK RESPONSE + suite.IsType(&mtoshipmentops.UpdateMTOShipmentUnprocessableEntity{}, response) + + }) + suite.Run("PATCH Success 200 RequiredDeliveryDate updated on destinationAddress creation", func() { // Under test: updateMTOShipmentHandler.Handle, RequiredDeliveryDate logic // Mocked: Planner @@ -2064,7 +2114,7 @@ func (suite *HandlerSuite) TestUpdateMTOShipmentDateLogic() { }, { Model: models.MTOShipment{ - Status: models.MTOShipmentStatusApproved, + Status: models.MTOShipmentStatusSubmitted, ApprovedDate: &now, PrimeEstimatedWeight: &primeEstimatedWeight, ScheduledPickupDate: &tenDaysFromNow, diff --git a/pkg/models/mto_service_items.go b/pkg/models/mto_service_items.go index f8a1b6901e2..ff68f709d8c 100644 --- a/pkg/models/mto_service_items.go +++ b/pkg/models/mto_service_items.go @@ -65,7 +65,6 @@ type MTOServiceItem struct { CustomerExpense bool `db:"customer_expense"` CustomerExpenseReason *string `db:"customer_expense_reason"` SITDeliveryMiles *int `db:"sit_delivery_miles"` - SITAuthorizedEndDate *time.Time `db:"sit_authorized_end_date"` } // MTOServiceItemSingle is an object representing a single column in the service items table diff --git a/pkg/models/okta_user_create.go b/pkg/models/okta_user_create.go new file mode 100644 index 00000000000..4621abd3d43 --- /dev/null +++ b/pkg/models/okta_user_create.go @@ -0,0 +1,14 @@ +package models + +type OktaUserPayload struct { + Profile Profile `json:"profile"` + GroupIds []string `json:"groupIds"` +} + +type Profile struct { + FirstName string `json:"firstName"` + LastName string `json:"lastName"` + Email string `json:"email"` + Login string `json:"login"` + MobilePhone string `json:"mobilePhone"` +} diff --git a/pkg/models/okta_user_info.go b/pkg/models/okta_user_info.go index c8ed7537fa0..0a193f6ac49 100644 --- a/pkg/models/okta_user_info.go +++ b/pkg/models/okta_user_info.go @@ -13,3 +13,18 @@ type OktaUser struct { EmailVerified bool `json:"email_verified"` Edipi string `json:"cac_edipi"` } + +type CreatedOktaUser struct { + ID string `json:"id"` + Status string `json:"status"` + Created string `json:"created"` + Activated string `json:"activated"` + Profile struct { + FirstName string `json:"firstName"` + LastName string `json:"lastName"` + MobilePhone string `json:"mobilePhone"` + SecondEmail string `json:"secondEmail"` + Login string `json:"login"` + Email string `json:"email"` + } `json:"profile"` +} diff --git a/pkg/services/event/internal_endpoint.go b/pkg/services/event/internal_endpoint.go index 5bae39a3e03..786fcdcb805 100644 --- a/pkg/services/event/internal_endpoint.go +++ b/pkg/services/event/internal_endpoint.go @@ -7,12 +7,6 @@ const InternalAPIName string = "internalapi" // -------------------- ENDPOINT KEYS -------------------- -// InternalShowPPMEstimateEndpointKey is the key for the showPPMEstimate endpoint in internal -const InternalShowPPMEstimateEndpointKey = "Internal.ShowPPMEstimate" - -// InternalShowPPMSitEstimateEndpointKey is the key for the showPPMSitEstimate endpoint in internal -const InternalShowPPMSitEstimateEndpointKey = "Internal.ShowPPMSitEstimate" - // InternalShowLoggedInUserEndpointKey is the key for the showLoggedInUser endpoint in internal const InternalShowLoggedInUserEndpointKey = "Internal.ShowLoggedInUser" @@ -153,14 +147,6 @@ const InternalUpdateWeightTicketEndpointKey = "Internal.UpdateWeightTicket" // -------------------- ENDPOINT MAP ENTRIES -------------------- var internalEndpoints = EndpointMapType{ - InternalShowPPMEstimateEndpointKey: { - APIName: InternalAPIName, - OperationID: "showPPMEstimate", - }, - InternalShowPPMSitEstimateEndpointKey: { - APIName: InternalAPIName, - OperationID: "showPPMSitEstimate", - }, InternalShowLoggedInUserEndpointKey: { APIName: InternalAPIName, OperationID: "showLoggedInUser", 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 fab071e4097..3aaca7b3f76 100644 --- a/pkg/services/mto_service_item/mto_service_item_updater.go +++ b/pkg/services/mto_service_item/mto_service_item_updater.go @@ -196,68 +196,71 @@ func (p *mtoServiceItemUpdater) updateServiceItem(appCtx appcontext.AppContext, serviceItem.RejectedAt = nil serviceItem.ApprovedAt = &now - // Get the shipment destination address - mtoShipment, err := p.shipmentFetcher.GetShipment(appCtx, *serviceItem.MTOShipmentID, "DestinationAddress", "PickupAddress", "MTOServiceItems.SITOriginHHGOriginalAddress") - if err != nil { - return nil, err - } - - // Check to see if there is already a SIT Destination Original Address - // by checking for the ID before trying to set one on the service item. - // If there isn't one, then we set it. We will update all four destination - // SIT service items that get created - if (serviceItem.ReService.Code == models.ReServiceCodeDDDSIT || - serviceItem.ReService.Code == models.ReServiceCodeDDSFSC || - serviceItem.ReService.Code == models.ReServiceCodeDDASIT || - serviceItem.ReService.Code == models.ReServiceCodeDDFSIT) && - serviceItem.SITDestinationOriginalAddressID == nil { - - // Set the original address on a service item to the shipment's - // destination address when approving destination SIT service items - // Creating a new address record to ensure SITDestinationOriginalAddress - // doesn't change if shipment destination address is updated - shipmentDestinationAddress := &models.Address{ - StreetAddress1: mtoShipment.DestinationAddress.StreetAddress1, - StreetAddress2: mtoShipment.DestinationAddress.StreetAddress2, - StreetAddress3: mtoShipment.DestinationAddress.StreetAddress3, - City: mtoShipment.DestinationAddress.City, - State: mtoShipment.DestinationAddress.State, - PostalCode: mtoShipment.DestinationAddress.PostalCode, - Country: mtoShipment.DestinationAddress.Country, - } - shipmentDestinationAddress, err = p.addressCreator.CreateAddress(appCtx, shipmentDestinationAddress) + if serviceItem.MTOShipmentID != nil { + // Get the shipment destination address + mtoShipment, err := p.shipmentFetcher.GetShipment(appCtx, *serviceItem.MTOShipmentID, "DestinationAddress", "PickupAddress", "MTOServiceItems.SITOriginHHGOriginalAddress") if err != nil { return nil, err } - serviceItem.SITDestinationOriginalAddressID = &shipmentDestinationAddress.ID - serviceItem.SITDestinationOriginalAddress = shipmentDestinationAddress - if serviceItem.SITDestinationFinalAddressID == nil { - serviceItem.SITDestinationFinalAddressID = &shipmentDestinationAddress.ID - serviceItem.SITDestinationFinalAddress = shipmentDestinationAddress - } + // Check to see if there is already a SIT Destination Original Address + // by checking for the ID before trying to set one on the service item. + // If there isn't one, then we set it. We will update all four destination + // SIT service items that get created + if (serviceItem.ReService.Code == models.ReServiceCodeDDDSIT || + serviceItem.ReService.Code == models.ReServiceCodeDDSFSC || + serviceItem.ReService.Code == models.ReServiceCodeDDASIT || + serviceItem.ReService.Code == models.ReServiceCodeDDFSIT) && + serviceItem.SITDestinationOriginalAddressID == nil { + + // Set the original address on a service item to the shipment's + // destination address when approving destination SIT service items + // Creating a new address record to ensure SITDestinationOriginalAddress + // doesn't change if shipment destination address is updated + shipmentDestinationAddress := &models.Address{ + StreetAddress1: mtoShipment.DestinationAddress.StreetAddress1, + StreetAddress2: mtoShipment.DestinationAddress.StreetAddress2, + StreetAddress3: mtoShipment.DestinationAddress.StreetAddress3, + City: mtoShipment.DestinationAddress.City, + State: mtoShipment.DestinationAddress.State, + PostalCode: mtoShipment.DestinationAddress.PostalCode, + Country: mtoShipment.DestinationAddress.Country, + } + shipmentDestinationAddress, err = p.addressCreator.CreateAddress(appCtx, shipmentDestinationAddress) + if err != nil { + return nil, err + } + serviceItem.SITDestinationOriginalAddressID = &shipmentDestinationAddress.ID + serviceItem.SITDestinationOriginalAddress = shipmentDestinationAddress - // Calculate SITDeliveryMiles for DDDSIT and DDSFSC origin SIT service items - if serviceItem.ReService.Code == models.ReServiceCodeDDDSIT || - serviceItem.ReService.Code == models.ReServiceCodeDDSFSC { - // Destination SIT: distance between shipment destination address & service item ORIGINAL destination address - milesCalculated, err := p.planner.ZipTransitDistance(appCtx, mtoShipment.DestinationAddress.PostalCode, serviceItem.SITDestinationOriginalAddress.PostalCode) + if serviceItem.SITDestinationFinalAddressID == nil { + serviceItem.SITDestinationFinalAddressID = &shipmentDestinationAddress.ID + serviceItem.SITDestinationFinalAddress = shipmentDestinationAddress + } + + // Calculate SITDeliveryMiles for DDDSIT and DDSFSC origin SIT service items + if serviceItem.ReService.Code == models.ReServiceCodeDDDSIT || + serviceItem.ReService.Code == models.ReServiceCodeDDSFSC { + // Destination SIT: distance between shipment destination address & service item ORIGINAL destination address + milesCalculated, err := p.planner.ZipTransitDistance(appCtx, mtoShipment.DestinationAddress.PostalCode, serviceItem.SITDestinationOriginalAddress.PostalCode) + if err != nil { + return nil, err + } + serviceItem.SITDeliveryMiles = &milesCalculated + } + + } + // Calculate SITDeliveryMiles for DOPSIT and DOSFSC origin SIT service items + if serviceItem.ReService.Code == models.ReServiceCodeDOPSIT || + serviceItem.ReService.Code == models.ReServiceCodeDOSFSC { + // Origin SIT: distance between shipment pickup address & service item ORIGINAL pickup address + milesCalculated, err := p.planner.ZipTransitDistance(appCtx, mtoShipment.PickupAddress.PostalCode, serviceItem.SITOriginHHGOriginalAddress.PostalCode) if err != nil { return nil, err } serviceItem.SITDeliveryMiles = &milesCalculated } } - // Calculate SITDeliveryMiles for DOPSIT and DOSFSC origin SIT service items - if serviceItem.ReService.Code == models.ReServiceCodeDOPSIT || - serviceItem.ReService.Code == models.ReServiceCodeDOSFSC { - // Origin SIT: distance between shipment pickup address & service item ORIGINAL pickup address - milesCalculated, err := p.planner.ZipTransitDistance(appCtx, mtoShipment.PickupAddress.PostalCode, serviceItem.SITOriginHHGOriginalAddress.PostalCode) - if err != nil { - return nil, err - } - serviceItem.SITDeliveryMiles = &milesCalculated - } } verrs, err := appCtx.DB().ValidateAndUpdate(&serviceItem) @@ -321,7 +324,7 @@ func (p *mtoServiceItemUpdater) UpdateMTOServiceItemPrime( // Authorized End Date and Required Delivery Date if (code == models.ReServiceCodeDOASIT || code == models.ReServiceCodeDDASIT) && updatedServiceItem.Status == models.MTOServiceItemStatusApproved { - err = calculateSITAuthorizedAndRequirededDates(appCtx, mtoServiceItem, shipment, planner) + err = calculateSITDates(appCtx, mtoServiceItem, shipment, planner) } } @@ -388,11 +391,10 @@ func calculateOriginSITRequiredDeliveryDate(appCtx appcontext.AppContext, shipme return &requiredDeliveryDate, nil } -// Calculate the Authorized End Date and the Required Delivery Date for the service item based on business logic using the +// Calculate the Required Delivery Date for the service item based on business logic using the // Customer Contact Date, Customer Requested Delivery Date, and SIT Departure Date -func calculateSITAuthorizedAndRequirededDates(appCtx appcontext.AppContext, serviceItem *models.MTOServiceItem, shipment models.MTOShipment, +func calculateSITDates(appCtx appcontext.AppContext, serviceItem *models.MTOServiceItem, shipment models.MTOShipment, planner route.Planner) error { - var verrs *validate.Errors location := DestinationSITLocation if serviceItem.ReService.Code == models.ReServiceCodeDOASIT { @@ -401,10 +403,6 @@ func calculateSITAuthorizedAndRequirededDates(appCtx appcontext.AppContext, serv sitDepartureDate := serviceItem.SITDepartureDate - // Calculate authorized end date and required delivery date based on sitCustomerContacted and sitRequestedDelivery - // using the below business logic. - sitAuthorizedEndDate := sitDepartureDate - if location == OriginSITLocation { if sitDepartureDate != nil { @@ -419,43 +417,13 @@ func calculateSITAuthorizedAndRequirededDates(appCtx appcontext.AppContext, serv } else { return apperror.NewNotFoundError(shipment.ID, "sit departure date not found, cannot update Required Delivery Date") } - - // Origin SIT: sitAuthorizedEndDate should be GracePeriodDays days after sitCustomerContacted or the sitDepartureDate whichever is earlier. - calculatedAuthorizedEndDate := serviceItem.SITCustomerContacted.AddDate(0, 0, GracePeriodDays) - - if calculatedAuthorizedEndDate.Before(*sitDepartureDate) { - sitAuthorizedEndDate = &calculatedAuthorizedEndDate - } - } else if location == DestinationSITLocation { - // Destination SIT: sitAuthorizedEndDate should be GracePeriodDays days after sitRequestedDelivery or the sitDepartureDate whichever is earlier. - calculatedAuthorizedEndDate := serviceItem.SITRequestedDelivery.AddDate(0, 0, GracePeriodDays) - - if calculatedAuthorizedEndDate.Before(*sitDepartureDate) { - sitAuthorizedEndDate = &calculatedAuthorizedEndDate - } - } - - // We retrieve the old service item so we can get the required values to update with the new value for Authorized End Date - oldServiceItem, err := models.FetchServiceItem(appCtx.DB(), serviceItem.ID) - if err != nil { - switch err { - case models.ErrFetchNotFound: - return apperror.NewNotFoundError(serviceItem.ID, "while looking for MTOServiceItem") - default: - return apperror.NewQueryError("MTOServiceItem", err, "") - } - } - - sitEndDate := oldServiceItem.SITEntryDate.AddDate(0, 0, *shipment.SITDaysAllowance) - - if (oldServiceItem.SITAuthorizedEndDate == nil && sitAuthorizedEndDate.After(sitEndDate)) || - (oldServiceItem.SITAuthorizedEndDate != nil && sitAuthorizedEndDate.After(*oldServiceItem.SITAuthorizedEndDate)) { - return apperror.NewUnprocessableEntityError("dates entered cannot extend authorized end date beyond its current date") } // For Origin SIT we need to update the Required Delivery Date which is stored with the shipment instead of the service item if location == OriginSITLocation { - verrs, err = appCtx.DB().ValidateAndUpdate(&shipment) + var verrs *validate.Errors + + verrs, err := appCtx.DB().ValidateAndUpdate(&shipment) if verrs != nil && verrs.HasAny() { return apperror.NewInvalidInputError(shipment.ID, err, verrs, "invalid input found while updating dates of shipment") @@ -464,15 +432,6 @@ func calculateSITAuthorizedAndRequirededDates(appCtx appcontext.AppContext, serv } } - oldServiceItem.SITAuthorizedEndDate = sitAuthorizedEndDate - verrs, err = appCtx.DB().ValidateAndUpdate(&oldServiceItem) - - if verrs != nil && verrs.HasAny() { - return apperror.NewInvalidInputError(oldServiceItem.ID, err, verrs, "invalid input found while updating the sit service item") - } else if err != nil { - return apperror.NewQueryError("Service item", err, "") - } - return nil } 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 44336a1d86e..5cdfd26b8d8 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 @@ -709,147 +709,6 @@ func (suite *MTOServiceItemServiceSuite) TestMTOServiceItemUpdater() { suite.Error(err) suite.IsType(apperror.UnprocessableEntityError{}, err) }) - - suite.Run("failure test for sit delivery date after authorized end date", func() { - now := time.Now() - requestApproavalsRequestedStatus := false - year, month, day := now.Add(time.Hour * 24 * -30).Date() - aMonthAgo := time.Date(year, month, day, 0, 0, 0, 0, time.UTC) - contactDatePlusGracePeriod := now.AddDate(0, 0, GracePeriodDays) - sitRequestedDelivery := time.Now().AddDate(0, 0, 10) - sitAuthorizedEndDate := time.Now().AddDate(0, 0, 5) - oldServiceItemPrime := factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ - { - Model: factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil), - LinkOnly: true, - }, - { - Model: models.ReService{ - Code: models.ReServiceCodeDOASIT, - }, - }, - { - Model: models.MTOServiceItem{ - SITDepartureDate: &contactDatePlusGracePeriod, - SITEntryDate: &aMonthAgo, - SITCustomerContacted: &now, - SITRequestedDelivery: &sitRequestedDelivery, - SITAuthorizedEndDate: &sitAuthorizedEndDate, - Status: "REJECTED", - RequestedApprovalsRequestedStatus: &requestApproavalsRequestedStatus, - }, - }, - }, nil) - - planner := &mocks.Planner{} - planner.On("ZipTransitDistance", - mock.AnythingOfType("*appcontext.appContext"), - mock.Anything, - mock.Anything, - ).Return(1234, nil) - - ghcDomesticTransitTime := models.GHCDomesticTransitTime{ - MaxDaysTransitTime: 12, - WeightLbsLower: 0, - WeightLbsUpper: 10000, - DistanceMilesLower: 1, - DistanceMilesUpper: 2000, - } - _, _ = suite.DB().ValidateAndCreate(&ghcDomesticTransitTime) - eTag := etag.GenerateEtag(oldServiceItemPrime.UpdatedAt) - - newServiceItemPrime := oldServiceItemPrime - newServiceItemPrime.Status = models.MTOServiceItemStatusApproved - shipmentSITAllowance := int(90) - estimatedWeight := unit.Pound(1400) - shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ - { - Model: models.MTOShipment{ - Status: models.MTOShipmentStatusApproved, - SITDaysAllowance: &shipmentSITAllowance, - PrimeEstimatedWeight: &estimatedWeight, - RequiredDeliveryDate: &aMonthAgo, - UpdatedAt: aMonthAgo, - }, - }, - }, nil) - shipment.MTOServiceItems = append(shipment.MTOServiceItems, newServiceItemPrime) - - _, err := updater.UpdateMTOServiceItemPrime(suite.AppContextForTest(), &newServiceItemPrime, planner, shipment, eTag) - - suite.Error(err) - suite.IsType(apperror.UnprocessableEntityError{}, err) - }) - - suite.Run("failure test for sit delivery date after allowance end date", func() { - now := time.Now() - requestApproavalsRequestedStatus := false - year, month, day := now.Add(time.Hour * 24 * -30).Date() - aMonthAgo := time.Date(year, month, day, 0, 0, 0, 0, time.UTC) - sitEntryDate := now.AddDate(0, 0, -120) - sitCustomerContact := now.AddDate(0, 0, 120) - sitRequestedDelivery := time.Now().AddDate(0, 0, 10) - oldServiceItemPrime := factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ - { - Model: factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil), - LinkOnly: true, - }, - { - Model: models.ReService{ - Code: models.ReServiceCodeDOASIT, - }, - }, - { - Model: models.MTOServiceItem{ - SITDepartureDate: &now, - SITEntryDate: &sitEntryDate, - SITCustomerContacted: &sitCustomerContact, - SITRequestedDelivery: &sitRequestedDelivery, - Status: "REJECTED", - RequestedApprovalsRequestedStatus: &requestApproavalsRequestedStatus, - }, - }, - }, nil) - - planner := &mocks.Planner{} - planner.On("ZipTransitDistance", - mock.AnythingOfType("*appcontext.appContext"), - mock.Anything, - mock.Anything, - ).Return(1234, nil) - - ghcDomesticTransitTime := models.GHCDomesticTransitTime{ - MaxDaysTransitTime: 12, - WeightLbsLower: 0, - WeightLbsUpper: 10000, - DistanceMilesLower: 1, - DistanceMilesUpper: 2000, - } - _, _ = suite.DB().ValidateAndCreate(&ghcDomesticTransitTime) - eTag := etag.GenerateEtag(oldServiceItemPrime.UpdatedAt) - - newServiceItemPrime := oldServiceItemPrime - newServiceItemPrime.Status = models.MTOServiceItemStatusApproved - shipmentSITAllowance := int(90) - estimatedWeight := unit.Pound(1400) - shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ - { - Model: models.MTOShipment{ - Status: models.MTOShipmentStatusApproved, - SITDaysAllowance: &shipmentSITAllowance, - PrimeEstimatedWeight: &estimatedWeight, - RequiredDeliveryDate: &aMonthAgo, - UpdatedAt: aMonthAgo, - }, - }, - }, nil) - shipment.MTOServiceItems = append(shipment.MTOServiceItems, newServiceItemPrime) - - _, err := updater.UpdateMTOServiceItemPrime(suite.AppContextForTest(), &newServiceItemPrime, planner, shipment, eTag) - - suite.Error(err) - suite.IsType(apperror.UnprocessableEntityError{}, err) - }) } func (suite *MTOServiceItemServiceSuite) TestValidateUpdateMTOServiceItem() { diff --git a/pkg/services/mto_shipment.go b/pkg/services/mto_shipment.go index 54fe37ab263..5bb0e039197 100644 --- a/pkg/services/mto_shipment.go +++ b/pkg/services/mto_shipment.go @@ -140,7 +140,6 @@ type CurrentSIT struct { SITEntryDate time.Time SITDepartureDate *time.Time SITAllowanceEndDate time.Time - SITAuthorizedEndDate *time.Time SITCustomerContacted *time.Time SITRequestedDelivery *time.Time } diff --git a/pkg/services/ppmshipment/ppm_shipment_updater.go b/pkg/services/ppmshipment/ppm_shipment_updater.go index 3e58400912b..cf9753a406e 100644 --- a/pkg/services/ppmshipment/ppm_shipment_updater.go +++ b/pkg/services/ppmshipment/ppm_shipment_updater.go @@ -53,7 +53,10 @@ func (f *ppmShipmentUpdater) updatePPMShipment(appCtx appcontext.AppContext, ppm return nil, err } - updatedPPMShipment := mergePPMShipment(*ppmShipment, oldPPMShipment) + updatedPPMShipment, err := mergePPMShipment(*ppmShipment, oldPPMShipment) + if err != nil { + return nil, err + } err = validatePPMShipment(appCtx, *updatedPPMShipment, oldPPMShipment, &oldPPMShipment.Shipment, checks...) if err != nil { diff --git a/pkg/services/ppmshipment/ppm_shipment_updater_test.go b/pkg/services/ppmshipment/ppm_shipment_updater_test.go index 5ed72d560c7..728b8f08366 100644 --- a/pkg/services/ppmshipment/ppm_shipment_updater_test.go +++ b/pkg/services/ppmshipment/ppm_shipment_updater_test.go @@ -3,6 +3,7 @@ package ppmshipment import ( "errors" "fmt" + "time" "github.com/gofrs/uuid" "github.com/stretchr/testify/mock" @@ -777,15 +778,17 @@ func (suite *PPMShipmentSuite) TestUpdatePPMShipment() { suite.Equal(*newFakeSITEstimatedCost, *updatedPPM.SITEstimatedCost) }) - suite.Run("Can successfully update a PPMShipment - final incentive", func() { + suite.Run("Can successfully update a PPMShipment - final incentive and actual move date", func() { appCtx := suite.AppContextWithSessionForTest(&auth.Session{}) subtestData := setUpForFinalIncentiveTests(nil, nil, nil, nil, nil) + today := time.Now() + originalPPM := factory.BuildMinimalPPMShipment(appCtx.DB(), []factory.Customization{ { Model: models.PPMShipment{ - ActualMoveDate: models.TimePointer(testdatagen.NextValidMoveDate), + ActualMoveDate: &today, ActualPickupPostalCode: models.StringPointer("79912"), ActualDestinationPostalCode: models.StringPointer("90909"), EstimatedWeight: models.PoundPointer(unit.Pound(5000)), @@ -803,6 +806,7 @@ func (suite *PPMShipmentSuite) TestUpdatePPMShipment() { // Fields that should now be updated suite.Equal(newPPM.FinalIncentive, updatedPPM.FinalIncentive) + suite.Equal(newPPM.ActualMoveDate, updatedPPM.ActualMoveDate) }) suite.Run("Can't update if Shipment can't be found", func() { diff --git a/pkg/services/ppmshipment/validation.go b/pkg/services/ppmshipment/validation.go index 689a38c0480..2b393c7de5c 100644 --- a/pkg/services/ppmshipment/validation.go +++ b/pkg/services/ppmshipment/validation.go @@ -1,6 +1,8 @@ package ppmshipment import ( + "time" + "github.com/gobuffalo/validate/v3" "github.com/gofrs/uuid" @@ -55,14 +57,20 @@ func (fn ppmShipmentValidatorFunc) Validate(appCtx appcontext.AppContext, newer return fn(appCtx, newer, older, ship) } -func mergePPMShipment(newPPMShipment models.PPMShipment, oldPPMShipment *models.PPMShipment) *models.PPMShipment { +func mergePPMShipment(newPPMShipment models.PPMShipment, oldPPMShipment *models.PPMShipment) (*models.PPMShipment, error) { + var err error if oldPPMShipment == nil { - return &newPPMShipment + return &newPPMShipment, nil } ppmShipment := *oldPPMShipment - ppmShipment.ActualMoveDate = services.SetOptionalDateTimeField(newPPMShipment.ActualMoveDate, ppmShipment.ActualMoveDate) + today := time.Now() + if newPPMShipment.ActualMoveDate != nil && today.Before(*newPPMShipment.ActualMoveDate) { + err = apperror.NewUpdateError(ppmShipment.ID, "Actual move date cannot be set to the future.") + } else { + ppmShipment.ActualMoveDate = services.SetOptionalDateTimeField(newPPMShipment.ActualMoveDate, ppmShipment.ActualMoveDate) + } ppmShipment.SecondaryPickupPostalCode = services.SetOptionalStringField(newPPMShipment.SecondaryPickupPostalCode, ppmShipment.SecondaryPickupPostalCode) ppmShipment.ActualPickupPostalCode = services.SetOptionalStringField(newPPMShipment.ActualPickupPostalCode, ppmShipment.ActualPickupPostalCode) @@ -177,5 +185,5 @@ func mergePPMShipment(newPPMShipment models.PPMShipment, oldPPMShipment *models. ppmShipment.WeightTickets = newPPMShipment.WeightTickets } - return &ppmShipment + return &ppmShipment, err } diff --git a/pkg/services/ppmshipment/validation_test.go b/pkg/services/ppmshipment/validation_test.go index 92b5f8ac36d..ced938cd4ae 100644 --- a/pkg/services/ppmshipment/validation_test.go +++ b/pkg/services/ppmshipment/validation_test.go @@ -31,9 +31,13 @@ func (suite *PPMShipmentSuite) TestMergePPMShipment() { hasReceivedAdvance bool hasSecondaryPickupAddress bool hasSecondaryDestinationAddress bool + hasActualMoveDate bool } var ( + today = time.Now() + futureDate = today.AddDate(0, 0, 2) + expectedSecondaryPickupAddress = &models.Address{ StreetAddress1: "123 Secondary Pickup", City: "New York", @@ -59,6 +63,7 @@ func (suite *PPMShipmentSuite) TestMergePPMShipment() { ID: id, ShipmentID: shipmentID, Status: models.PPMShipmentStatusDraft, + ActualMoveDate: &today, ExpectedDepartureDate: time.Date(2020, time.March, 15, 0, 0, 0, 0, time.UTC), PickupPostalCode: "90210", DestinationPostalCode: "08004", @@ -818,6 +823,16 @@ func (suite *PPMShipmentSuite) TestMergePPMShipment() { suite.True(mergedShipment.SecondaryDestinationAddressID == nil) }, }, + "attempt to update actual move date with invalid date": { + oldFlags: flags{ + hasActualMoveDate: true, + }, + newShipment: models.PPMShipment{ + ActualMoveDate: &futureDate, + }, + runChecks: func(mergedShipment models.PPMShipment, oldShipment models.PPMShipment, newShipment models.PPMShipment) { + }, + }, } for name, tc := range mergeTestCases { @@ -827,13 +842,17 @@ func (suite *PPMShipmentSuite) TestMergePPMShipment() { suite.Run(fmt.Sprintf("Can merge changes - %s", name), func() { oldShipment := setupShipmentData(tc.oldState, tc.oldFlags) - mergedShipment := mergePPMShipment(tc.newShipment, &oldShipment) + mergedShipment, err := mergePPMShipment(tc.newShipment, &oldShipment) // these should never change suite.Equal(oldShipment.ID, mergedShipment.ID) suite.Equal(oldShipment.ShipmentID, mergedShipment.ShipmentID) suite.Equal(oldShipment.Status, mergedShipment.Status) + if tc.oldFlags.hasActualMoveDate { + suite.Equal(err.Error(), "Update Error Actual move date cannot be set to the future.") + } + // now run test case specific checks tc.runChecks(*mergedShipment, oldShipment, tc.newShipment) }) diff --git a/pkg/services/shipment_summary_worksheet.go b/pkg/services/shipment_summary_worksheet.go index f44c94b7877..c0785f009b3 100644 --- a/pkg/services/shipment_summary_worksheet.go +++ b/pkg/services/shipment_summary_worksheet.go @@ -34,7 +34,7 @@ type Page1Values struct { AuthorizedDestination string NewDutyAssignment string WeightAllotment string - WeightAllotmentProgear string + WeightAllotmentProGear string WeightAllotmentProgearSpouse string TotalWeightAllotment string POVAuthorized string @@ -63,10 +63,39 @@ type Page1Values struct { // Page2Values is an object representing a Shipment Summary Worksheet type Page2Values struct { - CUIBanner string - PreparationDate string - TAC string - SAC string + CUIBanner string + PreparationDate string + TAC string + SAC string + ContractedExpenseMemberPaid string + ContractedExpenseGTCCPaid string + RentalEquipmentMemberPaid string + RentalEquipmentGTCCPaid string + PackingMaterialsMemberPaid string + PackingMaterialsGTCCPaid string + WeighingFeesMemberPaid string + WeighingFeesGTCCPaid string + GasMemberPaid string + GasGTCCPaid string + TollsMemberPaid string + TollsGTCCPaid string + OilMemberPaid string + OilGTCCPaid string + OtherMemberPaid string + OtherGTCCPaid string + TotalMemberPaid string + TotalGTCCPaid string + TotalMemberPaidRepeated string + TotalGTCCPaidRepeated string + TotalPaidNonSIT string + TotalMemberPaidSIT string + TotalGTCCPaidSIT string + TotalPaidSIT string + ShipmentPickupDates string + TrustedAgentName string + TrustedAgentDate string + TrustedAgentEmail string + TrustedAgentPhone string FormattedMovingExpenses ServiceMemberSignature string SignatureDate string @@ -81,47 +110,50 @@ type FormattedOtherExpenses struct { // FormattedMovingExpenses is an object representing the service member's moving expenses formatted for the SSW type FormattedMovingExpenses struct { - ContractedExpenseMemberPaid Dollar - ContractedExpenseGTCCPaid Dollar - RentalEquipmentMemberPaid Dollar - RentalEquipmentGTCCPaid Dollar - PackingMaterialsMemberPaid Dollar - PackingMaterialsGTCCPaid Dollar - WeighingFeesMemberPaid Dollar - WeighingFeesGTCCPaid Dollar - GasMemberPaid Dollar - GasGTCCPaid Dollar - TollsMemberPaid Dollar - TollsGTCCPaid Dollar - OilMemberPaid Dollar - OilGTCCPaid Dollar - OtherMemberPaid Dollar - OtherGTCCPaid Dollar - TotalMemberPaid Dollar - TotalGTCCPaid Dollar - TotalMemberPaidRepeated Dollar - TotalGTCCPaidRepeated Dollar - TotalPaidNonSIT Dollar - TotalMemberPaidSIT Dollar - TotalGTCCPaidSIT Dollar - TotalPaidSIT Dollar + ContractedExpenseMemberPaid string + ContractedExpenseGTCCPaid string + RentalEquipmentMemberPaid string + RentalEquipmentGTCCPaid string + PackingMaterialsMemberPaid string + PackingMaterialsGTCCPaid string + WeighingFeesMemberPaid string + WeighingFeesGTCCPaid string + GasMemberPaid string + GasGTCCPaid string + TollsMemberPaid string + TollsGTCCPaid string + OilMemberPaid string + OilGTCCPaid string + OtherMemberPaid string + OtherGTCCPaid string + TotalMemberPaid string + TotalGTCCPaid string + TotalMemberPaidRepeated string + TotalGTCCPaidRepeated string + TotalPaidNonSIT string + TotalMemberPaidSIT string + TotalGTCCPaidSIT string + TotalPaidSIT string } // ShipmentSummaryFormData is a container for the various objects required for the a Shipment Summary Worksheet type ShipmentSummaryFormData struct { - ServiceMember models.ServiceMember - Order models.Order - Move models.Move - CurrentDutyLocation models.DutyLocation - NewDutyLocation models.DutyLocation - WeightAllotment SSWMaxWeightEntitlement - PPMShipments models.PPMShipments - W2Address *models.Address - PreparationDate time.Time - Obligations Obligations - MovingExpenses models.MovingExpenses - PPMRemainingEntitlement unit.Pound - SignedCertification models.SignedCertification + ServiceMember models.ServiceMember + Order models.Order + Move models.Move + CurrentDutyLocation models.DutyLocation + NewDutyLocation models.DutyLocation + WeightAllotment SSWMaxWeightEntitlement + PPMShipment models.PPMShipment + PPMShipments models.PPMShipments + W2Address *models.Address + PreparationDate time.Time + Obligations Obligations + MovingExpenses models.MovingExpenses + MTOAgents models.MTOAgents + PPMRemainingEntitlement unit.Pound + SignedCertification models.SignedCertification + MaxSITStorageEntitlement int } // Obligations is an object representing the winning and non-winning Max Obligation and Actual Obligation sections of the shipment summary worksheet diff --git a/pkg/services/shipment_summary_worksheet/shipment_summary_worksheet.go b/pkg/services/shipment_summary_worksheet/shipment_summary_worksheet.go index b001dde362f..bcc9825735c 100644 --- a/pkg/services/shipment_summary_worksheet/shipment_summary_worksheet.go +++ b/pkg/services/shipment_summary_worksheet/shipment_summary_worksheet.go @@ -75,55 +75,21 @@ type textField struct { var newline = "\n\n" -// Page1Values is an object representing a Shipment Summary Worksheet -type Page1Values struct { - CUIBanner string - ServiceMemberName string - MaxSITStorageEntitlement string - PreferredPhoneNumber string - PreferredEmail string - DODId string - ServiceBranch string - RankGrade string - IssuingBranchOrAgency string - OrdersIssueDate string - OrdersTypeAndOrdersNumber string - AuthorizedOrigin string - AuthorizedDestination string - NewDutyAssignment string - WeightAllotment string - WeightAllotmentProgear string - WeightAllotmentProgearSpouse string - TotalWeightAllotment string - POVAuthorized string - ShipmentNumberAndTypes string - ShipmentPickUpDates string - ShipmentWeights string - ShipmentCurrentShipmentStatuses string - SITNumberAndTypes string - SITEntryDates string - SITEndDates string - SITDaysInStorage string - PreparationDate string - MaxObligationGCC100 string - TotalWeightAllotmentRepeat string - MaxObligationGCC95 string - MaxObligationSIT string - MaxObligationGCCMaxAdvance string - PPMRemainingEntitlement string - ActualObligationGCC100 string - ActualObligationGCC95 string - ActualObligationAdvance string - ActualObligationSIT string - MileageTotal string -} - // WorkSheetShipments is an object representing shipment line items on Shipment Summary Worksheet type WorkSheetShipments struct { - ShipmentNumberAndTypes string - PickUpDates string - ShipmentWeights string - CurrentShipmentStatuses string + ShipmentNumberAndTypes string + PickUpDates string + ShipmentWeights string + ShipmentWeightForObligation string + CurrentShipmentStatuses string +} + +// WorkSheetShipment is an object representing specific shipment items on Shipment Summary Worksheet +type WorkSheetShipment struct { + EstimatedIncentive string + MaxAdvance string + FinalIncentive string + AdvanceAmountReceived string } // WorkSheetSIT is an object representing SIT on the Shipment Summary Worksheet @@ -134,15 +100,6 @@ type WorkSheetSIT struct { DaysInStorage string } -// Page2Values is an object representing a Shipment Summary Worksheet -type Page2Values struct { - CUIBanner string - PreparationDate string - TAC string - SAC string - FormattedMovingExpenses -} - // Dollar represents a type for dollar monetary unit type Dollar float64 @@ -152,73 +109,6 @@ func (d Dollar) String() string { return p.Sprintf("$%.2f", d) } -// FormattedMovingExpenses is an object representing the service member's moving expenses formatted for the SSW -type FormattedMovingExpenses struct { - ContractedExpenseMemberPaid Dollar - ContractedExpenseGTCCPaid Dollar - RentalEquipmentMemberPaid Dollar - RentalEquipmentGTCCPaid Dollar - PackingMaterialsMemberPaid Dollar - PackingMaterialsGTCCPaid Dollar - WeighingFeesMemberPaid Dollar - WeighingFeesGTCCPaid Dollar - GasMemberPaid Dollar - GasGTCCPaid Dollar - TollsMemberPaid Dollar - TollsGTCCPaid Dollar - OilMemberPaid Dollar - OilGTCCPaid Dollar - OtherMemberPaid Dollar - OtherGTCCPaid Dollar - TotalMemberPaid Dollar - TotalGTCCPaid Dollar - TotalMemberPaidRepeated Dollar - TotalGTCCPaidRepeated Dollar - TotalPaidNonSIT Dollar - TotalMemberPaidSIT Dollar - TotalGTCCPaidSIT Dollar - TotalPaidSIT Dollar -} - -// FormattedOtherExpenses is an object representing the other moving expenses formatted for the SSW -type FormattedOtherExpenses struct { - Descriptions string - AmountsPaid string -} - -// Page3Values is an object representing a Shipment Summary Worksheet -type Page3Values struct { - CUIBanner string - PreparationDate string - ServiceMemberSignature string - SignatureDate string - FormattedOtherExpenses -} - -// ShipmentSummaryFormData is a container for the various objects required for the a Shipment Summary Worksheet -type ShipmentSummaryFormData struct { - ServiceMember models.ServiceMember - Order models.Order - Move models.Move - CurrentDutyLocation models.DutyLocation - NewDutyLocation models.DutyLocation - WeightAllotment SSWMaxWeightEntitlement - PPMShipments models.PPMShipments - PreparationDate time.Time - Obligations Obligations - MovingExpenses models.MovingExpenses - PPMRemainingEntitlement unit.Pound - SignedCertification models.SignedCertification -} - -// Obligations is an object representing the winning and non-winning Max Obligation and Actual Obligation sections of the shipment summary worksheet -type Obligations struct { - MaxObligation Obligation - ActualObligation Obligation - NonWinningMaxObligation Obligation - NonWinningActualObligation Obligation -} - // Obligation an object representing the obligations section on the shipment summary worksheet type Obligation struct { Gcc unit.Cents @@ -254,6 +144,13 @@ type SSWMaxWeightEntitlement struct { TotalWeight unit.Pound } +type Agent struct { + Name string + Email string + Date string + Phone string +} + // adds a line item to shipment summary worksheet SSWMaxWeightEntitlement and increments total allotment func (wa *SSWMaxWeightEntitlement) addLineItem(field string, value int) { r := reflect.ValueOf(wa).Elem() @@ -307,7 +204,7 @@ const ( func FormatValuesShipmentSummaryWorksheetFormPage1(data services.ShipmentSummaryFormData) services.Page1Values { page1 := services.Page1Values{} page1.CUIBanner = controlledUnclassifiedInformationText - page1.MaxSITStorageEntitlement = "90 days per each shipment" + page1.MaxSITStorageEntitlement = fmt.Sprintf("%02d Days in SIT", data.MaxSITStorageEntitlement) // We don't currently know what allows POV to be authorized, so we are hardcoding it to "No" to start page1.POVAuthorized = "No" page1.PreparationDate = FormatDate(data.PreparationDate) @@ -330,7 +227,7 @@ func FormatValuesShipmentSummaryWorksheetFormPage1(data services.ShipmentSummary page1.NewDutyAssignment = data.NewDutyLocation.Name page1.WeightAllotment = FormatWeights(data.WeightAllotment.Entitlement) - page1.WeightAllotmentProgear = FormatWeights(data.WeightAllotment.ProGear) + page1.WeightAllotmentProGear = FormatWeights(data.WeightAllotment.ProGear) page1.WeightAllotmentProgearSpouse = FormatWeights(data.WeightAllotment.SpouseProGear) page1.TotalWeightAllotment = FormatWeights(data.WeightAllotment.TotalWeight) @@ -339,17 +236,20 @@ func FormatValuesShipmentSummaryWorksheetFormPage1(data services.ShipmentSummary page1.ShipmentPickUpDates = formattedShipments.PickUpDates page1.ShipmentCurrentShipmentStatuses = formattedShipments.CurrentShipmentStatuses formattedSIT := FormatAllSITS(data.PPMShipments) - + formattedShipment := FormatCurrentShipment(data.PPMShipment) page1.SITDaysInStorage = formattedSIT.DaysInStorage page1.SITEntryDates = formattedSIT.EntryDates page1.SITEndDates = formattedSIT.EndDates - // page1.SITNumberAndTypes + page1.SITNumberAndTypes = formattedShipments.ShipmentNumberAndTypes page1.ShipmentWeights = formattedShipments.ShipmentWeights - // Obligations cannot be used at this time, require new computer setup. + page1.MaxObligationGCC100 = FormatWeights(data.WeightAllotment.TotalWeight) + " lbs; " + formattedShipment.EstimatedIncentive + page1.ActualObligationGCC100 = formattedShipments.ShipmentWeightForObligation + " lbs; " + formattedShipment.FinalIncentive + page1.MaxObligationGCCMaxAdvance = formattedShipment.MaxAdvance + page1.ActualObligationAdvance = formattedShipment.AdvanceAmountReceived + page1.MaxObligationSIT = fmt.Sprintf("%02d Days in SIT", data.MaxSITStorageEntitlement) + page1.ActualObligationSIT = formattedSIT.DaysInStorage page1.TotalWeightAllotmentRepeat = page1.TotalWeightAllotment - actualObligations := data.Obligations.ActualObligation page1.PPMRemainingEntitlement = FormatWeights(data.PPMRemainingEntitlement) - page1.MileageTotal = actualObligations.Miles.String() return page1 } @@ -394,18 +294,95 @@ func FormatGrade(grade *internalmessages.OrderPayGrade) string { // FormatValuesShipmentSummaryWorksheetFormPage2 formats the data for page 2 of the Shipment Summary Worksheet func FormatValuesShipmentSummaryWorksheetFormPage2(data services.ShipmentSummaryFormData) services.Page2Values { + + expensesMap := SubTotalExpenses(data.MovingExpenses) + agentInfo := FormatAgentInfo(data.MTOAgents) + formattedShipments := FormatAllShipments(data.PPMShipments) + page2 := services.Page2Values{} page2.CUIBanner = controlledUnclassifiedInformationText page2.TAC = derefStringTypes(data.Order.TAC) page2.SAC = derefStringTypes(data.Order.SAC) page2.PreparationDate = FormatDate(data.PreparationDate) + page2.ContractedExpenseMemberPaid = FormatDollars(expensesMap["ContractedExpenseMemberPaid"]) + page2.ContractedExpenseGTCCPaid = FormatDollars(expensesMap["ContractedExpenseGTCCPaid"]) + page2.PackingMaterialsMemberPaid = FormatDollars(expensesMap["PackingMaterialsMemberPaid"]) + page2.PackingMaterialsGTCCPaid = FormatDollars(expensesMap["PackingMaterialsGTCCPaid"]) + page2.WeighingFeesMemberPaid = FormatDollars(expensesMap["WeighingFeeMemberPaid"]) + page2.WeighingFeesGTCCPaid = FormatDollars(expensesMap["WeighingFeeGTCCPaid"]) + page2.RentalEquipmentMemberPaid = FormatDollars(expensesMap["RentalEquipmentMemberPaid"]) + page2.RentalEquipmentGTCCPaid = FormatDollars(expensesMap["RentalEquipmentGTCCPaid"]) + page2.TollsMemberPaid = FormatDollars(expensesMap["TollsMemberPaid"]) + page2.TollsGTCCPaid = FormatDollars(expensesMap["TollsGTCCPaid"]) + page2.OilMemberPaid = FormatDollars(expensesMap["OilMemberPaid"]) + page2.OilGTCCPaid = FormatDollars(expensesMap["OilGTCCPaid"]) + page2.OtherMemberPaid = FormatDollars(expensesMap["OtherMemberPaid"]) + page2.OtherGTCCPaid = FormatDollars(expensesMap["OtherGTCCPaid"]) + page2.TotalMemberPaid = FormatDollars(expensesMap["TotalMemberPaid"]) + page2.TotalGTCCPaid = FormatDollars(expensesMap["TotalGTCCPaid"]) + page2.TotalMemberPaidRepeated = FormatDollars(expensesMap["TotalMemberPaid"]) + page2.TotalGTCCPaidRepeated = FormatDollars(expensesMap["TotalGTCCPaid"]) + page2.TotalMemberPaidSIT = FormatDollars(expensesMap["StorageMemberPaid"]) + page2.TotalGTCCPaidSIT = FormatDollars(expensesMap["StorageGTCCPaid"]) page2.TotalMemberPaidRepeated = page2.TotalMemberPaid page2.TotalGTCCPaidRepeated = page2.TotalGTCCPaid + page2.ShipmentPickupDates = formattedShipments.PickUpDates + page2.TrustedAgentName = agentInfo.Name + page2.TrustedAgentDate = agentInfo.Date + page2.TrustedAgentEmail = agentInfo.Email + page2.TrustedAgentPhone = agentInfo.Phone page2.ServiceMemberSignature = FormatSignature(data.ServiceMember) - page2.SignatureDate = FormatSignatureDate(data.SignedCertification) + page2.SignatureDate = FormatSignatureDate(data.SignedCertification.UpdatedAt) return page2 } +func formatMaxAdvance(estimatedIncentive *unit.Cents) string { + if estimatedIncentive != nil { + maxAdvance := float64(*estimatedIncentive) * 0.6 + return FormatDollars(maxAdvance / 100) + } + maxAdvanceString := "No Incentive Found" + return maxAdvanceString + +} + +func FormatAgentInfo(agentArray []models.MTOAgent) Agent { + agentObject := Agent{} + if len(agentArray) == 0 { + agentObject.Name = "No agent specified" + agentObject.Email = "No agent specified" + agentObject.Date = "No agent specified" + agentObject.Phone = "No agent specified" + return agentObject + } + + agent := agentArray[0] + + switch { + case agent.FirstName != nil && agent.LastName != nil: + agentObject.Name = fmt.Sprintf("%s, %s", *agent.LastName, *agent.FirstName) + case agent.FirstName == nil && agent.LastName == nil: + agentObject.Name = "No name specified" + case agent.FirstName == nil: + agentObject.Name = fmt.Sprintf("No first name provided, Last Name: %s", *agent.LastName) + case agent.LastName == nil: + agentObject.Name = fmt.Sprintf("First Name: %s, No last name provided", *agent.FirstName) + } + + agentObject.Email = getOrDefault(agent.Email, "No Email Specified") + agentObject.Phone = getOrDefault(agent.Phone, "No Phone Specified") + agentObject.Date = agent.UpdatedAt.Format("20060102") + + return agentObject +} + +func getOrDefault(value *string, defaultValue string) string { + if value != nil { + return *value + } + return defaultValue +} + // FormatSignature formats a service member's signature for the Shipment Summary Worksheet func FormatSignature(sm models.ServiceMember) string { first := derefStringTypes(sm.FirstName) @@ -415,9 +392,9 @@ func FormatSignature(sm models.ServiceMember) string { } // FormatSignatureDate formats the date the service member electronically signed for the Shipment Summary Worksheet -func FormatSignatureDate(signature models.SignedCertification) string { +func FormatSignatureDate(signature time.Time) string { dateLayout := "02 Jan 2006 at 3:04pm" - dt := signature.Date.Format(dateLayout) + dt := signature.Format(dateLayout) return dt } @@ -441,7 +418,7 @@ func FormatAddress(w2Address *models.Address) string { w2Address.PostalCode, ) } else { - return "" // Return an empty string if no W2 address + return "W2 Address not found" } return addressString @@ -467,6 +444,30 @@ func FormatServiceMemberFullName(serviceMember models.ServiceMember) string { return strings.TrimSpace(fmt.Sprintf("%s, %s %s", lastName, firstName, middleName)) } +func FormatCurrentShipment(ppm models.PPMShipment) WorkSheetShipment { + formattedShipment := WorkSheetShipment{} + + if ppm.FinalIncentive != nil { + formattedShipment.FinalIncentive = ppm.FinalIncentive.ToDollarString() + } else { + formattedShipment.FinalIncentive = "No final incentive." + } + if ppm.EstimatedIncentive != nil { + formattedShipment.MaxAdvance = formatMaxAdvance(ppm.EstimatedIncentive) + formattedShipment.EstimatedIncentive = ppm.EstimatedIncentive.ToDollarString() + } else { + formattedShipment.MaxAdvance = "Advance not available." + formattedShipment.EstimatedIncentive = "No estimated incentive." + } + if ppm.AdvanceAmountReceived != nil { + formattedShipment.AdvanceAmountReceived = ppm.AdvanceAmountReceived.ToDollarString() + } else { + formattedShipment.AdvanceAmountReceived = "No advance received." + } + + return formattedShipment +} + // FormatAllShipments formats Shipment line items for the Shipment Summary Worksheet func FormatAllShipments(ppms models.PPMShipments) WorkSheetShipments { totalShipments := len(ppms) @@ -475,6 +476,7 @@ func FormatAllShipments(ppms models.PPMShipments) WorkSheetShipments { formattedPickUpDates := make([]string, totalShipments) formattedShipmentWeights := make([]string, totalShipments) formattedShipmentStatuses := make([]string, totalShipments) + formattedShipmentTotalWeights := unit.Pound(0) var shipmentNumber int for _, ppm := range ppms { @@ -482,12 +484,16 @@ func FormatAllShipments(ppms models.PPMShipments) WorkSheetShipments { formattedPickUpDates[shipmentNumber] = FormatPPMPickupDate(ppm) formattedShipmentWeights[shipmentNumber] = FormatPPMWeight(ppm) formattedShipmentStatuses[shipmentNumber] = FormatCurrentPPMStatus(ppm) + if ppm.EstimatedWeight != nil { + formattedShipmentTotalWeights += *ppm.EstimatedWeight + } shipmentNumber++ } formattedShipments.ShipmentNumberAndTypes = strings.Join(formattedNumberAndTypes, newline) formattedShipments.PickUpDates = strings.Join(formattedPickUpDates, newline) formattedShipments.ShipmentWeights = strings.Join(formattedShipmentWeights, newline) + formattedShipments.ShipmentWeightForObligation = FormatWeights(formattedShipmentTotalWeights) formattedShipments.CurrentShipmentStatuses = strings.Join(formattedShipmentStatuses, newline) return formattedShipments } @@ -526,29 +532,36 @@ func FetchMovingExpensesShipmentSummaryWorksheet(PPMShipment models.PPMShipment, return movingExpenseDocuments, nil } -// SubTotalExpenses groups moving expenses by type and payment method func SubTotalExpenses(expenseDocuments models.MovingExpenses) map[string]float64 { - var expenseType string totals := make(map[string]float64) + for _, expense := range expenseDocuments { - expenseType = getExpenseType(expense) + expenseType, addToTotal := getExpenseType(expense) expenseDollarAmt := expense.Amount.ToDollarFloatNoRound() + totals[expenseType] += expenseDollarAmt - // addToGrandTotal(totals, expenseType, expenseDollarAmt) + + if addToTotal && expenseType != "Storage" { + if paidWithGTCC := expense.PaidWithGTCC; paidWithGTCC != nil && *paidWithGTCC { + totals["TotalGTCCPaid"] += expenseDollarAmt + } else { + totals["TotalMemberPaid"] += expenseDollarAmt + } + } } + return totals } -func getExpenseType(expense models.MovingExpense) string { +func getExpenseType(expense models.MovingExpense) (string, bool) { expenseType := FormatEnum(string(*expense.MovingExpenseType), "") - paidWithGTCC := expense.PaidWithGTCC - if paidWithGTCC != nil { - if *paidWithGTCC { - return fmt.Sprintf("%s%s", expenseType, "GTCCPaid") - } + addToTotal := expenseType != "Storage" + + if paidWithGTCC := expense.PaidWithGTCC; paidWithGTCC != nil && *paidWithGTCC { + return fmt.Sprintf("%s%s", expenseType, "GTCCPaid"), addToTotal } - return fmt.Sprintf("%s%s", expenseType, "MemberPaid") + return fmt.Sprintf("%s%s", expenseType, "MemberPaid"), addToTotal } // FormatCurrentPPMStatus formats FormatCurrentPPMStatus for the Shipment Summary Worksheet @@ -695,10 +708,12 @@ func (SSWPPMComputer *SSWPPMComputer) FetchDataShipmentSummaryWorksheetFormData( ppmShipment := models.PPMShipment{} dbQErr := appCtx.DB().Q().Eager( "Shipment.MoveTaskOrder.Orders.ServiceMember", - "Shipment.MoveTaskOrder", - "Shipment.MoveTaskOrder.Orders", "Shipment.MoveTaskOrder.Orders.NewDutyLocation.Address", "Shipment.MoveTaskOrder.Orders.OriginDutyLocation.Address", + "Shipment.MTOAgents", + "W2Address", + "SignedCertification", + "MovingExpenses", ).Find(&ppmShipment, ppmShipmentID) if dbQErr != nil { @@ -714,21 +729,18 @@ func (SSWPPMComputer *SSWPPMComputer) FetchDataShipmentSummaryWorksheetFormData( } weightAllotment := SSWGetEntitlement(*ppmShipment.Shipment.MoveTaskOrder.Orders.Grade, ppmShipment.Shipment.MoveTaskOrder.Orders.HasDependents, ppmShipment.Shipment.MoveTaskOrder.Orders.SpouseHasProGear) - ppmRemainingEntitlement, err := CalculateRemainingPPMEntitlement(ppmShipment.Shipment.MoveTaskOrder, weightAllotment.TotalWeight) if err != nil { return nil, err } - // Signed Certification needs to be updated - // signedCertification, err := models.FetchSignedCertificationsPPMPayment(appCtx.DB(), session, ppmShipment.Shipment.MoveTaskOrderID) - // if err != nil { - // return ShipmentSummaryFormData{}, err - // } - // if signedCertification == nil { - // return ShipmentSummaryFormData{}, - // errors.New("shipment summary worksheet: signed certification is nil") - // } + maxSit, err := CalculateShipmentSITAllowance(appCtx, ppmShipment.Shipment) + if err != nil { + return nil, err + } + + // DOES NOT INCLUDE PPPO/PPSO SIGNATURE + signedCertification := ppmShipment.SignedCertification var ppmShipments []models.PPMShipment @@ -737,20 +749,54 @@ func (SSWPPMComputer *SSWPPMComputer) FetchDataShipmentSummaryWorksheetFormData( return nil, errors.New("order for PPM shipment does not have a origin duty location attached") } ssd := services.ShipmentSummaryFormData{ - ServiceMember: serviceMember, - Order: ppmShipment.Shipment.MoveTaskOrder.Orders, - Move: ppmShipment.Shipment.MoveTaskOrder, - CurrentDutyLocation: *ppmShipment.Shipment.MoveTaskOrder.Orders.OriginDutyLocation, - NewDutyLocation: ppmShipment.Shipment.MoveTaskOrder.Orders.NewDutyLocation, - WeightAllotment: weightAllotment, - PPMShipments: ppmShipments, - W2Address: ppmShipment.W2Address, - // SignedCertification: *signedCertification, - PPMRemainingEntitlement: ppmRemainingEntitlement, + ServiceMember: serviceMember, + Order: ppmShipment.Shipment.MoveTaskOrder.Orders, + Move: ppmShipment.Shipment.MoveTaskOrder, + CurrentDutyLocation: *ppmShipment.Shipment.MoveTaskOrder.Orders.OriginDutyLocation, + NewDutyLocation: ppmShipment.Shipment.MoveTaskOrder.Orders.NewDutyLocation, + WeightAllotment: weightAllotment, + PPMShipment: ppmShipment, + PPMShipments: ppmShipments, + W2Address: ppmShipment.W2Address, + MovingExpenses: ppmShipment.MovingExpenses, + MTOAgents: ppmShipment.Shipment.MTOAgents, + SignedCertification: *signedCertification, + PPMRemainingEntitlement: ppmRemainingEntitlement, + MaxSITStorageEntitlement: maxSit, } return &ssd, nil } +// CalculateShipmentSITAllowance finds the number of days allowed in SIT for a shipment based on its entitlement and any approved SIT extensions +func CalculateShipmentSITAllowance(appCtx appcontext.AppContext, shipment models.MTOShipment) (int, error) { + entitlement, err := fetchEntitlement(appCtx, shipment) + if err != nil { + return 0, err + } + + totalSITAllowance := 0 + if entitlement.StorageInTransit != nil { + totalSITAllowance = *entitlement.StorageInTransit + } + for _, ext := range shipment.SITDurationUpdates { + if ext.ApprovedDays != nil { + totalSITAllowance += *ext.ApprovedDays + } + } + return totalSITAllowance, nil +} + +func fetchEntitlement(appCtx appcontext.AppContext, mtoShipment models.MTOShipment) (*models.Entitlement, error) { + var move models.Move + err := appCtx.DB().Q().EagerPreload("Orders.Entitlement").Find(&move, mtoShipment.MoveTaskOrderID) + + if err != nil { + return nil, err + } + + return move.Orders.Entitlement, nil +} + // FillSSWPDFForm takes form data and fills an existing PDF form template with said data func (SSWPPMGenerator *SSWPPMGenerator) FillSSWPDFForm(Page1Values services.Page1Values, Page2Values services.Page2Values) (sswfile afero.File, pdfInfo *pdfcpu.PDFInfo, err error) { @@ -787,7 +833,7 @@ func (SSWPPMGenerator *SSWPPMGenerator) FillSSWPDFForm(Page1Values services.Page var sswHeader = header{ Source: "SSWPDFTemplate.pdf", Version: "pdfcpu v0.6.0 dev", - Creation: "2024-01-22 21:49:12 UTC", + Creation: "2024-03-08 17:36:47 UTC", Producer: "macOS Version 13.5 (Build 22G74) Quartz PDFContext, AppendMode 1.1", } diff --git a/pkg/services/shipment_summary_worksheet/shipment_summary_worksheet_test.go b/pkg/services/shipment_summary_worksheet/shipment_summary_worksheet_test.go index 2696ac81f45..2808a68d90c 100644 --- a/pkg/services/shipment_summary_worksheet/shipment_summary_worksheet_test.go +++ b/pkg/services/shipment_summary_worksheet/shipment_summary_worksheet_test.go @@ -292,7 +292,7 @@ func (suite *ShipmentSummaryWorksheetServiceSuite) TestFormatValuesShipmentSumma suite.Equal("Jenkins Jr., Marcus Joseph", sswPage1.ServiceMemberName) suite.Equal("E-9", sswPage1.RankGrade) suite.Equal("Air Force", sswPage1.ServiceBranch) - suite.Equal("90 days per each shipment", sswPage1.MaxSITStorageEntitlement) + suite.Equal("00 Days in SIT", sswPage1.MaxSITStorageEntitlement) suite.Equal("Yuma AFB, IA 50309", sswPage1.AuthorizedOrigin) suite.Equal("Fort Eisenhower, GA 30813", sswPage1.AuthorizedDestination) suite.Equal("No", sswPage1.POVAuthorized) @@ -307,7 +307,7 @@ func (suite *ShipmentSummaryWorksheetServiceSuite) TestFormatValuesShipmentSumma suite.Equal("Fort Eisenhower, GA 30813", sswPage1.NewDutyAssignment) suite.Equal("15,000", sswPage1.WeightAllotment) - suite.Equal("2,000", sswPage1.WeightAllotmentProgear) + suite.Equal("2,000", sswPage1.WeightAllotmentProGear) suite.Equal("500", sswPage1.WeightAllotmentProgearSpouse) suite.Equal("17,500", sswPage1.TotalWeightAllotment) @@ -346,7 +346,8 @@ func (suite *ShipmentSummaryWorksheetServiceSuite) TestFormatValuesShipmentSumma HasDependents: true, SpouseHasProGear: true, } - paidWithGTCC := false + paidWithGTCCFalse := false + paidWithGTCCTrue := true tollExpense := models.MovingExpenseReceiptTypeTolls oilExpense := models.MovingExpenseReceiptTypeOil amount := unit.Cents(10000) @@ -354,37 +355,37 @@ func (suite *ShipmentSummaryWorksheetServiceSuite) TestFormatValuesShipmentSumma { MovingExpenseType: &tollExpense, Amount: &amount, - PaidWithGTCC: &paidWithGTCC, + PaidWithGTCC: &paidWithGTCCFalse, }, { MovingExpenseType: &oilExpense, Amount: &amount, - PaidWithGTCC: &paidWithGTCC, + PaidWithGTCC: &paidWithGTCCFalse, }, { MovingExpenseType: &oilExpense, Amount: &amount, - PaidWithGTCC: &paidWithGTCC, + PaidWithGTCC: &paidWithGTCCTrue, }, { MovingExpenseType: &oilExpense, Amount: &amount, - PaidWithGTCC: &paidWithGTCC, + PaidWithGTCC: &paidWithGTCCFalse, }, { MovingExpenseType: &tollExpense, Amount: &amount, - PaidWithGTCC: &paidWithGTCC, + PaidWithGTCC: &paidWithGTCCTrue, }, { MovingExpenseType: &tollExpense, Amount: &amount, - PaidWithGTCC: &paidWithGTCC, + PaidWithGTCC: &paidWithGTCCTrue, }, { MovingExpenseType: &tollExpense, Amount: &amount, - PaidWithGTCC: &paidWithGTCC, + PaidWithGTCC: &paidWithGTCCFalse, }, } @@ -392,12 +393,16 @@ func (suite *ShipmentSummaryWorksheetServiceSuite) TestFormatValuesShipmentSumma Order: order, MovingExpenses: movingExpenses, } - sswPage2 := FormatValuesShipmentSummaryWorksheetFormPage2(ssd) + sswPage2 := FormatValuesShipmentSummaryWorksheetFormPage2(ssd) + suite.Equal("$200.00", sswPage2.TollsGTCCPaid) + suite.Equal("$200.00", sswPage2.TollsMemberPaid) + suite.Equal("$200.00", sswPage2.OilMemberPaid) + suite.Equal("$100.00", sswPage2.OilGTCCPaid) + suite.Equal("$300.00", sswPage2.TotalGTCCPaid) + suite.Equal("$400.00", sswPage2.TotalMemberPaid) suite.Equal("NTA4", sswPage2.TAC) suite.Equal("SAC", sswPage2.SAC) - - // fields w/ no expenses should format as $0.00, but must be temporarily removed until string function is replaced } func (suite *ShipmentSummaryWorksheetServiceSuite) TestGroupExpenses() { @@ -440,39 +445,7 @@ func (suite *ShipmentSummaryWorksheetServiceSuite) TestGroupExpenses() { map[string]float64{ "OilMemberPaid": 300, "TollsMemberPaid": 200, - }, - }, - { - models.MovingExpenses{ - { - MovingExpenseType: &tollExpense, - Amount: &amount, - PaidWithGTCC: &paidWithGTCC, - }, - { - MovingExpenseType: &oilExpense, - Amount: &amount, - PaidWithGTCC: &paidWithGTCC, - }, - { - MovingExpenseType: &oilExpense, - Amount: &amount, - PaidWithGTCC: &paidWithGTCC, - }, - { - MovingExpenseType: &oilExpense, - Amount: &amount, - PaidWithGTCC: &paidWithGTCC, - }, - { - MovingExpenseType: &tollExpense, - Amount: &amount, - PaidWithGTCC: &paidWithGTCC, - }, - }, - map[string]float64{ - "OilMemberPaid": 300, - "TollsMemberPaid": 200, + "TotalMemberPaid": 500, }, }, } @@ -622,11 +595,8 @@ func (suite *ShipmentSummaryWorksheetServiceSuite) TestFormatSignatureDate() { signature := models.SignedCertification{ Date: signatureDate, } - sswfd := ShipmentSummaryFormData{ - SignedCertification: signature, - } - formattedDate := FormatSignatureDate(sswfd.SignedCertification) + formattedDate := FormatSignatureDate(signature.Date) suite.Equal("26 Jan 2019 at 2:40pm", formattedDate) } @@ -649,7 +619,7 @@ func (suite *ShipmentSummaryWorksheetServiceSuite) TestFormatAddress() { // Test case 2: Nil W2 address nilAddress := (*models.Address)(nil) - expectedNilResult := "" + expectedNilResult := "W2 Address not found" resultNil := FormatAddress(nilAddress) @@ -765,7 +735,9 @@ func (suite *ShipmentSummaryWorksheetServiceSuite) TestFillSSWPDFForm() { Type: &factory.DutyLocations.OriginDutyLocation, }, { - Model: models.SignedCertification{}, + Model: models.SignedCertification{ + UpdatedAt: time.Now(), + }, }, }, nil) @@ -789,3 +761,219 @@ func (suite *ShipmentSummaryWorksheetServiceSuite) TestFillSSWPDFForm() { println(test.Name()) // ensures was generated with temp filesystem suite.Equal(info.PageCount, 2) // ensures PDF is not corrupted } + +func (suite *ShipmentSummaryWorksheetServiceSuite) TestFormatMaxAdvance() { + cents := unit.Cents(1000) + tests := []struct { + name string + estimatedIncentive *unit.Cents + expectedResult string + }{ + { + name: "Valid estimated incentive", + estimatedIncentive: ¢s, + expectedResult: "$6.00", + }, + { + name: "Nil estimated incentive", + estimatedIncentive: nil, + expectedResult: "No Incentive Found", + }, + } + + for _, tt := range tests { + result := formatMaxAdvance(tt.estimatedIncentive) + suite.Equal(tt.expectedResult, result) + } + +} + +func (suite *ShipmentSummaryWorksheetServiceSuite) TestGetOrDefault() { + testValue := "hello" + tests := []struct { + name string + value *string + defaultValue string + expectedResult string + }{ + { + name: "Non-nil value provided", + value: &testValue, // Example non-nil value + defaultValue: "world", // Example default value + expectedResult: "hello", + }, + { + name: "Nil value provided", + value: nil, + defaultValue: "world", // Example default value + expectedResult: "world", + }, + } + + for _, tt := range tests { + result := getOrDefault(tt.value, tt.defaultValue) + suite.Equal(tt.expectedResult, result) + } +} + +type mockPPMShipment struct { + FinalIncentive *unit.Cents + EstimatedIncentive *unit.Cents + AdvanceAmountReceived *unit.Cents +} + +func (suite *ShipmentSummaryWorksheetServiceSuite) TestFormatCurrentShipment() { + exampleValue1 := unit.Cents(5000) + exampleValue2 := unit.Cents(3000) + exampleValue3 := unit.Cents(1000) + tests := []struct { + name string + shipment mockPPMShipment + expectedResult WorkSheetShipment + }{ + { + name: "All fields present", + shipment: mockPPMShipment{ + FinalIncentive: &exampleValue1, // Example value + EstimatedIncentive: &exampleValue2, // Example value + AdvanceAmountReceived: &exampleValue3, // Example value + }, + expectedResult: WorkSheetShipment{ + FinalIncentive: "$50.00", // Example expected result + MaxAdvance: "$18.00", // Assuming formatMaxAdvance correctly formats + EstimatedIncentive: "$30.00", // Example expected result + AdvanceAmountReceived: "$10.00", // Example expected result + }, + }, + { + name: "Final Incentive nil", + shipment: mockPPMShipment{ + FinalIncentive: nil, + EstimatedIncentive: &exampleValue2, // Example value + AdvanceAmountReceived: &exampleValue3, // Example value + }, + expectedResult: WorkSheetShipment{ + FinalIncentive: "No final incentive.", + MaxAdvance: "$18.00", // Assuming formatMaxAdvance correctly formats + EstimatedIncentive: "$30.00", // Example expected result + AdvanceAmountReceived: "$10.00", // Example expected result + }, + }, + } + + for _, tt := range tests { + result := FormatCurrentShipment(models.PPMShipment{ + FinalIncentive: tt.shipment.FinalIncentive, + EstimatedIncentive: tt.shipment.EstimatedIncentive, + AdvanceAmountReceived: tt.shipment.AdvanceAmountReceived, + }) + + suite.Equal(tt.expectedResult.FinalIncentive, result.FinalIncentive) + suite.Equal(tt.expectedResult.MaxAdvance, result.MaxAdvance) + suite.Equal(tt.expectedResult.EstimatedIncentive, result.EstimatedIncentive) + suite.Equal(tt.expectedResult.AdvanceAmountReceived, result.AdvanceAmountReceived) + + } +} + +type mockMTOAgent struct { + FirstName *string + LastName *string + Email *string + Phone *string + UpdatedAt time.Time +} + +func (suite *ShipmentSummaryWorksheetServiceSuite) TestFormatAgentInfo() { + exampleFirstName := "John" + exampleLastName := "Doe" + exampleEmail := "john.doe@example.com" + examplePhone := "123-456-7890" + tests := []struct { + name string + agents []mockMTOAgent + expectedResult Agent + }{ + { + name: "No agents specified", + agents: []mockMTOAgent{}, + expectedResult: Agent{ + Name: "No agent specified", + Email: "No agent specified", + Phone: "No agent specified", + Date: "No agent specified", + }, + }, + { + name: "Agent with first and last name specified", + agents: []mockMTOAgent{ + { + FirstName: &exampleFirstName, + LastName: &exampleLastName, + Email: &exampleEmail, + Phone: &examplePhone, + UpdatedAt: time.Now(), + }, + }, + expectedResult: Agent{ + Name: "Doe, John", + Email: "john.doe@example.com", + Phone: "123-456-7890", + Date: time.Now().Format("20060102"), + }, + }, + { + name: "Agent with only first name specified", + agents: []mockMTOAgent{ + { + FirstName: &exampleFirstName, + Email: &exampleEmail, + Phone: &examplePhone, + UpdatedAt: time.Now(), + }, + }, + expectedResult: Agent{ + Name: "First Name: John, No last name provided", + Email: "john.doe@example.com", + Phone: "123-456-7890", + Date: time.Now().Format("20060102"), + }, + }, + { + name: "Agent with only last name specified", + agents: []mockMTOAgent{ + { + LastName: &exampleLastName, + Email: &exampleEmail, + Phone: &examplePhone, + UpdatedAt: time.Now(), + }, + }, + expectedResult: Agent{ + Name: "No first name provided, Last Name: Doe", + Email: "john.doe@example.com", + Phone: "123-456-7890", + Date: time.Now().Format("20060102"), + }, + }, + } + + for _, tt := range tests { + agents := make([]models.MTOAgent, len(tt.agents)) + for i, mockAgent := range tt.agents { + agents[i] = models.MTOAgent{ + FirstName: mockAgent.FirstName, + LastName: mockAgent.LastName, + Email: mockAgent.Email, + Phone: mockAgent.Phone, + UpdatedAt: mockAgent.UpdatedAt, + } + } + + result := FormatAgentInfo(agents) + suite.Equal(tt.expectedResult.Name, result.Name) + suite.Equal(tt.expectedResult.Email, result.Email) + suite.Equal(tt.expectedResult.Phone, result.Phone) + suite.Equal(tt.expectedResult.Date, result.Date) + } +} diff --git a/pkg/services/sit_extension/approved_sit_duration_update_creator.go b/pkg/services/sit_extension/approved_sit_duration_update_creator.go index f59178038af..2eb2e7c9f96 100644 --- a/pkg/services/sit_extension/approved_sit_duration_update_creator.go +++ b/pkg/services/sit_extension/approved_sit_duration_update_creator.go @@ -2,7 +2,6 @@ package sitextension import ( "database/sql" - "time" "github.com/gobuffalo/validate/v3" "github.com/gofrs/uuid" @@ -39,12 +38,6 @@ func (f *approvedSITDurationUpdateCreator) CreateApprovedSITDurationUpdate(appCt return nil, err } - err = resetSITAuthorizedEndDate(appCtx, shipmentID) - - if err != nil { - return nil, err - } - err = validateSITExtension(appCtx, *sitDurationUpdate, shipment, f.checks...) if err != nil { return nil, err @@ -104,58 +97,6 @@ func (f *approvedSITDurationUpdateCreator) updateSitDaysAllowance(appCtx appcont return &shipment, nil } -func resetSITAuthorizedEndDate(appCtx appcontext.AppContext, shipmentID uuid.UUID) error { - // We need to get the shipment with its service items to reset the Authorized End Date - // for an Origin or Destination SIT service item, since we are updating with a manual override - eagerAssociations := []string{"MoveTaskOrder", - "PickupAddress", - "DestinationAddress", - "SecondaryPickupAddress", - "SecondaryDeliveryAddress", - "MTOServiceItems.ReService", - "StorageFacility.Address", - "PPMShipment"} - shipment, err := mtoshipment.NewMTOShipmentFetcher().GetShipment(appCtx, shipmentID, eagerAssociations...) - - if err != nil { - return apperror.NewNotFoundError(shipmentID, "while looking for MTOServiceItem") - } - - today := time.Now() - - for _, serviceItem := range shipment.MTOServiceItems { - if code := serviceItem.ReService.Code; (code == models.ReServiceCodeDOASIT || code == models.ReServiceCodeDDASIT) && - serviceItem.Status == models.MTOServiceItemStatusApproved { - // get current SIT service item - if !serviceItem.SITEntryDate.After(today) && !(serviceItem.SITDepartureDate != nil && serviceItem.SITDepartureDate.Before(today)) { - // We retrieve the old service item so we can get the required values to update with the new value for Authorized End Date - aedServiceItem, err := models.FetchServiceItem(appCtx.DB(), serviceItem.ID) - - if err != nil { - switch err { - case models.ErrFetchNotFound: - return apperror.NewNotFoundError(serviceItem.ID, "while looking for MTOServiceItem") - default: - return apperror.NewQueryError("MTOServiceItem", err, "") - } - } - - aedServiceItem.SITAuthorizedEndDate = nil - verrs, err := appCtx.DB().ValidateAndUpdate(&aedServiceItem) - - if verrs != nil && verrs.HasAny() { - return apperror.NewInvalidInputError(aedServiceItem.ID, err, verrs, "invalid input found while updating the sit service item") - } else if err != nil { - return apperror.NewQueryError("Service item", err, "") - } - break - } - } - } - - return nil -} - func (f *approvedSITDurationUpdateCreator) handleError(modelID uuid.UUID, verrs *validate.Errors, err error) error { if verrs != nil && verrs.HasAny() { return apperror.NewInvalidInputError(modelID, nil, verrs, "") diff --git a/pkg/services/sit_status/shipment_sit_status.go b/pkg/services/sit_status/shipment_sit_status.go index dcfe7db0dfd..d473bb6f43b 100644 --- a/pkg/services/sit_status/shipment_sit_status.go +++ b/pkg/services/sit_status/shipment_sit_status.go @@ -122,17 +122,9 @@ func (f shipmentSITStatus) CalculateShipmentSITStatus(appCtx appcontext.AppConte sitCustomerContacted = currentSIT.SITCustomerContacted sitRequestedDelivery = currentSIT.SITRequestedDelivery - // Need to retrieve the current service item so we can populate the Authorized End Date for the current SIT - currentServiceItem, err := models.FetchServiceItem(appCtx.DB(), currentSIT.ID) - if err != nil { - return nil, err - } - - sitAuthorizedEndDate := currentServiceItem.SITAuthorizedEndDate doaSIT := getAdditionalSIT(shipmentSITs, shipment, today) if doaSIT != nil { - sitAuthorizedEndDate = doaSIT.SITAuthorizedEndDate sitCustomerContacted = doaSIT.SITCustomerContacted sitRequestedDelivery = doaSIT.SITRequestedDelivery sitDepartureDate = doaSIT.SITDepartureDate @@ -145,7 +137,6 @@ func (f shipmentSITStatus) CalculateShipmentSITStatus(appCtx appcontext.AppConte SITEntryDate: sitEntryDate, SITDepartureDate: sitDepartureDate, SITAllowanceEndDate: sitAllowanceEndDate, - SITAuthorizedEndDate: sitAuthorizedEndDate, SITCustomerContacted: sitCustomerContacted, SITRequestedDelivery: sitRequestedDelivery, } diff --git a/pkg/testdatagen/scenario/e2ebasic.go b/pkg/testdatagen/scenario/e2ebasic.go index 9f8030c7a93..679105fa267 100644 --- a/pkg/testdatagen/scenario/e2ebasic.go +++ b/pkg/testdatagen/scenario/e2ebasic.go @@ -2998,7 +2998,7 @@ func createPrimeSimulatorMoveNeedsShipmentUpdate(appCtx appcontext.AppContext, u shipmentFields := models.MTOShipment{ ID: uuid.FromStringOrNil("5375f237-430c-406d-9ec8-5a27244d563a"), - Status: models.MTOShipmentStatusApproved, + Status: models.MTOShipmentStatusSubmitted, RequestedPickupDate: &requestedPickupDate, RequestedDeliveryDate: &requestedDeliveryDate, } diff --git a/pkg/testdatagen/testharness/dispatch.go b/pkg/testdatagen/testharness/dispatch.go index c01749cb316..c82e8949716 100644 --- a/pkg/testdatagen/testharness/dispatch.go +++ b/pkg/testdatagen/testharness/dispatch.go @@ -173,6 +173,9 @@ var actionDispatcher = map[string]actionFunc{ "ApprovedMoveWithPPMWeightTicketOffice": func(appCtx appcontext.AppContext) testHarnessResponse { return MakeApprovedMoveWithPPMWeightTicketOffice(appCtx) }, + "ApprovedMoveWithPPMWeightTicketOfficeWithHHG": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeApprovedMoveWithPPMWeightTicketOfficeWithHHG(appCtx) + }, "ApprovedMoveWithPPMMovingExpense": func(appCtx appcontext.AppContext) testHarnessResponse { return MakeApprovedMoveWithPPMMovingExpense(appCtx) }, diff --git a/pkg/testdatagen/testharness/make_move.go b/pkg/testdatagen/testharness/make_move.go index 475dfd20139..1a85c8b8efb 100644 --- a/pkg/testdatagen/testharness/make_move.go +++ b/pkg/testdatagen/testharness/make_move.go @@ -547,7 +547,7 @@ func MakePrimeSimulatorMoveNeedsShipmentUpdate(appCtx appcontext.AppContext) mod pickupAddress := factory.BuildAddress(appCtx.DB(), nil, nil) shipmentFields := models.MTOShipment{ - Status: models.MTOShipmentStatusApproved, + Status: models.MTOShipmentStatusSubmitted, RequestedPickupDate: &requestedPickupDate, RequestedDeliveryDate: &requestedDeliveryDate, } @@ -3396,6 +3396,95 @@ func MakeApprovedMoveWithPPMWeightTicketOffice(appCtx appcontext.AppContext) mod return *newmove } +func MakeApprovedMoveWithPPMWeightTicketOfficeWithHHG(appCtx appcontext.AppContext) models.Move { + userUploader := newUserUploader(appCtx) + closeoutOffice := factory.BuildTransportationOffice(appCtx.DB(), []factory.Customization{ + { + Model: models.TransportationOffice{Gbloc: "KKFA", ProvidesCloseout: true}, + }, + }, nil) + userInfo := newUserInfo("customer") + moveInfo := scenario.MoveCreatorInfo{ + UserID: uuid.Must(uuid.NewV4()), + Email: userInfo.email, + SmID: uuid.Must(uuid.NewV4()), + FirstName: userInfo.firstName, + LastName: userInfo.lastName, + MoveID: uuid.Must(uuid.NewV4()), + MoveLocator: models.GenerateLocator(), + CloseoutOfficeID: &closeoutOffice.ID, + } + + approvedAt := time.Date(2022, 4, 15, 12, 30, 0, 0, time.UTC) + address := factory.BuildAddress(appCtx.DB(), nil, nil) + + assertions := testdatagen.Assertions{ + UserUploader: userUploader, + Move: models.Move{ + Status: models.MoveStatusAPPROVED, + }, + MTOShipment: models.MTOShipment{ + Status: models.MTOShipmentStatusApproved, + }, + PPMShipment: models.PPMShipment{ + ID: uuid.Must(uuid.NewV4()), + ApprovedAt: &approvedAt, + Status: models.PPMShipmentStatusNeedsPaymentApproval, + ActualMoveDate: models.TimePointer(time.Date(testdatagen.GHCTestYear, time.March, 16, 0, 0, 0, 0, time.UTC)), + ActualPickupPostalCode: models.StringPointer("42444"), + ActualDestinationPostalCode: models.StringPointer("30813"), + HasReceivedAdvance: models.BoolPointer(true), + AdvanceAmountReceived: models.CentPointer(unit.Cents(340000)), + W2Address: &address, + }, + } + + move, shipment := scenario.CreateGenericMoveWithPPMShipment(appCtx, moveInfo, false, userUploader, &assertions.MTOShipment, &assertions.Move, assertions.PPMShipment) + + estimatedWeight := unit.Pound(1400) + actualWeight := unit.Pound(2000) + + requestedPickupDate := time.Now().AddDate(0, 3, 0) + requestedDeliveryDate := requestedPickupDate.AddDate(0, 1, 0) + + factory.BuildMTOShipment(appCtx.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + PrimeEstimatedWeight: &estimatedWeight, + PrimeActualWeight: &actualWeight, + ShipmentType: models.MTOShipmentTypeHHG, + Status: models.MTOShipmentStatusApproved, + RequestedPickupDate: &requestedPickupDate, + RequestedDeliveryDate: &requestedDeliveryDate, + }, + }, + { + Model: move, + LinkOnly: true, + }, + }, nil) + + factory.BuildWeightTicket(appCtx.DB(), []factory.Customization{ + { + Model: shipment, + LinkOnly: true, + }, + { + Model: move.Orders.ServiceMember, + LinkOnly: true, + }, + }, nil) + + // re-fetch the move so that we ensure we have exactly what is in + // the db + newmove, err := models.FetchMove(appCtx.DB(), &auth.Session{}, move.ID) + if err != nil { + log.Panic(fmt.Errorf("Failed to fetch move: %w", err)) + } + + return *newmove +} + func MakeApprovedMoveWithPPMMovingExpense(appCtx appcontext.AppContext) models.Move { userUploader := newUserUploader(appCtx) diff --git a/playwright/tests/my/milmove/ppms/customerPpmTestFixture.js b/playwright/tests/my/milmove/ppms/customerPpmTestFixture.js index 3d8d94bc9ff..265f7a2c002 100644 --- a/playwright/tests/my/milmove/ppms/customerPpmTestFixture.js +++ b/playwright/tests/my/milmove/ppms/customerPpmTestFixture.js @@ -920,7 +920,7 @@ export class CustomerPpmPage extends CustomerPage { stepContainer = this.page.locator('[data-testid="stepContainer5"]'); } - await expect(stepContainer.getByRole('button', { name: 'Download Incentive Packet' })).toBeDisabled(); + await expect(stepContainer.getByRole('button', { name: 'Download Payment Packet' })).toBeDisabled(); await expect(stepContainer.getByText(/PPM documentation submitted: \d{2} \w{3} \d{4}/)).toBeVisible(); } diff --git a/playwright/tests/office/primesimulator/primeSimulatorFlows.spec.js b/playwright/tests/office/primesimulator/primeSimulatorFlows.spec.js index 14d08582ae5..3c74269588e 100644 --- a/playwright/tests/office/primesimulator/primeSimulatorFlows.spec.js +++ b/playwright/tests/office/primesimulator/primeSimulatorFlows.spec.js @@ -62,6 +62,7 @@ test.describe('Prime simulator user', () => { await expect(page.getByText(moveLocator)).toBeVisible(); expect(page.url()).toContain(`/simulator/moves/${moveID}/details`); // waits for the move details page to load + await expect(page.getByText('SUBMITTED')).toHaveCount(1); await page.getByRole('link', { name: 'Update Shipment', exact: true }).click(); // waits for the update shipment page to load diff --git a/playwright/tests/office/servicescounseling/servicesCounselingWeightTickets.spec.js b/playwright/tests/office/servicescounseling/servicesCounselingWeightTickets.spec.js index 76a72780a65..52b9c8cb710 100644 --- a/playwright/tests/office/servicescounseling/servicesCounselingWeightTickets.spec.js +++ b/playwright/tests/office/servicescounseling/servicesCounselingWeightTickets.spec.js @@ -81,3 +81,24 @@ test('A services counselor can reduce PPM weights for a move with excess weight' page.getByText('This move has excess weight. Review PPM weight ticket documents to resolve.'), ).toHaveCount(0); }); + +test('A service counselor can see HHG weights when reviewing weight tickets', async ({ page, scPage }) => { + // Create a move with TestHarness, and then navigate to the move details page for it + const move = await scPage.testHarness.buildApprovedMoveWithPPMWeightTicketOfficeWithHHG(); + await scPage.navigateToCloseoutMove(move.locator); + + // Navigate to the "Review documents" page + await page.getByRole('button', { name: 'Review documents' }).click(); + await scPage.waitForPage.reviewWeightTicket(); + + // Verify the heading "HHG 1" is present + await expect(page.getByRole('heading', { level: 3, name: /HHG 1/ })).toBeVisible(); + + // Verify that the labels for Estimated Weight and Actual Weight are present + await expect(page.getByText('Estimated Weight')).toBeVisible(); + await expect(page.getByText('Actual Weight')).toBeVisible(); + + // Verify that the specific weights are displayed as expected + await expect(page.getByText('1,400 lbs')).toBeVisible(); + await expect(page.getByText('2,000 lbs')).toBeVisible(); +}); diff --git a/playwright/tests/office/txo/sitUpdates.spec.js b/playwright/tests/office/txo/sitUpdates.spec.js index 92fc1cf9e6b..48ed1724cd2 100644 --- a/playwright/tests/office/txo/sitUpdates.spec.js +++ b/playwright/tests/office/txo/sitUpdates.spec.js @@ -243,8 +243,8 @@ test.describe('TOO user', () => { await expect(page.getByText('Total days of SIT approved')).toBeVisible(); await expect(page.getByText('Total days used')).toBeVisible(); await expect(page.getByText('Total days remaining')).toBeVisible(); - await expect(page.getByText('SIT start date')).toBeVisible(); - await expect(page.getByText(' SIT authorized end date')).toBeVisible(); + await expect(page.getByText('SIT start date').nth(0)).toBeVisible(); + await expect(page.getByText('SIT authorized end date')).toBeVisible(); await expect(page.getByText('Calculated total SIT days')).toBeVisible(); }); test('is showing the SIT Departure Date section', async ({ page }) => { diff --git a/playwright/tests/utils/testharness.js b/playwright/tests/utils/testharness.js index e67596a94ae..d132565695b 100644 --- a/playwright/tests/utils/testharness.js +++ b/playwright/tests/utils/testharness.js @@ -428,6 +428,14 @@ export class TestHarness { return this.buildDefault('ApprovedMoveWithPPMWeightTicketOffice'); } + /** + * Use testharness to build submitted move with partial ppm and weight ticket + * @returns {Promise} + */ + async buildApprovedMoveWithPPMWeightTicketOfficeWithHHG() { + return this.buildDefault('ApprovedMoveWithPPMWeightTicketOfficeWithHHG'); + } + /** * Use testharness to build approved move with ppm moving expenses * @returns {Promise} diff --git a/src/components/Customer/PPM/Booking/DateAndLocationForm/DateAndLocationForm.jsx b/src/components/Customer/PPM/Booking/DateAndLocationForm/DateAndLocationForm.jsx index b9face21570..43300fde43a 100644 --- a/src/components/Customer/PPM/Booking/DateAndLocationForm/DateAndLocationForm.jsx +++ b/src/components/Customer/PPM/Booking/DateAndLocationForm/DateAndLocationForm.jsx @@ -83,7 +83,8 @@ const DateAndLocationForm = ({ mtoShipment, destinationDutyLocation, serviceMemb const showCloseoutOffice = serviceMember.affiliation === SERVICE_MEMBER_AGENCIES.ARMY || - serviceMember.affiliation === SERVICE_MEMBER_AGENCIES.AIR_FORCE; + serviceMember.affiliation === SERVICE_MEMBER_AGENCIES.AIR_FORCE || + serviceMember.affiliation === SERVICE_MEMBER_AGENCIES.SPACE_FORCE; if (showCloseoutOffice) { validationShape.closeoutOffice = Yup.object().required('Required'); } else { diff --git a/src/components/MilMoveHeader/index.jsx b/src/components/MilMoveHeader/index.jsx index 6477e527b6b..ea2a98be056 100644 --- a/src/components/MilMoveHeader/index.jsx +++ b/src/components/MilMoveHeader/index.jsx @@ -6,7 +6,7 @@ import MmLogo from '../../shared/images/milmove-logo.svg'; import styles from './index.module.scss'; -const MilMoveHeader = ({ children }) => { +const MilMoveHeader = ({ isSpecialMove, children }) => { return (
@@ -17,6 +17,11 @@ const MilMoveHeader = ({ children }) => {
+ {isSpecialMove ? ( +
+

BLUEBARK

+
+ ) : null}
{children}
@@ -24,11 +29,13 @@ const MilMoveHeader = ({ children }) => { }; MilMoveHeader.defaultProps = { + isSpecialMove: null, children: null, }; MilMoveHeader.propTypes = { children: PropTypes.node, + isSpecialMove: PropTypes.bool, }; export default MilMoveHeader; diff --git a/src/components/Office/DefinitionLists/PPMShipmentInfoList.jsx b/src/components/Office/DefinitionLists/PPMShipmentInfoList.jsx index 4acf5414716..b77b3316d74 100644 --- a/src/components/Office/DefinitionLists/PPMShipmentInfoList.jsx +++ b/src/components/Office/DefinitionLists/PPMShipmentInfoList.jsx @@ -11,10 +11,11 @@ import { ShipmentShape } from 'types/shipment'; import { formatCentsTruncateWhole, formatWeight } from 'utils/formatters'; import { setFlagStyles, setDisplayFlags, getDisplayFlags, fieldValidationShape } from 'utils/displayFlags'; import { ADVANCE_STATUSES } from 'constants/ppms'; +import { ppmShipmentStatuses } from 'constants/shipments'; import affiliation from 'content/serviceMemberAgencies'; import { permissionTypes } from 'constants/permissions'; import Restricted from 'components/Restricted/Restricted'; -import { downloadPPMAOAPacket } from 'services/ghcApi'; +import { downloadPPMAOAPacket, downloadPPMPaymentPacket } from 'services/ghcApi'; const PPMShipmentInfoList = ({ className, @@ -30,6 +31,7 @@ const PPMShipmentInfoList = ({ hasRequestedAdvance, advanceAmountRequested, advanceStatus, + status, destinationPostalCode, estimatedIncentive, estimatedWeight, @@ -201,6 +203,21 @@ const PPMShipmentInfoList = ({ ); + const paymentPacketElement = ( +
+
Payment Packet
+
+

+ +

+
+
+ ); const counselorRemarksElementFlags = getDisplayFlags('counselorRemarks'); const counselorRemarksElement = (
@@ -234,6 +251,8 @@ const PPMShipmentInfoList = ({ {hasRequestedAdvanceElement} {hasRequestedAdvance === true && advanceStatusElement} {advanceStatus === ADVANCE_STATUSES.APPROVED.apiValue && aoaPacketElement} + {(status === ppmShipmentStatuses.PAYMENT_APPROVED || status === ppmShipmentStatuses.WAITING_ON_CUSTOMER) && + paymentPacketElement} {counselorRemarksElement} ); diff --git a/src/components/Office/DefinitionLists/PPMShipmentInfoList.test.jsx b/src/components/Office/DefinitionLists/PPMShipmentInfoList.test.jsx index 86da49eb2ab..9dd839ea5db 100644 --- a/src/components/Office/DefinitionLists/PPMShipmentInfoList.test.jsx +++ b/src/components/Office/DefinitionLists/PPMShipmentInfoList.test.jsx @@ -8,11 +8,13 @@ import affiliation from 'content/serviceMemberAgencies'; import { MockProviders } from 'testUtils'; import { permissionTypes } from 'constants/permissions'; import { ADVANCE_STATUSES } from 'constants/ppms'; -import { downloadPPMAOAPacket } from 'services/ghcApi'; +import { ppmShipmentStatuses } from 'constants/shipments'; +import { downloadPPMAOAPacket, downloadPPMPaymentPacket } from 'services/ghcApi'; jest.mock('services/ghcApi', () => ({ ...jest.requireActual('services/ghcApi'), downloadPPMAOAPacket: jest.fn(), + downloadPPMPaymentPacket: jest.fn(), })); afterEach(() => { @@ -113,4 +115,55 @@ describe('PPMShipmentInfoList', () => { expect(onErrorHandler).toHaveBeenCalledTimes(1); }); }); + + it('PPM Download Payment Paperwork - success', async () => { + const mockResponse = { + ok: true, + headers: { + 'content-disposition': 'filename="test.pdf"', + }, + status: 200, + data: null, + }; + downloadPPMPaymentPacket.mockImplementation(() => Promise.resolve(mockResponse)); + + renderWithPermissions({ ppmShipment: { status: ppmShipmentStatuses.PAYMENT_APPROVED } }); + + expect(screen.getByText('Download Payment Packet (PDF)', { exact: false })).toBeInTheDocument(); + + const downloadPaymentButton = screen.getByText('Download Payment Packet (PDF)'); + expect(downloadPaymentButton).toBeInTheDocument(); + + await userEvent.click(downloadPaymentButton); + + await waitFor(() => { + expect(downloadPPMPaymentPacket).toHaveBeenCalledTimes(1); + }); + }); + + it('PPM Download Payment Packet - failure', async () => { + downloadPPMPaymentPacket.mockRejectedValue({ + response: { body: { title: 'Error title', detail: 'Error detail' } }, + }); + + const shipment = { ppmShipment: { status: ppmShipmentStatuses.PAYMENT_APPROVED } }; + const onErrorHandler = jest.fn(); + + render( + + + , + ); + + expect(screen.getByText('Download Payment Packet (PDF)')).toBeInTheDocument(); + + const downloadPaymentButton = screen.getByText('Download Payment Packet (PDF)'); + expect(downloadPaymentButton).toBeInTheDocument(); + await userEvent.click(downloadPaymentButton); + + await waitFor(() => { + expect(downloadPPMPaymentPacket).toHaveBeenCalledTimes(1); + expect(onErrorHandler).toHaveBeenCalledTimes(1); + }); + }); }); diff --git a/src/components/Office/PPM/HHGWeightSummary/HHGWeightSummary.jsx b/src/components/Office/PPM/HHGWeightSummary/HHGWeightSummary.jsx new file mode 100644 index 00000000000..966001b5440 --- /dev/null +++ b/src/components/Office/PPM/HHGWeightSummary/HHGWeightSummary.jsx @@ -0,0 +1,70 @@ +import { React } from 'react'; +import classnames from 'classnames'; +import { Label } from '@trussworks/react-uswds'; +import { PropTypes } from 'prop-types'; + +import headerSectionStyles from '../PPMHeaderSummary/HeaderSection.module.scss'; + +import styles from './HHGWeightSummary.module.scss'; + +import { DEFAULT_EMPTY_VALUE } from 'shared/constants'; +import { ShipmentShape } from 'types/shipment'; +import { formatWeight } from 'utils/formatters'; + +const getHHGShipments = (mtoShipments) => { + return mtoShipments.filter((shipment) => shipment.shipmentType === 'HHG'); +}; + +const HHGSummaryBlock = ({ hhgNumber, estimatedWeight, actualWeight }) => { + return ( +
+

HHG {hhgNumber}

+
+
+
+ + + {estimatedWeight ? formatWeight(estimatedWeight) : DEFAULT_EMPTY_VALUE} + +
+
+ + + {actualWeight ? formatWeight(actualWeight) : DEFAULT_EMPTY_VALUE} + +
+
+
+
+ ); +}; +export default function HHGWeightSummary({ mtoShipments }) { + const hhgShipments = getHHGShipments(mtoShipments); + if (hhgShipments.length === 0) return null; + + return ( +
+
+ {getHHGShipments(mtoShipments).map((shipment, idx) => { + return ( + + ); + })} +
+
+
+ ); +} + +HHGWeightSummary.propTypes = { + mtoShipments: PropTypes.arrayOf(ShipmentShape), +}; + +HHGWeightSummary.defaultProps = { + mtoShipments: [], +}; diff --git a/src/components/Office/PPM/HHGWeightSummary/HHGWeightSummary.module.scss b/src/components/Office/PPM/HHGWeightSummary/HHGWeightSummary.module.scss new file mode 100644 index 00000000000..93ad4a02529 --- /dev/null +++ b/src/components/Office/PPM/HHGWeightSummary/HHGWeightSummary.module.scss @@ -0,0 +1,28 @@ +@import 'shared/styles/_basics'; +@import 'shared/styles/colors'; + +.HHGWeightSummary { + .header { + :global(.usa-label) { + width: auto; + display: inline-block; + @include u-margin-top(.5); + @include u-margin-right(1); + } + } + + section { + color: $base-darker; + } + + h4 { + @include u-margin-y(2.5); + } + + .light { + @include u-font('body', '3xs'); + } +} +.HHGContainer { + margin-bottom: 2.5rem; +} diff --git a/src/components/Office/PPM/HHGWeightSummary/HHGWeightSummary.test.jsx b/src/components/Office/PPM/HHGWeightSummary/HHGWeightSummary.test.jsx new file mode 100644 index 00000000000..dddff6cbff0 --- /dev/null +++ b/src/components/Office/PPM/HHGWeightSummary/HHGWeightSummary.test.jsx @@ -0,0 +1,43 @@ +import React from 'react'; +import { render, waitFor, screen } from '@testing-library/react'; + +import HHGWeightSummary from './HHGWeightSummary'; + +beforeEach(() => { + jest.clearAllMocks(); +}); + +const mtoShipments = [ + { + shipmentType: 'HHG', + primeEstimatedWeight: 110, + primeActualWeight: 100, + }, + { + shipmentType: 'HHG', + primeEstimatedWeight: 201, + primeActualWeight: 200, + }, +]; + +describe('HHGWeightSummary component', () => { + describe('displays form', () => { + it('renders blank form on load with defaults', async () => { + render(); + + await waitFor(() => { + expect(screen.getByRole('heading', { level: 3, name: 'HHG 1' })).toBeInTheDocument(); + expect(screen.getByRole('heading', { level: 3, name: 'HHG 2' })).toBeInTheDocument(); + }); + + expect(screen.getAllByText('Estimated Weight')[0]).toBeInTheDocument(); + expect(screen.getAllByText('Estimated Weight')[1]).toBeInTheDocument(); + expect(screen.getAllByText('Actual Weight')[0]).toBeInTheDocument(); + expect(screen.getAllByText('Actual Weight')[1]).toBeInTheDocument(); + expect(screen.getByText('110 lbs')).toBeInTheDocument(); + expect(screen.getByText('100 lbs')).toBeInTheDocument(); + expect(screen.getByText('201 lbs')).toBeInTheDocument(); + expect(screen.getByText('200 lbs')).toBeInTheDocument(); + }); + }); +}); diff --git a/src/components/Office/PPM/ReviewWeightTicket/ReviewWeightTicket.jsx b/src/components/Office/PPM/ReviewWeightTicket/ReviewWeightTicket.jsx index 9858f4cb3a1..fe29af9de74 100644 --- a/src/components/Office/PPM/ReviewWeightTicket/ReviewWeightTicket.jsx +++ b/src/components/Office/PPM/ReviewWeightTicket/ReviewWeightTicket.jsx @@ -7,6 +7,7 @@ import { Alert, FormGroup, Label, Radio, Textarea } from '@trussworks/react-uswd import * as Yup from 'yup'; import PPMHeaderSummary from '../PPMHeaderSummary/PPMHeaderSummary'; +import HHGWeightSummary from '../HHGWeightSummary/HHGWeightSummary'; import EditPPMNetWeight from '../EditNetWeights/EditPPMNetWeight'; import styles from './ReviewWeightTicket.module.scss'; @@ -240,6 +241,7 @@ function ReviewWeightTicket({ return (
+

Trip {tripNumber}

diff --git a/src/components/Office/RequestedServiceItemsTable/RequestedServiceItemsTable.jsx b/src/components/Office/RequestedServiceItemsTable/RequestedServiceItemsTable.jsx index 8bca89a9648..d8491cca89f 100644 --- a/src/components/Office/RequestedServiceItemsTable/RequestedServiceItemsTable.jsx +++ b/src/components/Office/RequestedServiceItemsTable/RequestedServiceItemsTable.jsx @@ -7,6 +7,8 @@ import { ServiceItemDetailsShape } from '../../../types/serviceItems'; import styles from './RequestedServiceItemsTable.module.scss'; import ServiceItemsTable from 'components/Office/ServiceItemsTable/ServiceItemsTable'; +import { ShipmentShape } from 'types'; +import { SitStatusShape } from 'types/sitStatusShape'; const RequestedServiceItemsTable = ({ serviceItems, @@ -15,6 +17,8 @@ const RequestedServiceItemsTable = ({ handleShowEditSitEntryDateModal, statusForTableType, serviceItemAddressUpdateAlert, + shipment, + sitStatus, }) => { const chooseTitleText = (status) => { switch (status) { @@ -46,6 +50,8 @@ const RequestedServiceItemsTable = ({ handleShowEditSitEntryDateModal={handleShowEditSitEntryDateModal} statusForTableType={statusForTableType} serviceItemAddressUpdateAlert={serviceItemAddressUpdateAlert} + shipment={shipment} + sitStatus={sitStatus} />
); @@ -56,6 +62,13 @@ RequestedServiceItemsTable.propTypes = { handleShowRejectionDialog: PropTypes.func.isRequired, statusForTableType: PropTypes.string.isRequired, serviceItems: PropTypes.arrayOf(ServiceItemDetailsShape).isRequired, + shipment: ShipmentShape, + sitStatus: SitStatusShape, +}; + +RequestedServiceItemsTable.defaultProps = { + shipment: {}, + sitStatus: undefined, }; export default RequestedServiceItemsTable; diff --git a/src/components/Office/RequestedServiceItemsTable/RequestedServiceItemsTable.test.jsx b/src/components/Office/RequestedServiceItemsTable/RequestedServiceItemsTable.test.jsx index 0e5d2622754..65d1620419a 100644 --- a/src/components/Office/RequestedServiceItemsTable/RequestedServiceItemsTable.test.jsx +++ b/src/components/Office/RequestedServiceItemsTable/RequestedServiceItemsTable.test.jsx @@ -70,22 +70,28 @@ const testDetails = (wrapper) => { expect(detailTypes.at(1).text()).toBe('Item size:'); expect(detailDefinitions.at(1).text()).toBe('7"x2"x3.5"'); - expect(detailTypes.at(3).text()).toBe('SIT entry date:'); + expect(detailTypes.at(3).text()).toBe('Original pickup address:'); expect(detailDefinitions.at(3).text().includes('-')).toBe(true); - expect(detailTypes.at(4).text()).toBe('First available delivery date 1:'); - expect(detailDefinitions.at(4).text().includes('15 Sep 2020')).toBe(true); - expect(detailTypes.at(5).text()).toBe('Customer contact attempt 1:'); - expect(detailDefinitions.at(5).text().includes('15 Sep 2020, 1200Z')).toBe(true); - - expect(detailTypes.at(6).text()).toBe('First available delivery date 2:'); - expect(detailDefinitions.at(6).text().includes('21 Sep 2020')).toBe(true); - expect(detailTypes.at(7).text()).toBe('Customer contact attempt 2:'); - expect(detailDefinitions.at(7).text().includes('21 Sep 2020, 2300Z')).toBe(true); - - expect(detailTypes.at(9).text()).toBe('ZIP:'); - expect(detailDefinitions.at(9).text().includes('12345')).toBe(true); - expect(detailTypes.at(8).text()).toBe('Reason:'); - expect(detailDefinitions.at(8).text().includes('Took a detour')).toBe(true); + expect(detailTypes.at(4).text()).toBe('Actual pickup address:'); + expect(detailDefinitions.at(4).text().includes('-')).toBe(true); + expect(detailTypes.at(5).text()).toBe('Delivery into SIT miles:'); + expect(detailDefinitions.at(5).text().includes('-')).toBe(true); + expect(detailTypes.at(6).text()).toBe('Original delivery address:'); + expect(detailDefinitions.at(6).text().includes('-')).toBe(true); + expect(detailTypes.at(7).text()).toBe('SIT entry date:'); + expect(detailDefinitions.at(7).text().includes('-')).toBe(true); + expect(detailTypes.at(8).text()).toBe('First available delivery date 1:'); + expect(detailDefinitions.at(8).text().includes('15 Sep 2020')).toBe(true); + expect(detailTypes.at(9).text()).toBe('Customer contact attempt 1:'); + expect(detailDefinitions.at(9).text().includes('15 Sep 2020, 1200Z')).toBe(true); + + expect(detailTypes.at(10).text()).toBe('First available delivery date 2:'); + expect(detailDefinitions.at(10).text().includes('21 Sep 2020')).toBe(true); + expect(detailTypes.at(11).text()).toBe('Customer contact attempt 2:'); + expect(detailDefinitions.at(11).text().includes('21 Sep 2020, 2300Z')).toBe(true); + + expect(detailTypes.at(12).text()).toBe('Reason:'); + expect(detailDefinitions.at(12).text().includes('Took a detour')).toBe(true); }; describe('RequestedServiceItemsTable', () => { @@ -129,11 +135,11 @@ describe('RequestedServiceItemsTable', () => { expect(wrapper.find('.codeName').at(0).text()).toBe('Domestic crating '); expect(wrapper.find('.nameAndDate').at(0).text().includes('20 Nov 2020')).toBe(true); - expect(wrapper.find('.codeName').at(1).text()).toBe('Domestic destination 1st day SIT '); - expect(wrapper.find('.nameAndDate').at(1).text().includes('1 Sep 2020')).toBe(true); + expect(wrapper.find('.codeName').at(1).text()).toBe('Domestic origin 1st day SIT '); + expect(wrapper.find('.nameAndDate').at(1).text().includes('15 Oct 2020')).toBe(true); - expect(wrapper.find('.codeName').at(2).text()).toBe('Domestic origin 1st day SIT '); - expect(wrapper.find('.nameAndDate').at(2).text().includes('15 Oct 2020')).toBe(true); + expect(wrapper.find('.codeName').at(2).text()).toBe('Domestic destination 1st day SIT '); + expect(wrapper.find('.nameAndDate').at(2).text().includes('1 Sep 2020')).toBe(true); }); it('shows the service item detail text', () => { diff --git a/src/components/Office/ServiceItemDetails/ServiceItemDetails.jsx b/src/components/Office/ServiceItemDetails/ServiceItemDetails.jsx index 808b5bea1be..6c5a705c13f 100644 --- a/src/components/Office/ServiceItemDetails/ServiceItemDetails.jsx +++ b/src/components/Office/ServiceItemDetails/ServiceItemDetails.jsx @@ -1,13 +1,17 @@ import React from 'react'; import { isEmpty, sortBy } from 'lodash'; import classnames from 'classnames'; +import moment from 'moment'; import { ServiceItemDetailsShape } from '../../../types/serviceItems'; import { trimFileName } from '../../../utils/serviceItems'; import styles from './ServiceItemDetails.module.scss'; +import { ShipmentShape } from 'types/shipment'; +import { SitStatusShape } from 'types/sitStatusShape'; import { formatDateWithUTC } from 'shared/dates'; +import { formatCityStateAndPostalCode } from 'utils/shipmentDisplay'; import { formatWeight, convertFromThousandthInchToInch } from 'utils/formatters'; function generateDetailText(details, id, className) { @@ -20,7 +24,7 @@ function generateDetailText(details, id, className) { return detailList; } -const generateDestinationSITDetailSection = (id, serviceRequestDocUploads, details, code) => { +const generateDestinationSITDetailSection = (id, serviceRequestDocUploads, details, code, shipment, sitStatus) => { const { customerContacts } = details; // Below we are using the sortBy func in lodash to sort the customer contacts // by the firstAvailableDeliveryDate field. sortBy returns a new @@ -34,76 +38,133 @@ const generateDestinationSITDetailSection = (id, serviceRequestDocUploads, detai 'First available delivery date 1': '-', 'Customer contact 1': '-', }); + const numberOfDaysApprovedForDOASIT = shipment.sitDaysAllowance ? shipment.sitDaysAllowance - 1 : 0; + const sitEndDate = + sitStatus && + sitStatus.currentSIT?.sitAllowanceEndDate && + formatDateWithUTC(sitStatus.currentSIT.sitAllowanceEndDate, 'DD MMM YYYY'); return (
- {code === 'DDDSIT' + {code === 'DDFSIT' ? generateDetailText({ - 'SIT departure date': details.sitDepartureDate - ? formatDateWithUTC(details.sitDepartureDate, 'DD MMM YYYY') + 'Original delivery address': details.sitDestinationOriginalAddress + ? formatCityStateAndPostalCode(details.sitDestinationOriginalAddress) : '-', - }) - : null} - {code === 'DDFSIT' || code === 'DDASIT' - ? generateDetailText({ 'SIT entry date': details.sitEntryDate ? formatDateWithUTC(details.sitEntryDate, 'DD MMM YYYY') : '-', }) : null} - - {!isEmpty(sortedCustomerContacts) - ? sortedCustomerContacts.map((contact, index) => ( - <> - {generateDetailText( - { - [`First available delivery date ${index + 1}`]: - contact && contact.firstAvailableDeliveryDate - ? formatDateWithUTC(contact.firstAvailableDeliveryDate, 'DD MMM YYYY') - : '-', - [`Customer contact attempt ${index + 1}`]: - contact && contact.dateOfContact && contact.timeMilitary - ? `${formatDateWithUTC(contact.dateOfContact, 'DD MMM YYYY')}, ${contact.timeMilitary}` - : '-', - }, - id, - )} - - )) - : defaultDetailText} - {generateDetailText({ Reason: details.reason ? details.reason : '-' })} - {details.rejectionReason && - generateDetailText({ 'Rejection reason': details.rejectionReason }, id, 'margin-top-2')} - {!isEmpty(serviceRequestDocUploads) ? ( -
-

Download service item documentation:

- {serviceRequestDocUploads.map((file) => ( + {code === 'DDASIT' + ? generateDetailText( + { + 'Original delivery address': details.sitDestinationOriginalAddress + ? formatCityStateAndPostalCode(details.sitDestinationOriginalAddress) + : '-', + "Add'l SIT Start Date": details.sitEntryDate + ? moment.utc(details.sitEntryDate).add(1, 'days').format('DD MMM YYYY') + : '-', + '# of days approved for': shipment.sitDaysAllowance ? `${numberOfDaysApprovedForDOASIT} days` : '-', + 'SIT expiration date': sitEndDate || '-', + 'Customer contacted homesafe': details.sitCustomerContacted + ? formatDateWithUTC(details.sitCustomerContacted, 'DD MMM YYYY') + : '-', + 'Customer requested delivery date': details.sitRequestedDelivery + ? formatDateWithUTC(details.sitRequestedDelivery, 'DD MMM YYYY') + : '-', + 'SIT departure date': details.sitDepartureDate + ? formatDateWithUTC(details.sitDepartureDate, 'DD MMM YYYY') + : '-', + }, + id, + ) + : null} + {code === 'DDSFSC' + ? generateDetailText( + { + 'Original delivery address': details.sitDestinationOriginalAddress + ? formatCityStateAndPostalCode(details.sitDestinationOriginalAddress) + : '-', + 'Final delivery address': details.sitDestinationFinalAddress + ? formatCityStateAndPostalCode(details.sitDestinationFinalAddress) + : '-', + 'Delivery into SIT miles': details.sitDeliveryMiles ? details.sitDeliveryMiles : '-', + }, + id, + ) + : null} + {code === 'DDDSIT' + ? generateDetailText( + { + 'Original delivery address': details.sitDestinationOriginalAddress + ? formatCityStateAndPostalCode(details.sitDestinationOriginalAddress) + : '-', + 'Final delivery address': details.sitDestinationFinalAddress + ? formatCityStateAndPostalCode(details.sitDestinationFinalAddress) + : '-', + 'Delivery into SIT miles': details.sitDeliveryMiles ? details.sitDeliveryMiles : '-', + }, + id, + ) + : null} + {code === 'DDFSIT' && ( + <> + {!isEmpty(sortedCustomerContacts) + ? sortedCustomerContacts.map((contact, index) => ( + <> + {generateDetailText( + { + [`First available delivery date ${index + 1}`]: + contact && contact.firstAvailableDeliveryDate + ? formatDateWithUTC(contact.firstAvailableDeliveryDate, 'DD MMM YYYY') + : '-', + [`Customer contact attempt ${index + 1}`]: + contact && contact.dateOfContact && contact.timeMilitary + ? `${formatDateWithUTC(contact.dateOfContact, 'DD MMM YYYY')}, ${contact.timeMilitary}` + : '-', + }, + id, + )} + + )) + : defaultDetailText} + {generateDetailText({ Reason: details.reason ? details.reason : '-' })} + {details.rejectionReason && + generateDetailText({ 'Rejection reason': details.rejectionReason }, id, 'margin-top-2')} + {!isEmpty(serviceRequestDocUploads) ? (
- - {trimFileName(file.filename)} - +

Download service item documentation:

+ {serviceRequestDocUploads.map((file) => ( + + ))}
- ))} -
- ) : null} + ) : null} + + )}
); }; -const ServiceItemDetails = ({ id, code, details, serviceRequestDocs }) => { +const ServiceItemDetails = ({ id, code, details, serviceRequestDocs, shipment, sitStatus }) => { const serviceRequestDocUploads = serviceRequestDocs?.map((doc) => doc.uploads[0]); let detailSection; switch (code) { - case 'DOFSIT': - case 'DOASIT': { + case 'DOFSIT': { detailSection = (
{generateDetailText( { + 'Original pickup address': details.sitOriginHHGOriginalAddress + ? formatCityStateAndPostalCode(details.sitOriginHHGOriginalAddress) + : '-', 'SIT entry date': details.sitEntryDate ? formatDateWithUTC(details.sitEntryDate, 'DD MMM YYYY') : '-', - ZIP: details.SITPostalCode ? details.SITPostalCode : '-', Reason: details.reason ? details.reason : '-', }, id, @@ -127,15 +188,105 @@ const ServiceItemDetails = ({ id, code, details, serviceRequestDocs }) => { ); break; } - case 'DOPSIT': + case 'DOASIT': { + const numberOfDaysApprovedForDOASIT = shipment.sitDaysAllowance ? shipment.sitDaysAllowance - 1 : 0; + const sitEndDate = + sitStatus && + sitStatus.currentSIT?.sitAllowanceEndDate && + formatDateWithUTC(sitStatus.currentSIT.sitAllowanceEndDate, 'DD MMM YYYY'); + + detailSection = ( +
+
+ {generateDetailText( + { + 'Original pickup address': details.sitOriginHHGOriginalAddress + ? formatCityStateAndPostalCode(details.sitOriginHHGOriginalAddress) + : '-', + "Add'l SIT Start Date": details.sitEntryDate + ? moment.utc(details.sitEntryDate).add(1, 'days').format('DD MMM YYYY') + : '-', + '# of days approved for': shipment.sitDaysAllowance ? `${numberOfDaysApprovedForDOASIT} days` : '-', + 'SIT expiration date': sitEndDate || '-', + 'Customer contacted homesafe': details.sitCustomerContacted + ? formatDateWithUTC(details.sitCustomerContacted, 'DD MMM YYYY') + : '-', + 'Customer requested delivery date': details.sitRequestedDelivery + ? formatDateWithUTC(details.sitRequestedDelivery, 'DD MMM YYYY') + : '-', + 'SIT departure date': details.sitDepartureDate + ? formatDateWithUTC(details.sitDepartureDate, 'DD MMM YYYY') + : '-', + }, + id, + )} + {details.rejectionReason && + generateDetailText({ 'Rejection reason': details.rejectionReason }, id, 'margin-top-2')} + {!isEmpty(serviceRequestDocUploads) ? ( +
+

Download service item documentation:

+ {serviceRequestDocUploads.map((file) => ( + + ))} +
+ ) : null} +
+
+ ); + break; + } + case 'DOPSIT': { + detailSection = ( +
+
+ {generateDetailText( + { + 'Original pickup address': details.sitOriginHHGOriginalAddress + ? formatCityStateAndPostalCode(details.sitOriginHHGOriginalAddress) + : '-', + 'Actual pickup address': details.sitOriginHHGActualAddress + ? formatCityStateAndPostalCode(details.sitOriginHHGActualAddress) + : '-', + 'Delivery into SIT miles': details.sitDeliveryMiles ? details.sitDeliveryMiles : '-', + }, + id, + )} + {details.rejectionReason && + generateDetailText({ 'Rejection reason': details.rejectionReason }, id, 'margin-top-2')} + {!isEmpty(serviceRequestDocUploads) ? ( +
+

Download service item documentation:

+ {serviceRequestDocUploads.map((file) => ( + + ))} +
+ ) : null} +
+
+ ); + break; + } case 'DOSFSC': { detailSection = (
{generateDetailText( { - ZIP: details.SITPostalCode ? details.SITPostalCode : '-', - Reason: details.reason ? details.reason : '-', + 'Original pickup address': details.sitOriginHHGOriginalAddress + ? formatCityStateAndPostalCode(details.sitOriginHHGOriginalAddress) + : '-', + 'Actual pickup address': details.sitOriginHHGActualAddress + ? formatCityStateAndPostalCode(details.sitOriginHHGActualAddress) + : '-', + 'Delivery into SIT miles': details.sitDeliveryMiles ? details.sitDeliveryMiles : '-', }, id, )} @@ -158,12 +309,38 @@ const ServiceItemDetails = ({ id, code, details, serviceRequestDocs }) => { ); break; } - case 'DDFSIT': - case 'DDASIT': - case 'DDDSIT': + case 'DDASIT': { + detailSection = generateDestinationSITDetailSection( + id, + serviceRequestDocUploads, + details, + code, + shipment, + sitStatus, + ); + break; + } + case 'DDDSIT': { + detailSection = generateDestinationSITDetailSection( + id, + serviceRequestDocUploads, + details, + code, + shipment, + sitStatus, + ); + break; + } case 'DDSFSC': { - detailSection = generateDestinationSITDetailSection(id, serviceRequestDocUploads, details, code); + detailSection = generateDestinationSITDetailSection( + id, + serviceRequestDocUploads, + details, + code, + shipment, + sitStatus, + ); break; } case 'DCRT': @@ -299,7 +476,15 @@ const ServiceItemDetails = ({ id, code, details, serviceRequestDocs }) => { ServiceItemDetails.propTypes = ServiceItemDetailsShape.isRequired; +ServiceItemDetails.propTypes = { + details: ServiceItemDetailsShape, + shipment: ShipmentShape, + sitStatus: SitStatusShape, +}; + ServiceItemDetails.defaultProps = { details: {}, + shipment: {}, + sitStatus: undefined, }; export default ServiceItemDetails; diff --git a/src/components/Office/ServiceItemDetails/ServiceItemDetails.test.jsx b/src/components/Office/ServiceItemDetails/ServiceItemDetails.test.jsx index 01ae7330bdc..52fbd47c689 100644 --- a/src/components/Office/ServiceItemDetails/ServiceItemDetails.test.jsx +++ b/src/components/Office/ServiceItemDetails/ServiceItemDetails.test.jsx @@ -3,11 +3,21 @@ import { render, screen } from '@testing-library/react'; import ServiceItemDetails from './ServiceItemDetails'; +const sitStatus = { + currentSIT: { + sitAllowanceEndDate: '2024-03-17', + }, +}; + +const shipment = { + sitDaysAllowance: 90, +}; + const details = { description: 'some description', pickupPostalCode: '90210', SITPostalCode: '12345', - sitEntryDate: '2020-10-10', + sitEntryDate: '2024-03-11T00:00:00.000Z', reason: 'some reason', itemDimensions: { length: 1000, width: 2500, height: 3000 }, crateDimensions: { length: 2000, width: 3500, height: 4000 }, @@ -16,6 +26,42 @@ const details = { { timeMilitary: '2300Z', firstAvailableDeliveryDate: '2020-09-21', dateOfContact: '2020-09-21' }, ], estimatedWeight: 2500, + sitCustomerContacted: '2024-03-14T00:00:00.000Z', + sitRequestedDelivery: '2024-03-15T00:00:00.000Z', + sitDepartureDate: '2024-03-16T00:00:00.000Z', + sitDeliveryMiles: 50, + sitOriginHHGOriginalAddress: { + city: 'Origin Original Tampa', + eTag: 'MjAyNC0wMy0xMlQxOTo1OTowOC41NjkxMzla', + id: '7fd6cb90-54cd-44d8-8735-102e28734d84', + postalCode: '33621', + state: 'FL', + streetAddress1: 'MacDill', + }, + sitOriginHHGActualAddress: { + city: 'Origin Actual MacDill', + eTag: 'HjAyNC0wMy0xMlQxOTo1OTowOC41NjkxMzla', + id: '8fd6cb90-54cd-44d8-8735-102e28734d84', + postalCode: '33621', + state: 'FL', + streetAddress1: 'MacDill', + }, + sitDestinationOriginalAddress: { + city: 'Destination Original Tampa', + eTag: 'MjAyNC0wMy0xMlQxOTo1OTowOC41NjkxMzla', + id: '7fd6cb90-54cd-44d8-8735-102e28734d84', + postalCode: '33621', + state: 'FL', + streetAddress1: 'MacDill', + }, + sitDestinationFinalAddress: { + city: 'Destination Final MacDill', + eTag: 'HjAyNC0wMy0xMlQxOTo1OTowOC41NjkxMzla', + id: '8fd6cb90-54cd-44d8-8735-102e28734d84', + postalCode: '33621', + state: 'FL', + streetAddress1: 'MacDill', + }, }; const serviceRequestDocs = [ @@ -34,30 +80,139 @@ const nilDetails = { estimatedWeight: null, }; +describe('ServiceItemDetails Domestic Destination SIT', () => { + it('renders DDASIT details', () => { + render( + , + ); + expect(screen.getByText('Original delivery address:')).toBeInTheDocument(); + expect(screen.getByText('Destination Original Tampa, FL 33621')).toBeInTheDocument(); + + expect(screen.getByText("Add'l SIT Start Date:")).toBeInTheDocument(); + expect(screen.getByText('12 Mar 2024')).toBeInTheDocument(); + + expect(screen.getByText('Customer contacted homesafe:')).toBeInTheDocument(); + expect(screen.getByText('14 Mar 2024')).toBeInTheDocument(); + + expect(screen.getByText('# of days approved for:')).toBeInTheDocument(); + expect(screen.getByText('89 days')).toBeInTheDocument(); + + expect(screen.getByText('SIT expiration date:')).toBeInTheDocument(); + expect(screen.getByText('17 Mar 2024')).toBeInTheDocument(); + + expect(screen.getByText('Customer requested delivery date:')).toBeInTheDocument(); + expect(screen.getByText('15 Mar 2024')).toBeInTheDocument(); + + expect(screen.getByText('SIT departure date:')).toBeInTheDocument(); + expect(screen.getByText('16 Mar 2024')).toBeInTheDocument(); + }); + + it('renders DDDSIT details', () => { + render(); + expect(screen.getByText('Original delivery address:')).toBeInTheDocument(); + expect(screen.getByText('Destination Original Tampa, FL 33621')).toBeInTheDocument(); + + expect(screen.getByText('Final delivery address:')).toBeInTheDocument(); + expect(screen.getByText('Destination Final MacDill, FL 33621')).toBeInTheDocument(); + + expect(screen.getByText('Delivery into SIT miles:')).toBeInTheDocument(); + expect(screen.getByText('50')).toBeInTheDocument(); + }); + it('renders DDFSIT details', () => { + render(); + expect(screen.getByText('Original delivery address:')).toBeInTheDocument(); + expect(screen.getByText('Destination Original Tampa, FL 33621')).toBeInTheDocument(); + }); + it('renders DDSFSC details', () => { + render(); + expect(screen.getByText('Original delivery address:')).toBeInTheDocument(); + expect(screen.getByText('Destination Original Tampa, FL 33621')).toBeInTheDocument(); + + expect(screen.getByText('Final delivery address:')).toBeInTheDocument(); + expect(screen.getByText('Destination Final MacDill, FL 33621')).toBeInTheDocument(); + + expect(screen.getByText('Delivery into SIT miles:')).toBeInTheDocument(); + expect(screen.getByText('50')).toBeInTheDocument(); + }); +}); + describe('ServiceItemDetails Domestic Origin SIT', () => { - it.each([['DOASIT'], ['DOPSIT']])('renders ZIP, reason, and service request documents', (code) => { - render(); + it(`renders DOASIT details`, () => { + render( + , + ); - expect(screen.getByText('ZIP:')).toBeInTheDocument(); - expect(screen.getByText('12345')).toBeInTheDocument(); - expect(screen.getByText('Reason:')).toBeInTheDocument(); - expect(screen.getByText('some reason')).toBeInTheDocument(); - expect(screen.getByText('Download service item documentation:')).toBeInTheDocument(); - const downloadLink = screen.getByText('receipt.pdf'); - expect(downloadLink).toBeInstanceOf(HTMLAnchorElement); + expect(screen.getByText('Original pickup address:')).toBeInTheDocument(); + expect(screen.getByText('Origin Original Tampa, FL 33621')).toBeInTheDocument(); + + expect(screen.getByText("Add'l SIT Start Date:")).toBeInTheDocument(); + expect(screen.getByText('12 Mar 2024')).toBeInTheDocument(); + + expect(screen.getByText('# of days approved for:')).toBeInTheDocument(); + expect(screen.getByText('89 days')).toBeInTheDocument(); + + expect(screen.getByText('SIT expiration date:')).toBeInTheDocument(); + expect(screen.getByText('17 Mar 2024')).toBeInTheDocument(); + + expect(screen.getByText('Customer contacted homesafe:')).toBeInTheDocument(); + expect(screen.getByText('14 Mar 2024')).toBeInTheDocument(); + + expect(screen.getByText('Customer requested delivery date:')).toBeInTheDocument(); + expect(screen.getByText('15 Mar 2024')).toBeInTheDocument(); + + expect(screen.getByText('SIT departure date:')).toBeInTheDocument(); + expect(screen.getByText('16 Mar 2024')).toBeInTheDocument(); + }); + + it(`renders DOPSIT details`, () => { + render(); + + expect(screen.getByText('Original pickup address:')).toBeInTheDocument(); + expect(screen.getByText('Origin Original Tampa, FL 33621')).toBeInTheDocument(); + + expect(screen.getByText('Actual pickup address:')).toBeInTheDocument(); + expect(screen.getByText('Origin Actual MacDill, FL 33621')).toBeInTheDocument(); + + expect(screen.getByText('Delivery into SIT miles:')).toBeInTheDocument(); + expect(screen.getByText('50')).toBeInTheDocument(); + }); + + it(`renders DOSFSC details`, () => { + render(); + + expect(screen.getByText('Original pickup address:')).toBeInTheDocument(); + expect(screen.getByText('Origin Original Tampa, FL 33621')).toBeInTheDocument(); + + expect(screen.getByText('Actual pickup address:')).toBeInTheDocument(); + expect(screen.getByText('Origin Actual MacDill, FL 33621')).toBeInTheDocument(); + + expect(screen.getByText('Delivery into SIT miles:')).toBeInTheDocument(); + expect(screen.getByText('50')).toBeInTheDocument(); }); }); describe('ServiceItemDetails for DOFSIT', () => { - it('renders SIT entry date, ZIP, and reason', () => { + it('renders SIT entry date, ZIP, original pickup address, and reason', () => { render(); + expect(screen.getByText('Original pickup address:')).toBeInTheDocument(); + expect(screen.getByText('Origin Original Tampa, FL 33621')).toBeInTheDocument(); expect(screen.getByText('SIT entry date:')).toBeInTheDocument(); - expect(screen.getByText('10 Oct 2020')).toBeInTheDocument(); - expect(screen.getByText('ZIP:')).toBeInTheDocument(); - expect(screen.getByText('12345')).toBeInTheDocument(); - expect(screen.getByText('Reason:')).toBeInTheDocument(); - expect(screen.getByText('some reason')).toBeInTheDocument(); + expect(screen.getByText('11 Mar 2024')).toBeInTheDocument(); expect(screen.getByText('Download service item documentation:')).toBeInTheDocument(); const downloadLink = screen.getByText('receipt.pdf'); expect(downloadLink).toBeInstanceOf(HTMLAnchorElement); @@ -65,22 +220,19 @@ describe('ServiceItemDetails for DOFSIT', () => { }); describe('ServiceItemDetails Domestic Destination SIT', () => { - it.each([['DDDSIT'], ['DDASIT'], ['DDFSIT']])( - 'renders first and second customer contact and available delivery date', - (code) => { - render(); - - expect(screen.getByText('Customer contact attempt 1:')).toBeInTheDocument(); - expect(screen.getByText('15 Sep 2020, 1200Z')).toBeInTheDocument(); - expect(screen.getByText('15 Sep 2020')).toBeInTheDocument(); - expect(screen.getByText('Customer contact attempt 2:')).toBeInTheDocument(); - expect(screen.getByText('21 Sep 2020, 2300Z')).toBeInTheDocument(); - expect(screen.getByText('21 Sep 2020')).toBeInTheDocument(); - expect(screen.getByText('Download service item documentation:')).toBeInTheDocument(); - const downloadLink = screen.getByText('receipt.pdf'); - expect(downloadLink).toBeInstanceOf(HTMLAnchorElement); - }, - ); + it('renders first and second customer contact and available delivery date', () => { + render(); + + expect(screen.getByText('Customer contact attempt 1:')).toBeInTheDocument(); + expect(screen.getByText('15 Sep 2020, 1200Z')).toBeInTheDocument(); + expect(screen.getByText('15 Sep 2020')).toBeInTheDocument(); + expect(screen.getByText('Customer contact attempt 2:')).toBeInTheDocument(); + expect(screen.getByText('21 Sep 2020, 2300Z')).toBeInTheDocument(); + expect(screen.getByText('21 Sep 2020')).toBeInTheDocument(); + expect(screen.getByText('Download service item documentation:')).toBeInTheDocument(); + const downloadLink = screen.getByText('receipt.pdf'); + expect(downloadLink).toBeInstanceOf(HTMLAnchorElement); + }); }); describe('ServiceItemDetails Crating', () => { diff --git a/src/components/Office/ServiceItemsTable/ServiceItemsTable.jsx b/src/components/Office/ServiceItemsTable/ServiceItemsTable.jsx index be871beeabc..0e96b67b60f 100644 --- a/src/components/Office/ServiceItemsTable/ServiceItemsTable.jsx +++ b/src/components/Office/ServiceItemsTable/ServiceItemsTable.jsx @@ -18,6 +18,31 @@ import { permissionTypes } from 'constants/permissions'; import { selectDateFieldByStatus, selectDatePrefixByStatus } from 'utils/dates'; import { useGHCGetMoveHistory, useMovePaymentRequestsQueries } from 'hooks/queries'; import ToolTip from 'shared/ToolTip/ToolTip'; +import { ShipmentShape } from 'types'; + +// Sorts service items in an order preferred by the customer +// Currently only SIT receives special sorting +function sortServiceItems(items) { + // Filter and sort destination SIT. Code index is also the sort order + const destinationServiceItemCodes = ['DDFSIT', 'DDASIT', 'DDDSIT', 'DDSFSC']; + const destinationServiceItems = items.filter((item) => destinationServiceItemCodes.includes(item.code)); + const sortedDestinationServiceItems = destinationServiceItems.sort( + (a, b) => destinationServiceItemCodes.indexOf(a.code) - destinationServiceItemCodes.indexOf(b.code), + ); + // Filter origin SIT. Code index is also the sort order + const originServiceItemCodes = ['DOFSIT', 'DOASIT', 'DOPSIT', 'DOSFSC']; + const originServiceItems = items.filter((item) => originServiceItemCodes.includes(item.code)); + const sortedOriginServiceItems = originServiceItems.sort( + (a, b) => originServiceItemCodes.indexOf(a.code) - originServiceItemCodes.indexOf(b.code), + ); + + // Filter all service items that are not specifically sorted + const remainingServiceItems = items.filter( + (item) => !destinationServiceItemCodes.includes(item.code) && !originServiceItemCodes.includes(item.code), + ); + + return [...remainingServiceItems, ...sortedOriginServiceItems, ...sortedDestinationServiceItems]; +} const ServiceItemsTable = ({ serviceItems, @@ -26,6 +51,7 @@ const ServiceItemsTable = ({ handleShowRejectionDialog, handleShowEditSitAddressModal, handleShowEditSitEntryDateModal, + shipment, }) => { const getServiceItemDisplayDate = (item) => { const prefix = selectDatePrefixByStatus(statusForTableType); @@ -129,7 +155,8 @@ const ServiceItemsTable = ({ return resubmittedServiceItemValues; }; - const tableRows = serviceItems.map((serviceItem) => { + const sortedServiceItems = sortServiceItems(serviceItems); + const tableRows = sortedServiceItems.map((serviceItem) => { const { id, code, details, mtoShipmentID, sitAddressUpdates, serviceRequestDocuments, ...item } = serviceItem; let hasPaymentRequestBeenMade; // if there are service items in the payment requests, we want to look to see if the service item is in there @@ -181,6 +208,8 @@ const ServiceItemsTable = ({ details={details} serviceRequestDocs={serviceRequestDocuments} serviceItem={serviceItem} + shipment={shipment} + sitStatus={shipment.sitStatus} /> @@ -296,6 +325,11 @@ ServiceItemsTable.propTypes = { handleShowRejectionDialog: PropTypes.func.isRequired, statusForTableType: PropTypes.string.isRequired, serviceItems: PropTypes.arrayOf(ServiceItemDetailsShape).isRequired, + shipment: ShipmentShape, +}; + +ServiceItemsTable.defaultProps = { + shipment: {}, }; export default ServiceItemsTable; diff --git a/src/components/Office/ServiceItemsTable/ServiceItemsTable.test.jsx b/src/components/Office/ServiceItemsTable/ServiceItemsTable.test.jsx index 4e389f1a4fd..735c2754e68 100644 --- a/src/components/Office/ServiceItemsTable/ServiceItemsTable.test.jsx +++ b/src/components/Office/ServiceItemsTable/ServiceItemsTable.test.jsx @@ -95,6 +95,14 @@ describe('ServiceItemsTable', () => { }, { timeMilitary: '0800Z', firstAvailableDeliveryDate: '2021-01-01', dateOfContact: '2021-01-01' }, ], + sitDestinationOriginalAddress: { + city: 'Destination Original Tampa', + eTag: 'MjAyNC0wMy0xMlQxOTo1OTowOC41NjkxMzla', + id: '7fd6cb90-54cd-44d8-8735-102e28734d84', + postalCode: '33621', + state: 'FL', + streetAddress1: 'MacDill', + }, }, }, ]; @@ -110,18 +118,21 @@ describe('ServiceItemsTable', () => { ); expect(wrapper.find('table').exists()).toBe(true); + expect(wrapper.find('dt').at(0).text()).toBe('Original delivery address:'); + expect(wrapper.find('dd').at(0).text()).toBe('Destination Original Tampa, FL 33621'); - expect(wrapper.find('dt').at(0).text()).toBe('SIT entry date:'); - expect(wrapper.find('dd').at(0).text()).toBe('31 Dec 2020'); - expect(wrapper.find('dt').at(1).text()).toBe('First available delivery date 1:'); + expect(wrapper.find('dt').at(1).text()).toBe('SIT entry date:'); expect(wrapper.find('dd').at(1).text()).toBe('31 Dec 2020'); - expect(wrapper.find('dt').at(2).text()).toBe('Customer contact attempt 1:'); - expect(wrapper.find('dd').at(2).text()).toBe('31 Dec 2020, 0400Z'); - expect(wrapper.find('dt').at(3).text()).toBe('First available delivery date 2:'); - expect(wrapper.find('dd').at(3).text()).toBe('01 Jan 2021'); - expect(wrapper.find('dt').at(4).text()).toBe('Customer contact attempt 2:'); - expect(wrapper.find('dd').at(4).text()).toBe('01 Jan 2021, 0800Z'); + expect(wrapper.find('dt').at(2).text()).toBe('First available delivery date 1:'); + expect(wrapper.find('dd').at(2).text()).toBe('31 Dec 2020'); + expect(wrapper.find('dt').at(3).text()).toBe('Customer contact attempt 1:'); + expect(wrapper.find('dd').at(3).text()).toBe('31 Dec 2020, 0400Z'); + + expect(wrapper.find('dt').at(4).text()).toBe('First available delivery date 2:'); + expect(wrapper.find('dd').at(4).text()).toBe('01 Jan 2021'); + expect(wrapper.find('dt').at(5).text()).toBe('Customer contact attempt 2:'); + expect(wrapper.find('dd').at(5).text()).toBe('01 Jan 2021, 0800Z'); }); it('should render the SITPostalCode ZIP, and reason for DOFSIT service item', () => { @@ -136,6 +147,14 @@ describe('ServiceItemsTable', () => { SITPostalCode: '12345', reason: 'This is the reason', sitEntryDate: '2023-12-25T00:00:00.000Z', + sitOriginHHGOriginalAddress: { + city: 'Origin Original Tampa', + eTag: 'MjAyNC0wMy0xMlQxOTo1OTowOC41NjkxMzla', + id: '7fd6cb90-54cd-44d8-8735-102e28734d84', + postalCode: '33621', + state: 'FL', + streetAddress1: 'MacDill', + }, }, }, ]; @@ -149,10 +168,11 @@ describe('ServiceItemsTable', () => { /> , ); - expect(wrapper.find('dt').at(0).contains('SIT entry date')).toBe(true); - expect(wrapper.find('dd').at(0).contains('25 Dec 2023')).toBe(true); - expect(wrapper.find('dt').at(1).contains('ZIP')).toBe(true); - expect(wrapper.find('dd').at(1).contains('12345')).toBe(true); + expect(wrapper.find('dt').at(0).contains('Original pickup address')).toBe(true); + expect(wrapper.find('dd').at(0).contains('Origin Original Tampa, FL 33621')).toBe(true); + + expect(wrapper.find('dt').at(1).contains('SIT entry date')).toBe(true); + expect(wrapper.find('dd').at(1).contains('25 Dec 2023')).toBe(true); expect(wrapper.find('dt').at(2).contains('Reason')).toBe(true); expect(wrapper.find('dd').at(2).contains('This is the reason')).toBe(true); }); diff --git a/src/components/Office/ShipmentDetails/ShipmentDetailsMain.jsx b/src/components/Office/ShipmentDetails/ShipmentDetailsMain.jsx index 05d0fecf691..301cc19e45d 100644 --- a/src/components/Office/ShipmentDetails/ShipmentDetailsMain.jsx +++ b/src/components/Office/ShipmentDetails/ShipmentDetailsMain.jsx @@ -256,6 +256,8 @@ const ShipmentDetailsMain = ({ reweighID: shipment.reweigh?.id, reweighWeight: shipment.reweigh?.weight, shipmentType: shipment.shipmentType, + shipmentActualProGearWeight: shipment.actualProGearWeight, + shipmentActualSpouseProGearWeight: shipment.actualSpouseProGearWeight, }} handleRequestReweighModal={handleRequestReweighModal} /> diff --git a/src/components/Office/ShipmentDisplay/ShipmentDisplay.jsx b/src/components/Office/ShipmentDisplay/ShipmentDisplay.jsx index e5c45d88fa8..a9d04fa9df2 100644 --- a/src/components/Office/ShipmentDisplay/ShipmentDisplay.jsx +++ b/src/components/Office/ShipmentDisplay/ShipmentDisplay.jsx @@ -5,7 +5,7 @@ import { Checkbox, Tag } from '@trussworks/react-uswds'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import classnames from 'classnames'; -import DownloadAOAErrorModal from 'shared/DownloadAOAErrorModal/DownloadAOAErrorModal'; +import DownloadPacketErrorModal from 'shared/DownloadPacketErrorModal/DownloadPacketErrorModal'; import { EditButton, ReviewButton } from 'components/form/IconButtons'; import ShipmentInfoListSelector from 'components/Office/DefinitionLists/ShipmentInfoListSelector'; import ShipmentContainer from 'components/Office/ShipmentContainer/ShipmentContainer'; @@ -42,7 +42,7 @@ const ShipmentDisplay = ({ const [isExpanded, setIsExpanded] = useState(false); const tac = retrieveTAC(displayInfo.tacType, ordersLOA); const sac = retrieveSAC(displayInfo.sacType, ordersLOA); - const [isDownloadAOAErrorModalVisible, setIsDownloadAOAErrorModalVisible] = useState(false); + const [isDownloadPacketErrorModalVisible, setIsDownloadPacketErrorModalVisible] = useState(false); const disableApproval = errorIfMissing.some((requiredInfo) => objectIsMissingFieldWithCondition(displayInfo, requiredInfo), @@ -56,8 +56,8 @@ const ShipmentDisplay = ({ 'chevron-down': !isExpanded, }); - const toggleDownloadAOAErrorModal = () => { - setIsDownloadAOAErrorModalVisible((prev) => !prev); + const toggleDownloadPacketErrorModal = () => { + setIsDownloadPacketErrorModalVisible((prev) => !prev); }; return ( @@ -110,9 +110,12 @@ const ShipmentDisplay = ({ errorIfMissing={errorIfMissing} showWhenCollapsed={showWhenCollapsed} neverShow={neverShow} - onErrorModalToggle={toggleDownloadAOAErrorModal} + onErrorModalToggle={toggleDownloadPacketErrorModal} + /> + - {editURL && ( { const isTOO = userRole === roleTypes.TOO; const isServiceCounselor = userRole === roleTypes.SERVICES_COUNSELOR; const showCloseoutOffice = - isServiceCounselor && isPPM && (serviceMember.agency === 'ARMY' || serviceMember.agency === 'AIR_FORCE'); + isServiceCounselor && + isPPM && + (serviceMember.agency === SERVICE_MEMBER_AGENCIES.ARMY || + serviceMember.agency === SERVICE_MEMBER_AGENCIES.AIR_FORCE || + serviceMember.agency === SERVICE_MEMBER_AGENCIES.SPACE_FORCE); const shipmentDestinationAddressOptions = dropdownInputOptions(shipmentDestinationTypes); diff --git a/src/components/Office/ShipmentSITDisplay/ShipmentSITDisplay.jsx b/src/components/Office/ShipmentSITDisplay/ShipmentSITDisplay.jsx index 1cf0f1dd352..157e1c973ad 100644 --- a/src/components/Office/ShipmentSITDisplay/ShipmentSITDisplay.jsx +++ b/src/components/Office/ShipmentSITDisplay/ShipmentSITDisplay.jsx @@ -163,14 +163,7 @@ const SitStatusTables = ({ shipment, sitExtensions, sitStatus, openModalButton, {currentDaysInSIT > 0 &&

Current location: {currentLocation}

}
diff --git a/src/components/Office/ShipmentSITDisplay/ShipmentSITDisplay.test.jsx b/src/components/Office/ShipmentSITDisplay/ShipmentSITDisplay.test.jsx index 253b63be62e..3b70b573128 100644 --- a/src/components/Office/ShipmentSITDisplay/ShipmentSITDisplay.test.jsx +++ b/src/components/Office/ShipmentSITDisplay/ShipmentSITDisplay.test.jsx @@ -387,6 +387,6 @@ describe('ShipmentSITDisplay', () => { const sitStartAndEndTable = await screen.findByTestId('sitStartAndEndTable'); expect(sitStartAndEndTable).toBeInTheDocument(); expect(within(sitStartAndEndTable).getByText('SIT authorized end date')).toBeInTheDocument(); - expect(within(sitStartAndEndTable).getByText('15 Aug 2021')).toBeInTheDocument(); + expect(within(sitStartAndEndTable).getByText('28 Aug 2021')).toBeInTheDocument(); }); }); diff --git a/src/components/Office/ShipmentSITDisplay/ShipmentSITDisplayTestParams.js b/src/components/Office/ShipmentSITDisplay/ShipmentSITDisplayTestParams.js index 3825b32c34a..fcf3458fb1f 100644 --- a/src/components/Office/ShipmentSITDisplay/ShipmentSITDisplayTestParams.js +++ b/src/components/Office/ShipmentSITDisplay/ShipmentSITDisplayTestParams.js @@ -82,7 +82,6 @@ export const SITStatusOriginAuthorized = { daysInSIT: 15, sitEntryDate: '2021-08-13', sitAllowanceEndDate: '2021-08-28', - sitAuthorizedEndDate: '2021-08-15', sitCustomerContacted: '2021-08-26', sitRequestedDelivery: '2021-08-30', }, diff --git a/src/components/Office/ShipmentWeightDetails/ShipmentWeightDetails.jsx b/src/components/Office/ShipmentWeightDetails/ShipmentWeightDetails.jsx index 8a6fd69e145..24561a24fc2 100644 --- a/src/components/Office/ShipmentWeightDetails/ShipmentWeightDetails.jsx +++ b/src/components/Office/ShipmentWeightDetails/ShipmentWeightDetails.jsx @@ -18,6 +18,7 @@ import { permissionTypes } from 'constants/permissions'; const ShipmentWeightDetails = ({ estimatedWeight, initialWeight, shipmentInfo, handleRequestReweighModal }) => { const emDash = '\u2014'; const lowestWeight = returnLowestValue(initialWeight, shipmentInfo.reweighWeight); + const shipmentIsPPM = shipmentInfo.shipmentType === SHIPMENT_OPTIONS.PPM; const reweighHeader = (
@@ -55,6 +56,19 @@ const ShipmentWeightDetails = ({ estimatedWeight, initialWeight, shipmentInfo, h lowestWeight ? formatWeight(lowestWeight) : emDash, ]} /> + {!shipmentIsPPM && ( + + )}
); @@ -69,6 +83,8 @@ ShipmentWeightDetails.propTypes = { reweighID: PropTypes.string, reweighWeight: PropTypes.number, shipmentType: ShipmentOptionsOneOf.isRequired, + shipmentActualProGearWeight: PropTypes.number, + shipmentActualSpouseProGearWeight: PropTypes.number, }).isRequired, handleRequestReweighModal: PropTypes.func.isRequired, }; diff --git a/src/components/Office/ShipmentWeightDetails/ShipmentWeightDetails.test.jsx b/src/components/Office/ShipmentWeightDetails/ShipmentWeightDetails.test.jsx index ebee8e7e10d..378f8776957 100644 --- a/src/components/Office/ShipmentWeightDetails/ShipmentWeightDetails.test.jsx +++ b/src/components/Office/ShipmentWeightDetails/ShipmentWeightDetails.test.jsx @@ -15,6 +15,8 @@ const shipmentInfoReweighRequested = { ifMatchEtag: 'etag1', reweighID: 'reweighRequestID', shipmentType: SHIPMENT_OPTIONS.HHG, + shipmentActualProGearWeight: 800, + shipmentActualSpouseProGearWeight: 200, }; const shipmentInfoNoReweigh = { @@ -23,12 +25,20 @@ const shipmentInfoNoReweigh = { shipmentType: SHIPMENT_OPTIONS.HHG, }; +const shipmentInfoNoReweighPPM = { + shipmentID: 'shipment1', + ifMatchEtag: 'etag1', + shipmentType: SHIPMENT_OPTIONS.PPM, +}; + const shipmentInfoReweigh = { shipmentID: 'shipment1', ifMatchEtag: 'etag1', reweighID: 'reweighRequestID', reweighWeight: 1000, shipmentType: SHIPMENT_OPTIONS.HHG, + shipmentActualProGearWeight: 100, + shipmentActualSpouseProGearWeight: 50, }; const handleRequestReweighModal = jest.fn(); @@ -57,6 +67,43 @@ describe('ShipmentWeightDetails', () => { const actualWeight = await screen.findByText('Actual shipment weight'); expect(actualWeight).toBeTruthy(); + + const actualProGearWeight = await screen.findByText('Actual pro gear weight'); + expect(actualProGearWeight).toBeTruthy(); + + const actualSpouseProGearWeight = await screen.findByText('Actual spouse pro gear weight'); + expect(actualSpouseProGearWeight).toBeTruthy(); + }); + + it('does not render pro gear for PPMs', async () => { + render( + + + , + ); + + const estimatedWeight = await screen.findByText('Estimated weight'); + expect(estimatedWeight).toBeTruthy(); + + const initialWeight = await screen.findByText('Initial weight'); + expect(initialWeight).toBeTruthy(); + + const reweighButton = await screen.findByText('Request reweigh'); + expect(reweighButton).toBeTruthy(); + + const actualWeight = await screen.findByText('Actual shipment weight'); + expect(actualWeight).toBeTruthy(); + + const actualProGearWeight = await screen.queryByText('Actual pro gear weight'); + expect(actualProGearWeight).toBeNull(); + + const actualSpouseProGearWeight = await screen.queryByText('Actual spouse pro gear weight'); + expect(actualSpouseProGearWeight).toBeNull(); }); it('renders with estimated weight if not an NTSR', async () => { @@ -70,7 +117,11 @@ describe('ShipmentWeightDetails', () => { ); const estWeight = await screen.findByText('11,000 lbs'); + const actualProGearWeight = await screen.findByText('800 lbs'); + const actualSpouseProGearWeight = await screen.findByText('200 lbs'); expect(estWeight).toBeTruthy(); + expect(actualProGearWeight).toBeTruthy(); + expect(actualSpouseProGearWeight).toBeTruthy(); }); it('renders without estimated weight if an NTSR', async () => { @@ -97,7 +148,11 @@ describe('ShipmentWeightDetails', () => { ); const shipWeight = await screen.findAllByText('12,000 lbs'); + const actualProGearWeight = await screen.findByText('800 lbs'); + const actualSpouseProGearWeight = await screen.findByText('200 lbs'); expect(shipWeight).toBeTruthy(); + expect(actualProGearWeight).toBeTruthy(); + expect(actualSpouseProGearWeight).toBeTruthy(); }); it('calls the submit function when submit button is clicked', async () => { @@ -131,10 +186,14 @@ describe('ShipmentWeightDetails', () => { const reweighButton = await screen.queryByText('Request reweigh'); const reweighRequestedLabel = await screen.queryByText('reweigh requested'); const reweighedLabel = await screen.queryByText('reweighed'); + const actualProGearWeight = await screen.findByText('800 lbs'); + const actualSpouseProGearWeight = await screen.findByText('200 lbs'); expect(reweighButton).toBeFalsy(); expect(reweighRequestedLabel).toBeTruthy(); expect(reweighedLabel).toBeFalsy(); + expect(actualProGearWeight).toBeTruthy(); + expect(actualSpouseProGearWeight).toBeTruthy(); }); it('renders without the rewiegh button when the user does not have permission', async () => { @@ -163,9 +222,13 @@ describe('ShipmentWeightDetails', () => { const reweighRequestedLabel = await screen.queryByText('reweigh requested'); const reweighedLabel = await screen.queryByText('reweighed'); + const actualProGearWeight = await screen.findByText('100 lbs'); + const actualSpouseProGearWeight = await screen.findByText('50 lbs'); expect(reweighRequestedLabel).toBeFalsy(); expect(reweighedLabel).toBeTruthy(); + expect(actualProGearWeight).toBeTruthy(); + expect(actualSpouseProGearWeight).toBeTruthy(); }); it('renders the lowest of either reweight or actual weight', async () => { diff --git a/src/components/Office/SubmitSITExtensionModal/SubmitSITExtensionModal.jsx b/src/components/Office/SubmitSITExtensionModal/SubmitSITExtensionModal.jsx index eb22ee94702..83badacaa91 100644 --- a/src/components/Office/SubmitSITExtensionModal/SubmitSITExtensionModal.jsx +++ b/src/components/Office/SubmitSITExtensionModal/SubmitSITExtensionModal.jsx @@ -169,14 +169,7 @@ const SubmitSITExtensionModal = ({ shipment, sitStatus, onClose, onSubmit }) => requestReason: '', officeRemarks: '', daysApproved: String(shipment.sitDaysAllowance), - sitEndDate: formatDateForDatePicker( - moment( - sitStatus?.currentSIT?.sitAuthorizedEndDate == null - ? sitStatus.currentSIT.sitAllowanceEndDate - : sitStatus?.currentSIT?.sitAuthorizedEndDate, - swaggerDateFormat, - ), - ), + sitEndDate: formatDateForDatePicker(moment(sitStatus.currentSIT.sitAllowanceEndDate, swaggerDateFormat)), }; const minimumDaysAllowed = sitStatus.calculatedTotalDaysInSIT - sitStatus.currentSIT.daysInSIT + 1; const sitEntryDate = moment(sitStatus.currentSIT.sitEntryDate, swaggerDateFormat); diff --git a/src/components/PPMSummaryList/PPMSummaryList.jsx b/src/components/PPMSummaryList/PPMSummaryList.jsx index 99a77bd3606..5f6a29c992b 100644 --- a/src/components/PPMSummaryList/PPMSummaryList.jsx +++ b/src/components/PPMSummaryList/PPMSummaryList.jsx @@ -8,6 +8,8 @@ import SectionWrapper from 'components/Customer/SectionWrapper'; import { ppmShipmentStatuses } from 'constants/shipments'; import { ShipmentShape } from 'types/shipment'; import { formatCustomerDate } from 'utils/formatters'; +import AsyncPacketDownloadLink from 'shared/AsyncPacketDownloadLink/AsyncPacketDownloadLink'; +import { downloadPPMPaymentPacket } from 'services/internalApi'; const submittedContent = ( <> @@ -70,7 +72,7 @@ const paymentReviewed = (approvedAt, submittedAt, reviewedAt) => { ); }; -const PPMSummaryStatus = (shipment, orderLabel, onButtonClick) => { +const PPMSummaryStatus = (shipment, orderLabel, onButtonClick, onDownloadError) => { const { ppmShipment: { status, approvedAt, submittedAt, reviewedAt }, } = shipment; @@ -88,11 +90,20 @@ const PPMSummaryStatus = (shipment, orderLabel, onButtonClick) => { content = approvedContent(approvedAt); break; case ppmShipmentStatuses.NEEDS_PAYMENT_APPROVAL: - actionButton = ; + actionButton = ; content = paymentSubmitted(approvedAt, submittedAt); break; case ppmShipmentStatuses.PAYMENT_APPROVED: - actionButton = ; + actionButton = ( + + ); + content = paymentReviewed(approvedAt, submittedAt, reviewedAt); break; default: @@ -109,7 +120,7 @@ const PPMSummaryStatus = (shipment, orderLabel, onButtonClick) => { ); }; -const PPMSummaryList = ({ shipments, onUploadClick }) => { +const PPMSummaryList = ({ shipments, onUploadClick, onDownloadError }) => { const { length } = shipments; return shipments.map((shipment, i) => { return ( @@ -119,20 +130,22 @@ const PPMSummaryList = ({ shipments, onUploadClick }) => { hasMany={length > 1} index={i} onUploadClick={() => onUploadClick(shipment.id)} + onDownloadError={onDownloadError} /> ); }); }; -const PPMSummaryListItem = ({ shipment, hasMany, index, onUploadClick }) => { +const PPMSummaryListItem = ({ shipment, hasMany, index, onUploadClick, onDownloadError }) => { const orderLabel = hasMany ? `PPM ${index + 1}` : 'PPM'; - return PPMSummaryStatus(shipment, orderLabel, onUploadClick); + return PPMSummaryStatus(shipment, orderLabel, onUploadClick, onDownloadError); }; PPMSummaryList.propTypes = { shipments: arrayOf(ShipmentShape).isRequired, onUploadClick: func.isRequired, + onDownloadError: func.isRequired, }; PPMSummaryListItem.propTypes = { @@ -140,6 +153,7 @@ PPMSummaryListItem.propTypes = { index: number.isRequired, hasMany: bool.isRequired, onUploadClick: func.isRequired, + onDownloadError: func.isRequired, }; export default PPMSummaryList; diff --git a/src/components/PPMSummaryList/PPMSummaryList.test.jsx b/src/components/PPMSummaryList/PPMSummaryList.test.jsx index 5add5d5882c..fbef1065e87 100644 --- a/src/components/PPMSummaryList/PPMSummaryList.test.jsx +++ b/src/components/PPMSummaryList/PPMSummaryList.test.jsx @@ -1,11 +1,22 @@ import React from 'react'; -import { render, screen } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import PPMSummaryList from './PPMSummaryList'; +import { MockProviders } from 'testUtils'; +import { downloadPPMPaymentPacket } from 'services/internalApi'; import { ppmShipmentStatuses, shipmentStatuses } from 'constants/shipments'; +jest.mock('services/internalApi', () => ({ + ...jest.requireActual('services/internalApi'), + downloadPPMPaymentPacket: jest.fn(), +})); + +afterEach(() => { + jest.resetAllMocks(); +}); + const shipments = [ { id: '1', @@ -88,7 +99,7 @@ describe('PPMSummaryList component', () => { describe('payment docs submitted for closeout review', () => { it('should display submitted date and disabled button with copy', () => { render(); - expect(screen.getByRole('button', { name: 'Download Incentive Packet' })).toBeDisabled(); + expect(screen.getByRole('button', { name: 'Download Payment Packet' })).toBeDisabled(); expect(screen.queryByText(`PPM approved: 15 Apr 2022`)).toBeInTheDocument(); expect(screen.queryByText(`PPM documentation submitted: 19 Apr 2022`)).toBeInTheDocument(); @@ -104,7 +115,7 @@ describe('PPMSummaryList component', () => { describe('payment docs reviewed', () => { it('should display reviewed date and enabled button with copy', () => { render(); - expect(screen.getByRole('button', { name: 'Download Incentive Packet' })).toBeEnabled(); + expect(screen.getByRole('button', { name: 'Download Payment Packet' })).toBeEnabled(); expect(screen.queryByText(`PPM approved: 15 Apr 2022`)).toBeInTheDocument(); expect(screen.queryByText(`PPM documentation submitted: 19 Apr 2022`)).toBeInTheDocument(); @@ -132,4 +143,59 @@ describe('PPMSummaryList component', () => { expect(screen.queryByText('PPM 2')).toBeInTheDocument(); }); }); + + it('PPM Download Payment Packet - success', async () => { + const mockResponse = { + ok: true, + headers: { + 'content-disposition': 'filename="test.pdf"', + }, + status: 200, + data: null, + }; + downloadPPMPaymentPacket.mockImplementation(() => Promise.resolve(mockResponse)); + + render( + + + , + ); + + expect(screen.getByText('Download Payment Packet', { exact: false })).toBeInTheDocument(); + + const downloadPaymentButton = screen.getByText('Download Payment Packet'); + expect(downloadPaymentButton).toBeInTheDocument(); + + await userEvent.click(downloadPaymentButton); + + await waitFor(() => { + expect(downloadPPMPaymentPacket).toHaveBeenCalledTimes(1); + }); + }); + + it('PPM Download Payment Packet - failure', async () => { + downloadPPMPaymentPacket.mockRejectedValue({ + response: { body: { title: 'Error title', detail: 'Error detail' } }, + }); + + const shipment = { ppmShipment: { status: ppmShipmentStatuses.PAYMENT_APPROVED } }; + const onErrorHandler = jest.fn(); + + render( + + + , + ); + + expect(screen.getByText('Download Payment Packet')).toBeInTheDocument(); + + const downloadPaymentButton = screen.getByText('Download Payment Packet'); + expect(downloadPaymentButton).toBeInTheDocument(); + await userEvent.click(downloadPaymentButton); + + await waitFor(() => { + expect(downloadPPMPaymentPacket).toHaveBeenCalledTimes(1); + expect(onErrorHandler).toHaveBeenCalledTimes(1); + }); + }); }); diff --git a/src/components/Statements/PrivacyAndPolicyStatement.jsx b/src/components/Statements/PrivacyAndPolicyStatement.jsx index f709ed247ac..35416fd23f0 100644 --- a/src/components/Statements/PrivacyAndPolicyStatement.jsx +++ b/src/components/Statements/PrivacyAndPolicyStatement.jsx @@ -37,13 +37,12 @@ function PrivacyPolicy() { personal property to include facilitating payment to Service-members. Certain information, limited to rolodex contact information, is disclosed to contracted Transportation Services Providers in order to effect the movement of household goods and personal property. Use of information in this system is restricted to MilMove - / DPS Prototype account holders and disclosure is prohibited without the written consent of the DPS Program - Management Office. + account holders and disclosure is prohibited without the written consent of the Defense Personal Property + Management Office (DPMO).

DISCLOSURE: Voluntary. However, failure to provide the requested information may result in the Service-member - being unable to use the DPS(MilMove) prototype system to effectuate their household goods or personal property - movement. + being unable to use the MilMove system to effectuate their household goods or personal property movement.

diff --git a/src/constants/routes.js b/src/constants/routes.js index f1c1335963d..f3f1017a5ee 100644 --- a/src/constants/routes.js +++ b/src/constants/routes.js @@ -80,7 +80,7 @@ export const servicesCounselingRoutes = { SHIPMENT_REVIEW_PATH: 'shipments/:shipmentId/document-review', BASE_REVIEW_SHIPMENT_WEIGHTS_PATH: `${BASE_COUNSELING_MOVE_PATH}/review-shipment-weights`, REVIEW_SHIPMENT_WEIGHTS_PATH: 'review-shipment-weights', - CUSTOMER_NAME_PATH: '/onboarding/customerName', + CREATE_CUSTOMER_PATH: '/onboarding/create-customer', }; const BASE_MOVES_PATH = '/moves/:moveCode'; diff --git a/src/containers/Headers/CustomerLoggedInHeader.jsx b/src/containers/Headers/CustomerLoggedInHeader.jsx index 680765cf107..a5824dfa8a8 100644 --- a/src/containers/Headers/CustomerLoggedInHeader.jsx +++ b/src/containers/Headers/CustomerLoggedInHeader.jsx @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; -import { useNavigate } from 'react-router-dom'; +import { useLocation, useNavigate } from 'react-router-dom'; import MilMoveHeader from 'components/MilMoveHeader/index'; import CustomerUserInfo from 'components/MilMoveHeader/CustomerUserInfo'; @@ -10,8 +10,16 @@ import { logOut as logOutAction } from 'store/auth/actions'; import { selectIsProfileComplete } from 'store/entities/selectors'; import { selectCurrentMoveId } from 'store/general/selectors'; -const CustomerLoggedInHeader = ({ isProfileComplete, logOut, moveId }) => { +const CustomerLoggedInHeader = ({ state, isProfileComplete, logOut, moveId }) => { const navigate = useNavigate(); + const { pathname } = useLocation(); + const moveID = pathname.split('/')[2]; + + let isSpecialMove = false; + if (Object.keys(state.entities.orders).length > 0) { + const currentOrderType = Object.values(state.entities.orders).filter((order) => order.moves[0] === moveID)[0]; + isSpecialMove = ['BLUEBARK'].includes(currentOrderType?.orders_type); + } const handleLogout = () => { logOut(); @@ -28,7 +36,7 @@ const CustomerLoggedInHeader = ({ isProfileComplete, logOut, moveId }) => { }; return ( - + ); @@ -44,6 +52,7 @@ CustomerLoggedInHeader.defaultProps = { }; const mapStateToProps = (state) => ({ + state, isProfileComplete: selectIsProfileComplete(state), // Grab moveId from state that was set from the most recent navigation to a move moveId: selectCurrentMoveId(state), diff --git a/src/pages/MyMove/Home/MoveHome.jsx b/src/pages/MyMove/Home/MoveHome.jsx index f9b33d2f88f..efb6f297b58 100644 --- a/src/pages/MyMove/Home/MoveHome.jsx +++ b/src/pages/MyMove/Home/MoveHome.jsx @@ -17,7 +17,7 @@ import { } from './HomeHelpers'; import AsyncPacketDownloadLink from 'shared/AsyncPacketDownloadLink/AsyncPacketDownloadLink'; -import DownloadAOAErrorModal from 'shared/DownloadAOAErrorModal/DownloadAOAErrorModal'; +import DownloadPacketErrorModal from 'shared/DownloadPacketErrorModal/DownloadPacketErrorModal'; import ConnectedDestructiveShipmentConfirmationModal from 'components/ConfirmationModals/DestructiveShipmentConfirmationModal'; import Contact from 'components/Customer/Home/Contact'; import DocsUploaded from 'components/Customer/Home/DocsUploaded'; @@ -54,7 +54,6 @@ import { formatCustomerDate, formatWeight } from 'utils/formatters'; import { isPPMAboutInfoComplete, isPPMShipmentComplete, isWeightTicketComplete } from 'utils/shipments'; import withRouter from 'utils/routing'; import { ADVANCE_STATUSES } from 'constants/ppms'; -import { CHECK_SPECIAL_ORDERS_TYPES, SPECIAL_ORDERS_TYPES } from 'constants/orders'; const Description = ({ className, children, dataTestId }) => (

@@ -83,7 +82,7 @@ const MoveHome = ({ serviceMemberMoves, isProfileComplete, serviceMember, signed const [targetShipmentId, setTargetShipmentId] = useState(null); const [showDeleteSuccessAlert, setShowDeleteSuccessAlert] = useState(false); const [showDeleteErrorAlert, setShowDeleteErrorAlert] = useState(false); - const [showDownloadPPMAOAPaperworkErrorAlert, setShowDownloadPPMAOAPaperworkErrorAlert] = useState(false); + const [showDownloadPPMPaperworkErrorAlert, setShowDownloadPPMPaperworkErrorAlert] = useState(false); // fetching all move data on load since this component is dependent on that data // this will run each time the component is loaded/accessed @@ -192,7 +191,7 @@ const MoveHome = ({ serviceMemberMoves, isProfileComplete, serviceMember, signed // checking to see if prime is counseling this move, return true const isPrimeCounseled = () => { - return !orders.provides_services_counseling; + return !orders.providesServicesCounseling; }; // logic that handles deleting a shipment @@ -376,8 +375,8 @@ const MoveHome = ({ serviceMemberMoves, isProfileComplete, serviceMember, signed ); }; - const toggleDownloadAOAErrorModal = () => { - setShowDownloadPPMAOAPaperworkErrorAlert(!showDownloadPPMAOAPaperworkErrorAlert); + const togglePPMPacketErrorModal = () => { + setShowDownloadPPMPaperworkErrorAlert(!showDownloadPPMPaperworkErrorAlert); }; // early return if loading user/service member @@ -411,8 +410,6 @@ const MoveHome = ({ serviceMemberMoves, isProfileComplete, serviceMember, signed // eslint-disable-next-line camelcase const currentLocation = current_location; const shipmentNumbersByType = {}; - - const isSpecialMove = CHECK_SPECIAL_ORDERS_TYPES(orders?.orders_type); return ( <> - +

- {isSpecialMove ? ( -
-

{SPECIAL_ORDERS_TYPES[`${orders.orders_type}`]}

-
- ) : null}

{serviceMember.first_name} {serviceMember.last_name} @@ -555,7 +547,7 @@ const MoveHome = ({ serviceMemberMoves, isProfileComplete, serviceMember, signed > {hasSubmittedMove() ? ( - Move submitted {formatCustomerDate(move.submittedAt)}.
+ Move submitted {formatCustomerDate(move.submittedAt) || 'Not submitted yet'}.
@@ -609,7 +601,7 @@ const MoveHome = ({ serviceMemberMoves, isProfileComplete, serviceMember, signed id={shipment?.ppmShipment?.id} label="Download AOA Paperwork (PDF)" asyncRetrieval={downloadPPMAOAPacket} - onFailure={toggleDownloadAOAErrorModal} + onFailure={togglePPMPacketErrorModal} />

)} @@ -662,7 +654,11 @@ const MoveHome = ({ serviceMemberMoves, isProfileComplete, serviceMember, signed completedHeaderText="Manage your PPM" step={hasAdvanceRequested() ? '6' : '5'} > - + )} diff --git a/src/pages/MyMove/Home/MoveHome.test.jsx b/src/pages/MyMove/Home/MoveHome.test.jsx index 26150190344..4fa61c930d4 100644 --- a/src/pages/MyMove/Home/MoveHome.test.jsx +++ b/src/pages/MyMove/Home/MoveHome.test.jsx @@ -1281,7 +1281,7 @@ describe('Home component', () => { await wrapper.find(buttonId).simulate('click'); await waitFor(() => { // scrape text from error modal - expect(wrapper.text()).toContain('Something went wrong downloading PPM AOA paperwork'); + expect(wrapper.text()).toContain('Something went wrong downloading PPM paperwork'); expect(downloadPPMAOAPacket).toHaveBeenCalledTimes(1); }); }); diff --git a/src/pages/MyMove/Home/index.jsx b/src/pages/MyMove/Home/index.jsx index 6653d715a9b..be4e34803a5 100644 --- a/src/pages/MyMove/Home/index.jsx +++ b/src/pages/MyMove/Home/index.jsx @@ -56,7 +56,7 @@ import { isPPMAboutInfoComplete, isPPMShipmentComplete, isWeightTicketComplete } import withRouter from 'utils/routing'; import { RouterShape } from 'types/router'; import { ADVANCE_STATUSES } from 'constants/ppms'; -import DownloadAOAErrorModal from 'shared/DownloadAOAErrorModal/DownloadAOAErrorModal'; +import DownloadPacketErrorModal from 'shared/DownloadPacketErrorModal/DownloadPacketErrorModal'; import { CHECK_SPECIAL_ORDERS_TYPES, SPECIAL_ORDERS_TYPES } from 'constants/orders'; const Description = ({ className, children, dataTestId }) => ( @@ -84,7 +84,7 @@ export class Home extends Component { targetShipmentId: null, showDeleteSuccessAlert: false, showDeleteErrorAlert: false, - showDownloadPPMAOAPaperworkErrorAlert: false, + showDownloadPPMPaperworkErrorAlert: false, }; } @@ -364,9 +364,9 @@ export class Home extends Component { navigate(path); }; - toggleDownloadAOAErrorModal = () => { + toggleDownloadPacketErrorModal = () => { this.setState((prevState) => ({ - showDownloadPPMAOAPaperworkErrorAlert: !prevState.showDownloadPPMAOAPaperworkErrorAlert, + showDownloadPPMPaperworkErrorAlert: !prevState.showDownloadPPMPaperworkErrorAlert, })); }; @@ -400,7 +400,7 @@ export class Home extends Component { targetShipmentId, showDeleteSuccessAlert, showDeleteErrorAlert, - showDownloadPPMAOAPaperworkErrorAlert, + showDownloadPPMPaperworkErrorAlert, } = this.state; // early return if loading user/service member @@ -448,9 +448,9 @@ export class Home extends Component { submitText="Yes, Delete" closeText="No, Keep It" /> -
@@ -637,7 +637,7 @@ export class Home extends Component { id={shipment?.ppmShipment?.id} label="Download AOA Paperwork (PDF)" asyncRetrieval={downloadPPMAOAPacket} - onFailure={this.toggleDownloadAOAErrorModal} + onFailure={this.toggleDownloadPacketErrorModal} />

)} @@ -690,7 +690,11 @@ export class Home extends Component { completedHeaderText="Manage your PPM" step={this.hasAdvanceRequested ? '6' : '5'} > - + )} diff --git a/src/pages/MyMove/Home/index.test.jsx b/src/pages/MyMove/Home/index.test.jsx index 798b4cf7a02..87ce31cd88e 100644 --- a/src/pages/MyMove/Home/index.test.jsx +++ b/src/pages/MyMove/Home/index.test.jsx @@ -652,7 +652,7 @@ describe('Home component', () => { await waitFor(() => { expect( - screen.getByText(/Something went wrong downloading PPM AOA paperwork./, { exact: false }), + screen.getByText(/Something went wrong downloading PPM paperwork./, { exact: false }), ).toBeInTheDocument(); expect(downloadPPMAOAPacket).toHaveBeenCalledTimes(1); }); diff --git a/src/pages/MyMove/PPM/Booking/DateAndLocation/DateAndLocation.jsx b/src/pages/MyMove/PPM/Booking/DateAndLocation/DateAndLocation.jsx index 5e8dcfa1750..f64f6986c3f 100644 --- a/src/pages/MyMove/PPM/Booking/DateAndLocation/DateAndLocation.jsx +++ b/src/pages/MyMove/PPM/Booking/DateAndLocation/DateAndLocation.jsx @@ -31,7 +31,8 @@ const DateAndLocation = ({ mtoShipment, serviceMember, destinationDutyLocation, const includeCloseoutOffice = serviceMember.affiliation === SERVICE_MEMBER_AGENCIES.ARMY || - serviceMember.affiliation === SERVICE_MEMBER_AGENCIES.AIR_FORCE; + serviceMember.affiliation === SERVICE_MEMBER_AGENCIES.AIR_FORCE || + serviceMember.affiliation === SERVICE_MEMBER_AGENCIES.SPACE_FORCE; const isNewShipment = !mtoShipment?.id; useEffect(() => { diff --git a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx new file mode 100644 index 00000000000..5a60c5229f7 --- /dev/null +++ b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx @@ -0,0 +1,410 @@ +import React, { useState } from 'react'; +import { GridContainer, Grid, Alert, Label, Radio, Fieldset } from '@trussworks/react-uswds'; +import { useNavigate } from 'react-router-dom'; +import { Field, Formik } from 'formik'; +import * as Yup from 'yup'; +import { connect } from 'react-redux'; + +import styles from './CreateCustomerForm.module.scss'; + +import { Form } from 'components/form/Form'; +import TextField from 'components/form/fields/TextField/TextField'; +import NotificationScrollToTop from 'components/NotificationScrollToTop'; +import { servicesCounselingRoutes } from 'constants/routes'; +import WizardNavigation from 'components/Customer/WizardNavigation/WizardNavigation'; +import SectionWrapper from 'components/Customer/SectionWrapper'; +import formStyles from 'styles/form.module.scss'; +import { CheckboxField, DropdownInput } from 'components/form/fields'; +import { dropdownInputOptions } from 'utils/formatters'; +import { SERVICE_MEMBER_AGENCY_LABELS } from 'content/serviceMemberAgencies'; +import MaskedTextField from 'components/form/fields/MaskedTextField/MaskedTextField'; +import { backupContactInfoSchema, requiredAddressSchema } from 'utils/validation'; +import { createCustomerWithOktaOption } from 'services/ghcApi'; +import { getResponseError } from 'services/internalApi'; +import { setFlashMessage as setFlashMessageAction } from 'store/flash/actions'; + +export const CreateCustomerForm = ({ setFlashMessage }) => { + const [serverError, setServerError] = useState(null); + const navigate = useNavigate(); + + const branchOptions = dropdownInputOptions(SERVICE_MEMBER_AGENCY_LABELS); + const statesList = [ + { value: 'AL', key: 'AL' }, + { value: 'AK', key: 'AK' }, + { value: 'AR', key: 'AR' }, + { value: 'AZ', key: 'AZ' }, + { value: 'CA', key: 'CA' }, + { value: 'CO', key: 'CO' }, + { value: 'CT', key: 'CT' }, + { value: 'DC', key: 'DC' }, + { value: 'DE', key: 'DE' }, + { value: 'FL', key: 'FL' }, + { value: 'GA', key: 'GA' }, + { value: 'HI', key: 'HI' }, + { value: 'IA', key: 'IA' }, + { value: 'ID', key: 'ID' }, + { value: 'IL', key: 'IL' }, + { value: 'IN', key: 'IN' }, + { value: 'KS', key: 'KS' }, + { value: 'KY', key: 'KY' }, + { value: 'LA', key: 'LA' }, + { value: 'MA', key: 'MA' }, + { value: 'MD', key: 'MD' }, + { value: 'ME', key: 'ME' }, + { value: 'MI', key: 'MI' }, + { value: 'MN', key: 'MN' }, + { value: 'MO', key: 'MO' }, + { value: 'MS', key: 'MS' }, + { value: 'MT', key: 'MT' }, + { value: 'NC', key: 'NC' }, + { value: 'ND', key: 'ND' }, + { value: 'NE', key: 'NE' }, + { value: 'NH', key: 'NH' }, + { value: 'NJ', key: 'NJ' }, + { value: 'NM', key: 'NM' }, + { value: 'NV', key: 'NV' }, + { value: 'NY', key: 'NY' }, + { value: 'OH', key: 'OH' }, + { value: 'OK', key: 'OK' }, + { value: 'OR', key: 'OR' }, + { value: 'PA', key: 'PA' }, + { value: 'RI', key: 'RI' }, + { value: 'SC', key: 'SC' }, + { value: 'SD', key: 'SD' }, + { value: 'TN', key: 'TN' }, + { value: 'TX', key: 'TX' }, + { value: 'UT', key: 'UT' }, + { value: 'VA', key: 'VA' }, + { value: 'VT', key: 'VT' }, + { value: 'WA', key: 'WA' }, + { value: 'WI', key: 'WI' }, + { value: 'WV', key: 'WV' }, + { value: 'WY', key: 'WY' }, + ]; + + const residentialAddressName = 'residential_address'; + const backupAddressName = 'backup_mailing_address'; + const backupContactName = 'backup_contact'; + + const initialValues = { + affiliation: '', + edipi: '', + first_name: '', + middle_name: '', + last_name: '', + suffix: '', + telephone: '', + secondary_telephone: null, + personal_email: '', + phone_is_preferred: false, + email_is_preferred: false, + [residentialAddressName]: { + streetAddress1: '', + streetAddress2: '', + streetAddress3: '', + city: '', + state: '', + postalCode: '', + }, + [backupAddressName]: { + streetAddress1: '', + streetAddress2: '', + streetAddress3: '', + city: '', + state: '', + postalCode: '', + }, + [backupContactName]: { + name: '', + telephone: '', + email: '', + }, + create_okta_account: '', + }; + + const handleBack = () => { + navigate(servicesCounselingRoutes.BASE_QUEUE_SEARCH_PATH); + }; + + const onSubmit = async (values) => { + // Convert strings to booleans to satisfy swagger + const createOktaAccount = values.create_okta_account === 'true'; + + const body = { + affiliation: values.affiliation, + edipi: values.edipi, + firstName: values.first_name, + middleName: values.middle_name, + lastName: values.last_name, + suffix: values.suffix, + telephone: values.telephone, + secondaryTelephone: values.secondary_telephone, + personalEmail: values.personal_email, + phoneIsPreferred: values.phone_is_preferred, + emailIsPreferred: values.email_is_preferred, + residentialAddress: values[residentialAddressName], + backupMailingAddress: values[backupAddressName], + backupContact: { + name: values[backupContactName].name, + email: values[backupContactName].email, + phone: values[backupContactName].telephone, + }, + createOktaAccount, + }; + + return createCustomerWithOktaOption({ body }) + .then(() => { + setFlashMessage('CUSTOMER_CREATE_SUCCESS', 'success', `Customer created successfully.`); + navigate(servicesCounselingRoutes.BASE_QUEUE_SEARCH_PATH); + }) + .catch((e) => { + const { response } = e; + const errorMessage = getResponseError(response, 'failed to create service member due to server error'); + setServerError(errorMessage); + }); + }; + + const validationSchema = Yup.object().shape({ + affiliation: Yup.mixed().oneOf(Object.keys(SERVICE_MEMBER_AGENCY_LABELS)).required('Required'), + edipi: Yup.string().matches(/[0-9]{10}/, 'Enter a 10-digit DOD ID number'), + first_name: Yup.string().required('Required'), + middle_name: Yup.string(), + last_name: Yup.string().required('Required'), + suffix: Yup.string(), + telephone: Yup.string() + .min(12, 'Please enter a valid phone number. Phone numbers must be entered as ###-###-####.') + .required('Required'), + secondary_telephone: Yup.string() + .min(12, 'Please enter a valid phone number. Phone numbers must be entered as ###-###-####.') + .nullable(), + personal_email: Yup.string() + .matches(/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$/, 'Must be a valid email address') + .required('Required'), + phoneIsPreferred: Yup.boolean(), + emailIsPreferred: Yup.boolean(), + [residentialAddressName]: requiredAddressSchema.required(), + [backupAddressName]: requiredAddressSchema.required(), + [backupContactName]: backupContactInfoSchema.required(), + create_okta_account: Yup.boolean().required('Required'), + }); + + return ( + + + + {serverError && ( + + + + {serverError} + + + + )} + + + + + {({ isValid, handleSubmit }) => { + return ( + +

Create Customer Profile

+ +

Customer Affiliation

+ + +
+ +

Customer Name

+ + + + +
+ +

Contact Info

+ + + + +
+ + +
+
+ +

Current Address

+ + + + + +
+
+ +
+
+ +
+
+
+ +

Backup Address

+ + + + + +
+
+ +
+
+ +
+
+
+ +

Backup Contact

+ + + +
+ +

Okta Account

+
+ Do you want to create an Okta account for this customer? +
+ + +
+
+
+
+ +
+ + ); + }} +
+
+
+
+ ); +}; + +const mapDispatchToProps = { + setFlashMessage: setFlashMessageAction, +}; + +export default connect(() => ({}), mapDispatchToProps)(CreateCustomerForm); diff --git a/src/pages/Office/CustomerOnboarding/CustomerName.module.scss b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.module.scss similarity index 76% rename from src/pages/Office/CustomerOnboarding/CustomerName.module.scss rename to src/pages/Office/CustomerOnboarding/CreateCustomerForm.module.scss index bff50b614eb..8121bcd6e51 100644 --- a/src/pages/Office/CustomerOnboarding/CustomerName.module.scss +++ b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.module.scss @@ -7,3 +7,7 @@ .nameForm { width: 100%; } + +.header { + text-align: center; +} diff --git a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx new file mode 100644 index 00000000000..19d553d6475 --- /dev/null +++ b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx @@ -0,0 +1,197 @@ +import React from 'react'; +import { render, fireEvent, waitFor, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import { CreateCustomerForm } from './CreateCustomerForm'; + +import { MockProviders } from 'testUtils'; +import { createCustomerWithOktaOption } from 'services/ghcApi'; + +const mockNavigate = jest.fn(); +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useNavigate: () => mockNavigate, +})); + +jest.mock('services/ghcApi', () => ({ + ...jest.requireActual('services/ghcApi'), + createCustomerWithOktaOption: jest.fn(), +})); + +jest.mock('store/flash/actions', () => ({ + ...jest.requireActual('store/flash/actions'), + setFlashMessage: jest.fn(), +})); + +beforeEach(jest.resetAllMocks); + +const fakePayload = { + affiliation: 'ARMY', + edipi: '1234567890', + first_name: 'Shish', + middle_name: 'Ka', + last_name: 'Bob', + suffix: 'Mr.', + telephone: '555-555-5555', + secondary_telephone: '999-867-5309', + personal_email: 'tastyAndDelicious@mail.mil', + phone_is_preferred: true, + email_is_preferred: '', + residential_address: { + streetAddress1: '8711 S Hungry Ave.', + streetAddress2: '', + streetAddress3: '', + city: 'Starving', + state: 'OK', + postalCode: '74133', + }, + backup_mailing_address: { + streetAddress1: '420 S. Munchies Lane', + streetAddress2: '', + streetAddress3: '', + city: 'Mustang', + state: 'KS', + postalCode: '73064', + }, + backup_contact: { + name: 'Silly String', + telephone: '666-666-6666', + email: 'allOverDaPlace@mail.com', + }, + create_okta_account: 'true', +}; + +const fakeResponse = { + affiliation: 'string', + firstName: 'John', + lastName: 'Doe', + telephone: '216-421-1392', + personalEmail: '73sGJ6jq7cS%6@PqElR.WUzkqFNvtduyyA', + suffix: 'Jr.', + middleName: 'David', + residentialAddress: { + id: 'c56a4180-65aa-42ec-a945-5fd21dec0538', + streetAddress1: '123 Main Ave', + streetAddress2: 'Apartment 9000', + streetAddress3: 'Montmârtre', + city: 'Anytown', + eTag: 'string', + state: 'AL', + postalCode: '90210', + country: 'USA', + }, + backupContact: { + name: 'string', + email: 'backupContact@mail.com', + phone: '381-100-5880', + }, + id: 'c56a4180-65aa-42ec-a945-5fd21dec0538', + edipi: 'string', + userID: 'c56a4180-65aa-42ec-a945-5fd21dec0538', + oktaID: 'string', + oktaEmail: 'string', + phoneIsPreferred: true, + emailIsPreferred: true, + secondaryTelephone: '499-793-2722', + backupAddress: { + id: 'c56a4180-65aa-42ec-a945-5fd21dec0538', + streetAddress1: '123 Main Ave', + streetAddress2: 'Apartment 9000', + streetAddress3: 'Montmârtre', + city: 'Anytown', + eTag: 'string', + state: 'AL', + postalCode: '90210', + country: 'USA', + }, +}; + +const testProps = { + setFlashMessage: jest.fn(), +}; + +describe('CreateCustomerForm', () => { + it('renders without crashing', async () => { + render( + + + , + ); + + // checking that all headers exist + expect(screen.getByText('Create Customer Profile')).toBeInTheDocument(); + expect(screen.getByText('Customer Affiliation')).toBeInTheDocument(); + expect(screen.getByText('Customer Name')).toBeInTheDocument(); + expect(screen.getByText('Contact Info')).toBeInTheDocument(); + expect(screen.getByText('Current Address')).toBeInTheDocument(); + expect(screen.getByText('Backup Address')).toBeInTheDocument(); + expect(screen.getByText('Backup Contact')).toBeInTheDocument(); + expect(screen.getByText('Okta Account')).toBeInTheDocument(); + + const saveBtn = await screen.findByRole('button', { name: 'Save' }); + expect(saveBtn).toBeInTheDocument(); + expect(saveBtn).toBeDisabled(); + const cancelBtn = await screen.findByRole('button', { name: 'Cancel' }); + expect(cancelBtn).toBeInTheDocument(); + }); + + it('navigates the user on cancel click', async () => { + const { getByText } = render( + + + , + ); + fireEvent.click(getByText('Cancel')); + await waitFor(() => { + expect(mockNavigate).toHaveBeenCalled(); + }); + }); + + it('submits the form and navigates the user once all required fields are filled out', async () => { + createCustomerWithOktaOption.mockImplementation(() => Promise.resolve(fakeResponse)); + + const { getByLabelText, getByTestId, getByRole } = render( + + + , + ); + + const user = userEvent.setup(); + + const saveBtn = await screen.findByRole('button', { name: 'Save' }); + expect(saveBtn).toBeInTheDocument(); + + await user.selectOptions(getByLabelText('Branch of service'), [fakePayload.affiliation]); + + await user.type(getByLabelText('First name'), fakePayload.first_name); + await user.type(getByLabelText('Last name'), fakePayload.last_name); + + await user.type(getByLabelText('Best contact phone'), fakePayload.telephone); + await user.type(getByLabelText('Personal email'), fakePayload.personal_email); + + await userEvent.type(getByTestId('res-add-street1'), fakePayload.residential_address.streetAddress1); + await userEvent.type(getByTestId('res-add-city'), fakePayload.residential_address.city); + await userEvent.selectOptions(getByTestId('res-add-state'), [fakePayload.residential_address.state]); + await userEvent.type(getByTestId('res-add-zip'), fakePayload.residential_address.postalCode); + + await userEvent.type(getByTestId('backup-add-street1'), fakePayload.backup_mailing_address.streetAddress1); + await userEvent.type(getByTestId('backup-add-city'), fakePayload.backup_mailing_address.city); + await userEvent.selectOptions(getByTestId('backup-add-state'), [fakePayload.backup_mailing_address.state]); + await userEvent.type(getByTestId('backup-add-zip'), fakePayload.backup_mailing_address.postalCode); + + await userEvent.type(getByLabelText('Name'), fakePayload.backup_contact.name); + await userEvent.type(getByRole('textbox', { name: 'Email' }), fakePayload.backup_contact.email); + await userEvent.type(getByRole('textbox', { name: 'Phone' }), fakePayload.backup_contact.telephone); + + const oktaRadioButton = getByLabelText('Yes'); + await userEvent.click(oktaRadioButton); + + await waitFor(() => { + expect(saveBtn).toBeEnabled(); + }); + await userEvent.click(saveBtn); + + expect(createCustomerWithOktaOption).toHaveBeenCalled(); + expect(mockNavigate).toHaveBeenCalled(); + }); +}); diff --git a/src/pages/Office/CustomerOnboarding/CreateCustomerform.stories.jsx b/src/pages/Office/CustomerOnboarding/CreateCustomerform.stories.jsx new file mode 100644 index 00000000000..672da45ecea --- /dev/null +++ b/src/pages/Office/CustomerOnboarding/CreateCustomerform.stories.jsx @@ -0,0 +1,18 @@ +import React from 'react'; + +import CreateCustomerForm from './CreateCustomerForm'; + +import { MockProviders } from 'testUtils'; + +export default { + title: 'Office Components/CreateCustomerForm', + parameters: { layout: 'fullscreen' }, +}; + +export const Form = () => { + return ( + + + + ); +}; diff --git a/src/pages/Office/CustomerOnboarding/CustomerName.jsx b/src/pages/Office/CustomerOnboarding/CustomerName.jsx deleted file mode 100644 index 83a5dc88809..00000000000 --- a/src/pages/Office/CustomerOnboarding/CustomerName.jsx +++ /dev/null @@ -1,81 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { useState } from 'react'; -import { GridContainer, Grid, Alert } from '@trussworks/react-uswds'; -import { useNavigate } from 'react-router-dom'; - -import styles from './CustomerName.module.scss'; - -import NotificationScrollToTop from 'components/NotificationScrollToTop'; -import NameForm from 'components/Customer/NameForm/NameForm'; -import { patchServiceMember, getResponseError } from 'services/internalApi'; -import { ServiceMemberShape } from 'types/customerShapes'; -import { servicesCounselingRoutes } from 'constants/routes'; - -export const CustomerName = ({ serviceMember, updateServiceMember }) => { - const [serverError, setServerError] = useState(null); - const navigate = useNavigate(); - const initialValues = { - first_name: serviceMember?.first_name || '', - middle_name: serviceMember?.middle_name || '', - last_name: serviceMember?.last_name || '', - suffix: serviceMember?.suffix || '', - }; - - const handleNext = () => { - // add next route - }; - - const handleBack = () => { - navigate(servicesCounselingRoutes.BASE_QUEUE_SEARCH_PATH); - }; - - const handleSubmit = (values) => { - const payload = { - id: serviceMember.id, - first_name: values.first_name, - middle_name: values.middle_name, - last_name: values.last_name, - suffix: values.suffix, - }; - - return patchServiceMember(payload) - .then(updateServiceMember) - .then(handleNext) - .catch((e) => { - // TODO - error handling - below is rudimentary error handling to approximate existing UX - // Error shape: https://github.com/swagger-api/swagger-js/blob/master/docs/usage/http-client.md#errors - const { response } = e; - const errorMessage = getResponseError(response, 'failed to update service member due to server error'); - setServerError(errorMessage); - }); - }; - - return ( - - - - {serverError && ( - - - - {serverError} - - - - )} - - - - - - - - ); -}; - -CustomerName.propTypes = { - updateServiceMember: PropTypes.func.isRequired, - serviceMember: ServiceMemberShape.isRequired, -}; - -export default CustomerName; diff --git a/src/pages/Office/CustomerOnboarding/CustomerName.test.jsx b/src/pages/Office/CustomerOnboarding/CustomerName.test.jsx deleted file mode 100644 index 5c8d22efc6c..00000000000 --- a/src/pages/Office/CustomerOnboarding/CustomerName.test.jsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; -import { render, screen } from '@testing-library/react'; - -import NameForm from 'components/Customer/NameForm/NameForm'; - -describe('Name page', () => { - it('renders the NameForm', async () => { - render(); - - expect(await screen.findByRole('heading', { name: 'Name', level: 1 })).toBeInTheDocument(); - }); -}); diff --git a/src/pages/Office/MoveTaskOrder/MoveTaskOrder.jsx b/src/pages/Office/MoveTaskOrder/MoveTaskOrder.jsx index 06df9b776c8..0c0c9c672a5 100644 --- a/src/pages/Office/MoveTaskOrder/MoveTaskOrder.jsx +++ b/src/pages/Office/MoveTaskOrder/MoveTaskOrder.jsx @@ -152,6 +152,13 @@ export const MoveTaskOrder = (props) => { rejectionReason: item.rejectionReason, sitDepartureDate: item.sitDepartureDate, sitEntryDate: item.sitEntryDate, + sitOriginHHGOriginalAddress: item.sitOriginHHGOriginalAddress, + sitOriginHHGActualAddress: item.sitOriginHHGActualAddress, + sitDestinationFinalAddress: item.sitDestinationFinalAddress, + sitDestinationOriginalAddress: item.sitDestinationOriginalAddress, + sitCustomerContacted: item.sitCustomerContacted, + sitRequestedDelivery: item.sitRequestedDelivery, + sitDeliveryMiles: item.sitDeliveryMiles, }; if (serviceItemsForShipment[`${newItem.mtoShipmentID}`]) { @@ -1089,6 +1096,8 @@ export const MoveTaskOrder = (props) => { handleShowRejectionDialog={handleShowRejectionDialog} handleShowEditSitEntryDateModal={handleShowEditSitEntryDateModal} statusForTableType={SERVICE_ITEM_STATUSES.SUBMITTED} + shipment={mtoShipment} + sitStatus={mtoShipment.sitStatus} /> )} {approvedServiceItems?.length > 0 && ( @@ -1098,6 +1107,8 @@ export const MoveTaskOrder = (props) => { handleShowRejectionDialog={handleShowRejectionDialog} handleShowEditSitEntryDateModal={handleShowEditSitEntryDateModal} statusForTableType={SERVICE_ITEM_STATUSES.APPROVED} + shipment={mtoShipment} + sitStatus={mtoShipment.sitStatus} /> )} {rejectedServiceItems?.length > 0 && ( @@ -1106,6 +1117,8 @@ export const MoveTaskOrder = (props) => { handleUpdateMTOServiceItemStatus={handleUpdateMTOServiceItemStatus} handleShowRejectionDialog={handleShowRejectionDialog} statusForTableType={SERVICE_ITEM_STATUSES.REJECTED} + shipment={mtoShipment} + sitStatus={mtoShipment.sitStatus} /> )} diff --git a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx index 685e718b63f..098e8e1419d 100644 --- a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx +++ b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx @@ -33,6 +33,7 @@ import MoveSearchForm from 'components/MoveSearchForm/MoveSearchForm'; import { roleTypes } from 'constants/userRoles'; import SearchResultsTable from 'components/Table/SearchResultsTable'; import TabNav from 'components/TabNav'; +import ConnectedFlashMessage from 'containers/FlashMessage/FlashMessage'; import { CHECK_SPECIAL_ORDERS_TYPES, SPECIAL_ORDERS_TYPES } from 'constants/orders'; const counselingColumns = () => [ @@ -214,7 +215,7 @@ const ServicesCounselingQueue = () => { }; const handleAddCustomerClick = () => { - navigate(generatePath(servicesCounselingRoutes.CUSTOMER_NAME_PATH)); + navigate(generatePath(servicesCounselingRoutes.CREATE_CUSTOMER_PATH)); }; const [search, setSearch] = useState({ moveCode: null, dodID: null, customerName: null }); @@ -295,7 +296,15 @@ const ServicesCounselingQueue = () => { return (
{renderNavBar()} -

Search for a move

+ +
+

Search for a move

+ {searchHappened && counselorMoveCreateFeatureFlag && ( + + )} +
{searchHappened && ( { roleType={roleTypes.SERVICES_COUNSELOR} /> )} - {searchHappened && counselorMoveCreateFeatureFlag && ( - - )}
); } diff --git a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.module.scss b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.module.scss index 34e625a2f86..d5f700630bf 100644 --- a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.module.scss +++ b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.module.scss @@ -23,3 +23,15 @@ display: block; color: red; } + +.searchFormContainer { + display: flex; + align-items: center; + gap: 10px; +} + +.addCustomerBtn { + height: 43px; + width: 150px; + background-color: $link; +} diff --git a/src/pages/Office/index.jsx b/src/pages/Office/index.jsx index 751f917fbad..b0a4003473c 100644 --- a/src/pages/Office/index.jsx +++ b/src/pages/Office/index.jsx @@ -41,7 +41,6 @@ import PrimeBanner from 'pages/PrimeUI/PrimeBanner/PrimeBanner'; import PermissionProvider from 'components/Restricted/PermissionProvider'; import withRouter from 'utils/routing'; import { OktaLoggedOutBanner, OktaNeedsLoggedOutBanner } from 'components/OktaLogoutBanner'; -import CustomerName from 'pages/Office/CustomerOnboarding/CustomerName'; // Lazy load these dependencies (they correspond to unique routes & only need to be loaded when that URL is accessed) const SignIn = lazy(() => import('pages/SignIn/SignIn')); @@ -89,6 +88,7 @@ const PrimeUIShipmentUpdateDestinationAddress = lazy(() => ); const QAECSRMoveSearch = lazy(() => import('pages/Office/QAECSRMoveSearch/QAECSRMoveSearch')); +const CreateCustomerForm = lazy(() => import('pages/Office/CustomerOnboarding/CreateCustomerForm')); export class OfficeApp extends Component { constructor(props) { @@ -275,10 +275,10 @@ export class OfficeApp extends Component { /> )} - + } /> diff --git a/src/services/ghcApi.js b/src/services/ghcApi.js index 7a3b7c3790b..93d6d2d44a8 100644 --- a/src/services/ghcApi.js +++ b/src/services/ghcApi.js @@ -345,6 +345,11 @@ export async function acknowledgeExcessWeightRisk({ orderID, ifMatchETag }) { return makeGHCRequest(operationPath, { orderID, 'If-Match': ifMatchETag }); } +export async function createCustomerWithOktaOption({ body }) { + const operationPath = 'customer.createCustomerWithOktaOption'; + return makeGHCRequest(operationPath, { body }); +} + export async function updateCustomerInfo({ customerId, ifMatchETag, body }) { const operationPath = 'customer.updateCustomer'; return makeGHCRequest(operationPath, { customerID: customerId, 'If-Match': ifMatchETag, body }); @@ -671,3 +676,7 @@ export const reviewShipmentAddressUpdate = async ({ shipmentID, ifMatchETag, bod export async function downloadPPMAOAPacket(ppmShipmentId) { return makeGHCRequestRaw('ppm.showAOAPacket', { ppmShipmentId }); } + +export async function downloadPPMPaymentPacket(ppmShipmentId) { + return makeGHCRequestRaw('ppm.showPaymentPacket', { ppmShipmentId }); +} diff --git a/src/services/internalApi.js b/src/services/internalApi.js index 5cfa4ebeaba..c8b666670be 100644 --- a/src/services/internalApi.js +++ b/src/services/internalApi.js @@ -441,40 +441,6 @@ export async function deleteProGearWeightTicket(ppmShipmentId, proGearWeightTick ); } -/** PPMS */ -export async function calculatePPMEstimate(moveDate, originZip, originDutyLocationZip, ordersId, weightEstimate) { - return makeInternalRequest( - 'ppm.showPPMEstimate', - { - original_move_date: moveDate, - origin_zip: originZip, - origin_duty_location_zip: originDutyLocationZip, - orders_id: ordersId, - weight_estimate: weightEstimate, - }, - { - normalize: false, - }, - ); -} - -export async function calculatePPMSITEstimate(ppmId, moveDate, sitDays, originZip, ordersId, weightEstimate) { - return makeInternalRequest( - 'ppm.showPPMSitEstimate', - { - personally_procured_move_id: ppmId, - original_move_date: moveDate, - days_in_storage: sitDays, - origin_zip: originZip, - orders_id: ordersId, - weight_estimate: weightEstimate, - }, - { - normalize: false, - }, - ); -} - export async function createMovingExpense(ppmShipmentId) { return makeInternalRequest( 'ppm.createMovingExpense', @@ -535,3 +501,7 @@ export async function searchTransportationOffices(search) { export async function downloadPPMAOAPacket(ppmShipmentId) { return makeInternalRequestRaw('ppm.showAOAPacket', { ppmShipmentId }); } + +export async function downloadPPMPaymentPacket(ppmShipmentId) { + return makeInternalRequestRaw('ppm.showPaymentPacket', { ppmShipmentId }); +} diff --git a/src/shared/AsyncPacketDownloadLink/AsyncPacketDownloadLink.jsx b/src/shared/AsyncPacketDownloadLink/AsyncPacketDownloadLink.jsx index 61d9e6f34c2..0aef0283c4f 100644 --- a/src/shared/AsyncPacketDownloadLink/AsyncPacketDownloadLink.jsx +++ b/src/shared/AsyncPacketDownloadLink/AsyncPacketDownloadLink.jsx @@ -43,12 +43,13 @@ export const onPacketDownloadSuccessHandler = (response) => { * @param {func} onSucccess on success response handler * @param {func} onFailure on failure response handler */ -const AsyncPacketDownloadLink = ({ id, label, asyncRetrieval, onSucccess, onFailure }) => { +const AsyncPacketDownloadLink = ({ id, label, asyncRetrieval, onSucccess, onFailure, className }) => { const dataTestId = `asyncPacketDownloadLink${id}`; + return (