diff --git a/.envrc b/.envrc index d0fb8d2781d..cf126f87261 100644 --- a/.envrc +++ b/.envrc @@ -144,6 +144,7 @@ export DB_SSL_MODE=disable # Multi Move feature flag export FEATURE_FLAG_MULTI_MOVE=true export FEATURE_FLAG_COUNSELOR_MOVE_CREATE=true +export FEATURE_FLAG_CUSTOMER_REGISTRATION=false export FEATURE_FLAG_MOVE_LOCK=false export FEATURE_FLAG_OKTA_DODID_INPUT=false diff --git a/config/env/demo.app-client-tls.env b/config/env/demo.app-client-tls.env index 5989ccfd533..d7cfefc7794 100644 --- a/config/env/demo.app-client-tls.env +++ b/config/env/demo.app-client-tls.env @@ -52,3 +52,4 @@ FEATURE_FLAG_DODID_UNIQUE=false FEATURE_FLAG_ENABLE_ALASKA=false FEATURE_FLAG_ENABLE_HAWAII=false FEATURE_FLAG_BULK_ASSIGNMENT=false +FEATURE_FLAG_CUSTOMER_REGISTRATION=false diff --git a/config/env/demo.app.env b/config/env/demo.app.env index 2621c72fb7a..22445b891f6 100644 --- a/config/env/demo.app.env +++ b/config/env/demo.app.env @@ -57,3 +57,4 @@ FEATURE_FLAG_DODID_UNIQUE=false FEATURE_FLAG_ENABLE_ALASKA=false FEATURE_FLAG_ENABLE_HAWAII=false FEATURE_FLAG_BULK_ASSIGNMENT=false +FEATURE_FLAG_CUSTOMER_REGISTRATION=false diff --git a/config/env/exp.app-client-tls.env b/config/env/exp.app-client-tls.env index 706d0d7d7eb..6d7367ae654 100644 --- a/config/env/exp.app-client-tls.env +++ b/config/env/exp.app-client-tls.env @@ -51,4 +51,5 @@ FEATURE_FLAG_QUEUE_MANAGEMENT=false FEATURE_FLAG_DODID_UNIQUE=false FEATURE_FLAG_ENABLE_ALASKA=false FEATURE_FLAG_ENABLE_HAWAII=false -FEATURE_FLAG_BULK_ASSIGNMENT=false \ No newline at end of file +FEATURE_FLAG_BULK_ASSIGNMENT=false +FEATURE_FLAG_CUSTOMER_REGISTRATION=false \ No newline at end of file diff --git a/config/env/exp.app.env b/config/env/exp.app.env index c425328db35..21718e30fe3 100644 --- a/config/env/exp.app.env +++ b/config/env/exp.app.env @@ -56,4 +56,5 @@ FEATURE_FLAG_QUEUE_MANAGEMENT=false FEATURE_FLAG_DODID_UNIQUE=false FEATURE_FLAG_ENABLE_ALASKA=false FEATURE_FLAG_ENABLE_HAWAII=false -FEATURE_FLAG_BULK_ASSIGNMENT=false \ No newline at end of file +FEATURE_FLAG_BULK_ASSIGNMENT=false +FEATURE_FLAG_CUSTOMER_REGISTRATION=false \ No newline at end of file diff --git a/config/env/loadtest.app-client-tls.env b/config/env/loadtest.app-client-tls.env index 7f6b5d92528..bdde5c7d1b6 100644 --- a/config/env/loadtest.app-client-tls.env +++ b/config/env/loadtest.app-client-tls.env @@ -49,4 +49,5 @@ FEATURE_FLAG_QUEUE_MANAGEMENT=false FEATURE_FLAG_DODID_UNIQUE=false FEATURE_FLAG_ENABLE_ALASKA=false FEATURE_FLAG_ENABLE_HAWAII=false -FEATURE_FLAG_BULK_ASSIGNMENT=false \ No newline at end of file +FEATURE_FLAG_BULK_ASSIGNMENT=false +FEATURE_FLAG_CUSTOMER_REGISTRATION=false \ No newline at end of file diff --git a/config/env/loadtest.app.env b/config/env/loadtest.app.env index cc97c07c5e8..22d584ea4c6 100644 --- a/config/env/loadtest.app.env +++ b/config/env/loadtest.app.env @@ -54,4 +54,5 @@ FEATURE_FLAG_QUEUE_MANAGEMENT=false FEATURE_FLAG_DODID_UNIQUE=false FEATURE_FLAG_ENABLE_ALASKA=false FEATURE_FLAG_ENABLE_HAWAII=false -FEATURE_FLAG_BULK_ASSIGNMENT=false \ No newline at end of file +FEATURE_FLAG_BULK_ASSIGNMENT=false +FEATURE_FLAG_CUSTOMER_REGISTRATION=false \ No newline at end of file diff --git a/config/env/prd.app-client-tls.env b/config/env/prd.app-client-tls.env index c9d4a6228ed..1a44df90cfd 100644 --- a/config/env/prd.app-client-tls.env +++ b/config/env/prd.app-client-tls.env @@ -48,4 +48,5 @@ FEATURE_FLAG_QUEUE_MANAGEMENT=false FEATURE_FLAG_DODID_UNIQUE=false FEATURE_FLAG_ENABLE_ALASKA=false FEATURE_FLAG_ENABLE_HAWAII=false -FEATURE_FLAG_BULK_ASSIGNMENT=false \ No newline at end of file +FEATURE_FLAG_BULK_ASSIGNMENT=false +FEATURE_FLAG_CUSTOMER_REGISTRATION=false \ No newline at end of file diff --git a/config/env/prd.app.env b/config/env/prd.app.env index 8d2fbc7c7a2..22b755e062f 100644 --- a/config/env/prd.app.env +++ b/config/env/prd.app.env @@ -55,4 +55,5 @@ FEATURE_FLAG_QUEUE_MANAGEMENT=false FEATURE_FLAG_DODID_UNIQUE=false FEATURE_FLAG_ENABLE_ALASKA=false FEATURE_FLAG_ENABLE_HAWAII=false -FEATURE_FLAG_BULK_ASSIGNMENT=false \ No newline at end of file +FEATURE_FLAG_BULK_ASSIGNMENT=false +FEATURE_FLAG_CUSTOMER_REGISTRATION=false \ No newline at end of file diff --git a/config/env/stg.app-client-tls.env b/config/env/stg.app-client-tls.env index 067e1e9bdd2..024a4de2638 100644 --- a/config/env/stg.app-client-tls.env +++ b/config/env/stg.app-client-tls.env @@ -50,4 +50,5 @@ FEATURE_FLAG_QUEUE_MANAGEMENT=false FEATURE_FLAG_DODID_UNIQUE=false FEATURE_FLAG_ENABLE_ALASKA=false FEATURE_FLAG_ENABLE_HAWAII=false -FEATURE_FLAG_BULK_ASSIGNMENT=false \ No newline at end of file +FEATURE_FLAG_BULK_ASSIGNMENT=false +FEATURE_FLAG_CUSTOMER_REGISTRATION=false \ No newline at end of file diff --git a/config/flipt/storage/development.features.yaml b/config/flipt/storage/development.features.yaml index cde89f09329..55bf7a44350 100644 --- a/config/flipt/storage/development.features.yaml +++ b/config/flipt/storage/development.features.yaml @@ -227,6 +227,14 @@ flags: - segment: key: mil-app value: false + - key: customer_registration + name: Customer registration feature flag + type: BOOLEAN_FLAG_TYPE + enabled: false + rollouts: + - segment: + key: mil-app + value: false segments: - key: mil-app name: Mil App diff --git a/pkg/assets/paperwork/formtemplates/ShipmentSummaryWorksheet.pdf b/pkg/assets/paperwork/formtemplates/ShipmentSummaryWorksheet.pdf index 83a149a47d2..44f5774a753 100644 Binary files a/pkg/assets/paperwork/formtemplates/ShipmentSummaryWorksheet.pdf and b/pkg/assets/paperwork/formtemplates/ShipmentSummaryWorksheet.pdf differ diff --git a/pkg/gen/ghcapi/embedded_spec.go b/pkg/gen/ghcapi/embedded_spec.go index 60d6eb7460f..b1979ece4ae 100644 --- a/pkg/gen/ghcapi/embedded_spec.go +++ b/pkg/gen/ghcapi/embedded_spec.go @@ -8230,6 +8230,9 @@ func init() { } ] }, + "ppmType": { + "$ref": "#/definitions/PPMType" + }, "proGearWeight": { "type": "integer", "x-nullable": true @@ -12647,8 +12650,7 @@ func init() { "INCENTIVE_BASED", "ACTUAL_EXPENSE", "SMALL_PACKAGE" - ], - "readOnly": true + ] }, "PWSViolation": { "description": "A PWS violation for an evaluation report", @@ -15163,6 +15165,9 @@ func init() { } ] }, + "ppmType": { + "$ref": "#/definitions/PPMType" + }, "proGearWeight": { "type": "integer", "x-nullable": true @@ -25883,6 +25888,9 @@ func init() { } ] }, + "ppmType": { + "$ref": "#/definitions/PPMType" + }, "proGearWeight": { "type": "integer", "x-nullable": true @@ -30374,8 +30382,7 @@ func init() { "INCENTIVE_BASED", "ACTUAL_EXPENSE", "SMALL_PACKAGE" - ], - "readOnly": true + ] }, "PWSViolation": { "description": "A PWS violation for an evaluation report", @@ -32949,6 +32956,9 @@ func init() { } ] }, + "ppmType": { + "$ref": "#/definitions/PPMType" + }, "proGearWeight": { "type": "integer", "x-nullable": true diff --git a/pkg/gen/ghcmessages/create_p_p_m_shipment.go b/pkg/gen/ghcmessages/create_p_p_m_shipment.go index 87746177f68..82a671eb655 100644 --- a/pkg/gen/ghcmessages/create_p_p_m_shipment.go +++ b/pkg/gen/ghcmessages/create_p_p_m_shipment.go @@ -63,6 +63,9 @@ type CreatePPMShipment struct { Address } `json:"pickupAddress"` + // ppm type + PpmType PPMType `json:"ppmType,omitempty"` + // pro gear weight ProGearWeight *int64 `json:"proGearWeight,omitempty"` @@ -133,6 +136,10 @@ func (m *CreatePPMShipment) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validatePpmType(formats); err != nil { + res = append(res, err) + } + if err := m.validateSecondaryDestinationAddress(formats); err != nil { res = append(res, err) } @@ -212,6 +219,23 @@ func (m *CreatePPMShipment) validatePickupAddress(formats strfmt.Registry) error return nil } +func (m *CreatePPMShipment) validatePpmType(formats strfmt.Registry) error { + if swag.IsZero(m.PpmType) { // not required + return nil + } + + if err := m.PpmType.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("ppmType") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("ppmType") + } + return err + } + + return nil +} + func (m *CreatePPMShipment) validateSecondaryDestinationAddress(formats strfmt.Registry) error { if swag.IsZero(m.SecondaryDestinationAddress) { // not required return nil @@ -308,6 +332,10 @@ func (m *CreatePPMShipment) ContextValidate(ctx context.Context, formats strfmt. res = append(res, err) } + if err := m.contextValidatePpmType(ctx, formats); err != nil { + res = append(res, err) + } + if err := m.contextValidateSecondaryDestinationAddress(ctx, formats); err != nil { res = append(res, err) } @@ -344,6 +372,24 @@ func (m *CreatePPMShipment) contextValidatePickupAddress(ctx context.Context, fo return nil } +func (m *CreatePPMShipment) contextValidatePpmType(ctx context.Context, formats strfmt.Registry) error { + + if swag.IsZero(m.PpmType) { // not required + return nil + } + + if err := m.PpmType.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("ppmType") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("ppmType") + } + return err + } + + return nil +} + func (m *CreatePPMShipment) contextValidateSecondaryDestinationAddress(ctx context.Context, formats strfmt.Registry) error { return nil diff --git a/pkg/gen/ghcmessages/p_p_m_type.go b/pkg/gen/ghcmessages/p_p_m_type.go index eb16d8cdaf2..0d6f94369c9 100644 --- a/pkg/gen/ghcmessages/p_p_m_type.go +++ b/pkg/gen/ghcmessages/p_p_m_type.go @@ -77,16 +77,7 @@ func (m PPMType) Validate(formats strfmt.Registry) error { return nil } -// ContextValidate validate this p p m type based on the context it is used +// ContextValidate validates this p p m type based on context it is used func (m PPMType) ContextValidate(ctx context.Context, formats strfmt.Registry) error { - var res []error - - if err := validate.ReadOnly(ctx, "", "body", PPMType(m)); err != nil { - return err - } - - if len(res) > 0 { - return errors.CompositeValidationError(res...) - } return nil } diff --git a/pkg/gen/ghcmessages/update_p_p_m_shipment.go b/pkg/gen/ghcmessages/update_p_p_m_shipment.go index 1684f99a4c9..1113dbbcfea 100644 --- a/pkg/gen/ghcmessages/update_p_p_m_shipment.go +++ b/pkg/gen/ghcmessages/update_p_p_m_shipment.go @@ -102,6 +102,9 @@ type UpdatePPMShipment struct { Address } `json:"pickupAddress,omitempty"` + // ppm type + PpmType PPMType `json:"ppmType,omitempty"` + // pro gear weight ProGearWeight *int64 `json:"proGearWeight,omitempty"` @@ -186,6 +189,10 @@ func (m *UpdatePPMShipment) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validatePpmType(formats); err != nil { + res = append(res, err) + } + if err := m.validateSecondaryDestinationAddress(formats); err != nil { res = append(res, err) } @@ -319,6 +326,23 @@ func (m *UpdatePPMShipment) validatePickupAddress(formats strfmt.Registry) error return nil } +func (m *UpdatePPMShipment) validatePpmType(formats strfmt.Registry) error { + if swag.IsZero(m.PpmType) { // not required + return nil + } + + if err := m.PpmType.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("ppmType") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("ppmType") + } + return err + } + + return nil +} + func (m *UpdatePPMShipment) validateSecondaryDestinationAddress(formats strfmt.Registry) error { if swag.IsZero(m.SecondaryDestinationAddress) { // not required return nil @@ -429,6 +453,10 @@ func (m *UpdatePPMShipment) ContextValidate(ctx context.Context, formats strfmt. res = append(res, err) } + if err := m.contextValidatePpmType(ctx, formats); err != nil { + res = append(res, err) + } + if err := m.contextValidateSecondaryDestinationAddress(ctx, formats); err != nil { res = append(res, err) } @@ -490,6 +518,24 @@ func (m *UpdatePPMShipment) contextValidatePickupAddress(ctx context.Context, fo return nil } +func (m *UpdatePPMShipment) contextValidatePpmType(ctx context.Context, formats strfmt.Registry) error { + + if swag.IsZero(m.PpmType) { // not required + return nil + } + + if err := m.PpmType.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("ppmType") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("ppmType") + } + return err + } + + return nil +} + func (m *UpdatePPMShipment) contextValidateSecondaryDestinationAddress(ctx context.Context, formats strfmt.Registry) error { return nil diff --git a/pkg/gen/internalapi/configure_mymove.go b/pkg/gen/internalapi/configure_mymove.go index 3b277e0037c..06ecfa8af5c 100644 --- a/pkg/gen/internalapi/configure_mymove.go +++ b/pkg/gen/internalapi/configure_mymove.go @@ -85,6 +85,11 @@ func configureAPI(api *internaloperations.MymoveAPI) http.Handler { return middleware.NotImplemented("operation feature_flags.BooleanFeatureFlagForUser has not yet been implemented") }) } + if api.FeatureFlagsBooleanFeatureFlagUnauthenticatedHandler == nil { + api.FeatureFlagsBooleanFeatureFlagUnauthenticatedHandler = feature_flags.BooleanFeatureFlagUnauthenticatedHandlerFunc(func(params feature_flags.BooleanFeatureFlagUnauthenticatedParams) middleware.Responder { + return middleware.NotImplemented("operation feature_flags.BooleanFeatureFlagUnauthenticated has not yet been implemented") + }) + } if api.OfficeCancelMoveHandler == nil { api.OfficeCancelMoveHandler = office.CancelMoveHandlerFunc(func(params office.CancelMoveParams) middleware.Responder { return middleware.NotImplemented("operation office.CancelMove has not yet been implemented") diff --git a/pkg/gen/internalapi/embedded_spec.go b/pkg/gen/internalapi/embedded_spec.go index ed60226b151..40610e84331 100644 --- a/pkg/gen/internalapi/embedded_spec.go +++ b/pkg/gen/internalapi/embedded_spec.go @@ -1666,6 +1666,57 @@ func init() { } } }, + "/open/feature-flags/boolean/{key}": { + "post": { + "description": "Determines if a feature flag is enabled.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "featureFlags" + ], + "summary": "Determines if a feature flag is enabled. Only used for unauthenticated users.", + "operationId": "booleanFeatureFlagUnauthenticated", + "parameters": [ + { + "type": "string", + "description": "Feature Flag Key", + "name": "key", + "in": "path", + "required": true + }, + { + "description": "context for the feature flag request", + "name": "flagContext", + "in": "body", + "required": true, + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + ], + "responses": { + "200": { + "description": "Boolean Feature Flag Status", + "schema": { + "$ref": "#/definitions/FeatureFlagBoolean" + } + }, + "401": { + "description": "request requires user authentication" + }, + "500": { + "description": "internal server error" + } + } + } + }, "/orders": { "post": { "description": "Creates an instance of orders tied to a service member", @@ -3920,6 +3971,9 @@ func init() { "pickupAddress": { "$ref": "#/definitions/Address" }, + "ppmType": { + "$ref": "#/definitions/PPMType" + }, "secondaryDestinationAddress": { "$ref": "#/definitions/Address" }, @@ -6705,8 +6759,7 @@ func init() { "INCENTIVE_BASED", "ACTUAL_EXPENSE", "SMALL_PACKAGE" - ], - "readOnly": true + ] }, "PatchMovePayload": { "type": "object", @@ -7861,6 +7914,9 @@ func init() { "pickupAddress": { "$ref": "#/definitions/Address" }, + "ppmType": { + "$ref": "#/definitions/PPMType" + }, "proGearWeight": { "type": "integer", "x-nullable": true @@ -10471,6 +10527,57 @@ func init() { } } }, + "/open/feature-flags/boolean/{key}": { + "post": { + "description": "Determines if a feature flag is enabled.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "featureFlags" + ], + "summary": "Determines if a feature flag is enabled. Only used for unauthenticated users.", + "operationId": "booleanFeatureFlagUnauthenticated", + "parameters": [ + { + "type": "string", + "description": "Feature Flag Key", + "name": "key", + "in": "path", + "required": true + }, + { + "description": "context for the feature flag request", + "name": "flagContext", + "in": "body", + "required": true, + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + ], + "responses": { + "200": { + "description": "Boolean Feature Flag Status", + "schema": { + "$ref": "#/definitions/FeatureFlagBoolean" + } + }, + "401": { + "description": "request requires user authentication" + }, + "500": { + "description": "internal server error" + } + } + } + }, "/orders": { "post": { "description": "Creates an instance of orders tied to a service member", @@ -13081,6 +13188,9 @@ func init() { "pickupAddress": { "$ref": "#/definitions/Address" }, + "ppmType": { + "$ref": "#/definitions/PPMType" + }, "secondaryDestinationAddress": { "$ref": "#/definitions/Address" }, @@ -15871,8 +15981,7 @@ func init() { "INCENTIVE_BASED", "ACTUAL_EXPENSE", "SMALL_PACKAGE" - ], - "readOnly": true + ] }, "PatchMovePayload": { "type": "object", @@ -17029,6 +17138,9 @@ func init() { "pickupAddress": { "$ref": "#/definitions/Address" }, + "ppmType": { + "$ref": "#/definitions/PPMType" + }, "proGearWeight": { "type": "integer", "x-nullable": true diff --git a/pkg/gen/internalapi/internaloperations/feature_flags/boolean_feature_flag_unauthenticated.go b/pkg/gen/internalapi/internaloperations/feature_flags/boolean_feature_flag_unauthenticated.go new file mode 100644 index 00000000000..e36dc8f224c --- /dev/null +++ b/pkg/gen/internalapi/internaloperations/feature_flags/boolean_feature_flag_unauthenticated.go @@ -0,0 +1,58 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package feature_flags + +// 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" +) + +// BooleanFeatureFlagUnauthenticatedHandlerFunc turns a function with the right signature into a boolean feature flag unauthenticated handler +type BooleanFeatureFlagUnauthenticatedHandlerFunc func(BooleanFeatureFlagUnauthenticatedParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn BooleanFeatureFlagUnauthenticatedHandlerFunc) Handle(params BooleanFeatureFlagUnauthenticatedParams) middleware.Responder { + return fn(params) +} + +// BooleanFeatureFlagUnauthenticatedHandler interface for that can handle valid boolean feature flag unauthenticated params +type BooleanFeatureFlagUnauthenticatedHandler interface { + Handle(BooleanFeatureFlagUnauthenticatedParams) middleware.Responder +} + +// NewBooleanFeatureFlagUnauthenticated creates a new http.Handler for the boolean feature flag unauthenticated operation +func NewBooleanFeatureFlagUnauthenticated(ctx *middleware.Context, handler BooleanFeatureFlagUnauthenticatedHandler) *BooleanFeatureFlagUnauthenticated { + return &BooleanFeatureFlagUnauthenticated{Context: ctx, Handler: handler} +} + +/* + BooleanFeatureFlagUnauthenticated swagger:route POST /open/feature-flags/boolean/{key} featureFlags booleanFeatureFlagUnauthenticated + +Determines if a feature flag is enabled. Only used for unauthenticated users. + +Determines if a feature flag is enabled. +*/ +type BooleanFeatureFlagUnauthenticated struct { + Context *middleware.Context + Handler BooleanFeatureFlagUnauthenticatedHandler +} + +func (o *BooleanFeatureFlagUnauthenticated) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewBooleanFeatureFlagUnauthenticatedParams() + 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/feature_flags/boolean_feature_flag_unauthenticated_parameters.go b/pkg/gen/internalapi/internaloperations/feature_flags/boolean_feature_flag_unauthenticated_parameters.go new file mode 100644 index 00000000000..10e7184e8e5 --- /dev/null +++ b/pkg/gen/internalapi/internaloperations/feature_flags/boolean_feature_flag_unauthenticated_parameters.go @@ -0,0 +1,95 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package feature_flags + +// 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/strfmt" +) + +// NewBooleanFeatureFlagUnauthenticatedParams creates a new BooleanFeatureFlagUnauthenticatedParams object +// +// There are no default values defined in the spec. +func NewBooleanFeatureFlagUnauthenticatedParams() BooleanFeatureFlagUnauthenticatedParams { + + return BooleanFeatureFlagUnauthenticatedParams{} +} + +// BooleanFeatureFlagUnauthenticatedParams contains all the bound params for the boolean feature flag unauthenticated operation +// typically these are obtained from a http.Request +// +// swagger:parameters booleanFeatureFlagUnauthenticated +type BooleanFeatureFlagUnauthenticatedParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /*context for the feature flag request + Required: true + In: body + */ + FlagContext map[string]string + /*Feature Flag Key + Required: true + In: path + */ + Key string +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewBooleanFeatureFlagUnauthenticatedParams() beforehand. +func (o *BooleanFeatureFlagUnauthenticatedParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + if runtime.HasBody(r) { + defer r.Body.Close() + var body map[string]string + if err := route.Consumer.Consume(r.Body, &body); err != nil { + if err == io.EOF { + res = append(res, errors.Required("flagContext", "body", "")) + } else { + res = append(res, errors.NewParseError("flagContext", "body", "", err)) + } + } else { + // no validation required on inline body + o.FlagContext = body + } + } else { + res = append(res, errors.Required("flagContext", "body", "")) + } + + rKey, rhkKey, _ := route.Params.GetOK("key") + if err := o.bindKey(rKey, rhkKey, route.Formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindKey binds and validates parameter Key from path. +func (o *BooleanFeatureFlagUnauthenticatedParams) bindKey(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: true + // Parameter is provided by construction from the route + o.Key = raw + + return nil +} diff --git a/pkg/gen/internalapi/internaloperations/feature_flags/boolean_feature_flag_unauthenticated_responses.go b/pkg/gen/internalapi/internaloperations/feature_flags/boolean_feature_flag_unauthenticated_responses.go new file mode 100644 index 00000000000..f69a6ae0702 --- /dev/null +++ b/pkg/gen/internalapi/internaloperations/feature_flags/boolean_feature_flag_unauthenticated_responses.go @@ -0,0 +1,109 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package feature_flags + +// 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" +) + +// BooleanFeatureFlagUnauthenticatedOKCode is the HTTP code returned for type BooleanFeatureFlagUnauthenticatedOK +const BooleanFeatureFlagUnauthenticatedOKCode int = 200 + +/* +BooleanFeatureFlagUnauthenticatedOK Boolean Feature Flag Status + +swagger:response booleanFeatureFlagUnauthenticatedOK +*/ +type BooleanFeatureFlagUnauthenticatedOK struct { + + /* + In: Body + */ + Payload *internalmessages.FeatureFlagBoolean `json:"body,omitempty"` +} + +// NewBooleanFeatureFlagUnauthenticatedOK creates BooleanFeatureFlagUnauthenticatedOK with default headers values +func NewBooleanFeatureFlagUnauthenticatedOK() *BooleanFeatureFlagUnauthenticatedOK { + + return &BooleanFeatureFlagUnauthenticatedOK{} +} + +// WithPayload adds the payload to the boolean feature flag unauthenticated o k response +func (o *BooleanFeatureFlagUnauthenticatedOK) WithPayload(payload *internalmessages.FeatureFlagBoolean) *BooleanFeatureFlagUnauthenticatedOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the boolean feature flag unauthenticated o k response +func (o *BooleanFeatureFlagUnauthenticatedOK) SetPayload(payload *internalmessages.FeatureFlagBoolean) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *BooleanFeatureFlagUnauthenticatedOK) 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 + } + } +} + +// BooleanFeatureFlagUnauthenticatedUnauthorizedCode is the HTTP code returned for type BooleanFeatureFlagUnauthenticatedUnauthorized +const BooleanFeatureFlagUnauthenticatedUnauthorizedCode int = 401 + +/* +BooleanFeatureFlagUnauthenticatedUnauthorized request requires user authentication + +swagger:response booleanFeatureFlagUnauthenticatedUnauthorized +*/ +type BooleanFeatureFlagUnauthenticatedUnauthorized struct { +} + +// NewBooleanFeatureFlagUnauthenticatedUnauthorized creates BooleanFeatureFlagUnauthenticatedUnauthorized with default headers values +func NewBooleanFeatureFlagUnauthenticatedUnauthorized() *BooleanFeatureFlagUnauthenticatedUnauthorized { + + return &BooleanFeatureFlagUnauthenticatedUnauthorized{} +} + +// WriteResponse to the client +func (o *BooleanFeatureFlagUnauthenticatedUnauthorized) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses + + rw.WriteHeader(401) +} + +// BooleanFeatureFlagUnauthenticatedInternalServerErrorCode is the HTTP code returned for type BooleanFeatureFlagUnauthenticatedInternalServerError +const BooleanFeatureFlagUnauthenticatedInternalServerErrorCode int = 500 + +/* +BooleanFeatureFlagUnauthenticatedInternalServerError internal server error + +swagger:response booleanFeatureFlagUnauthenticatedInternalServerError +*/ +type BooleanFeatureFlagUnauthenticatedInternalServerError struct { +} + +// NewBooleanFeatureFlagUnauthenticatedInternalServerError creates BooleanFeatureFlagUnauthenticatedInternalServerError with default headers values +func NewBooleanFeatureFlagUnauthenticatedInternalServerError() *BooleanFeatureFlagUnauthenticatedInternalServerError { + + return &BooleanFeatureFlagUnauthenticatedInternalServerError{} +} + +// WriteResponse to the client +func (o *BooleanFeatureFlagUnauthenticatedInternalServerError) 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/feature_flags/boolean_feature_flag_unauthenticated_urlbuilder.go b/pkg/gen/internalapi/internaloperations/feature_flags/boolean_feature_flag_unauthenticated_urlbuilder.go new file mode 100644 index 00000000000..544b0762f69 --- /dev/null +++ b/pkg/gen/internalapi/internaloperations/feature_flags/boolean_feature_flag_unauthenticated_urlbuilder.go @@ -0,0 +1,99 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package feature_flags + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" + "strings" +) + +// BooleanFeatureFlagUnauthenticatedURL generates an URL for the boolean feature flag unauthenticated operation +type BooleanFeatureFlagUnauthenticatedURL struct { + Key string + + _basePath string + // avoid unkeyed usage + _ struct{} +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *BooleanFeatureFlagUnauthenticatedURL) WithBasePath(bp string) *BooleanFeatureFlagUnauthenticatedURL { + 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 *BooleanFeatureFlagUnauthenticatedURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *BooleanFeatureFlagUnauthenticatedURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/open/feature-flags/boolean/{key}" + + key := o.Key + if key != "" { + _path = strings.Replace(_path, "{key}", key, -1) + } else { + return nil, errors.New("key is required on BooleanFeatureFlagUnauthenticatedURL") + } + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/internal" + } + _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 *BooleanFeatureFlagUnauthenticatedURL) 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 *BooleanFeatureFlagUnauthenticatedURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *BooleanFeatureFlagUnauthenticatedURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on BooleanFeatureFlagUnauthenticatedURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on BooleanFeatureFlagUnauthenticatedURL") + } + + 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 *BooleanFeatureFlagUnauthenticatedURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/pkg/gen/internalapi/internaloperations/mymove_api.go b/pkg/gen/internalapi/internaloperations/mymove_api.go index b1ba4e1ac47..fb2ed827df3 100644 --- a/pkg/gen/internalapi/internaloperations/mymove_api.go +++ b/pkg/gen/internalapi/internaloperations/mymove_api.go @@ -76,6 +76,9 @@ func NewMymoveAPI(spec *loads.Document) *MymoveAPI { FeatureFlagsBooleanFeatureFlagForUserHandler: feature_flags.BooleanFeatureFlagForUserHandlerFunc(func(params feature_flags.BooleanFeatureFlagForUserParams) middleware.Responder { return middleware.NotImplemented("operation feature_flags.BooleanFeatureFlagForUser has not yet been implemented") }), + FeatureFlagsBooleanFeatureFlagUnauthenticatedHandler: feature_flags.BooleanFeatureFlagUnauthenticatedHandlerFunc(func(params feature_flags.BooleanFeatureFlagUnauthenticatedParams) middleware.Responder { + return middleware.NotImplemented("operation feature_flags.BooleanFeatureFlagUnauthenticated has not yet been implemented") + }), OfficeCancelMoveHandler: office.CancelMoveHandlerFunc(func(params office.CancelMoveParams) middleware.Responder { return middleware.NotImplemented("operation office.CancelMove has not yet been implemented") }), @@ -330,6 +333,8 @@ type MymoveAPI struct { OfficeApproveReimbursementHandler office.ApproveReimbursementHandler // FeatureFlagsBooleanFeatureFlagForUserHandler sets the operation handler for the boolean feature flag for user operation FeatureFlagsBooleanFeatureFlagForUserHandler feature_flags.BooleanFeatureFlagForUserHandler + // FeatureFlagsBooleanFeatureFlagUnauthenticatedHandler sets the operation handler for the boolean feature flag unauthenticated operation + FeatureFlagsBooleanFeatureFlagUnauthenticatedHandler feature_flags.BooleanFeatureFlagUnauthenticatedHandler // OfficeCancelMoveHandler sets the operation handler for the cancel move operation OfficeCancelMoveHandler office.CancelMoveHandler // DocumentsCreateDocumentHandler sets the operation handler for the create document operation @@ -556,6 +561,9 @@ func (o *MymoveAPI) Validate() error { if o.FeatureFlagsBooleanFeatureFlagForUserHandler == nil { unregistered = append(unregistered, "feature_flags.BooleanFeatureFlagForUserHandler") } + if o.FeatureFlagsBooleanFeatureFlagUnauthenticatedHandler == nil { + unregistered = append(unregistered, "feature_flags.BooleanFeatureFlagUnauthenticatedHandler") + } if o.OfficeCancelMoveHandler == nil { unregistered = append(unregistered, "office.CancelMoveHandler") } @@ -864,6 +872,10 @@ func (o *MymoveAPI) initHandlerCache() { if o.handlers["POST"] == nil { o.handlers["POST"] = make(map[string]http.Handler) } + o.handlers["POST"]["/open/feature-flags/boolean/{key}"] = feature_flags.NewBooleanFeatureFlagUnauthenticated(o.context, o.FeatureFlagsBooleanFeatureFlagUnauthenticatedHandler) + if o.handlers["POST"] == nil { + o.handlers["POST"] = make(map[string]http.Handler) + } o.handlers["POST"]["/moves/{moveId}/cancel"] = office.NewCancelMove(o.context, o.OfficeCancelMoveHandler) if o.handlers["POST"] == nil { o.handlers["POST"] = make(map[string]http.Handler) diff --git a/pkg/gen/internalmessages/create_p_p_m_shipment.go b/pkg/gen/internalmessages/create_p_p_m_shipment.go index 98c9983390d..a8b0993bd45 100644 --- a/pkg/gen/internalmessages/create_p_p_m_shipment.go +++ b/pkg/gen/internalmessages/create_p_p_m_shipment.go @@ -43,6 +43,9 @@ type CreatePPMShipment struct { // Required: true PickupAddress *Address `json:"pickupAddress"` + // ppm type + PpmType PPMType `json:"ppmType,omitempty"` + // secondary destination address SecondaryDestinationAddress *Address `json:"secondaryDestinationAddress,omitempty"` @@ -76,6 +79,10 @@ func (m *CreatePPMShipment) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validatePpmType(formats); err != nil { + res = append(res, err) + } + if err := m.validateSecondaryDestinationAddress(formats); err != nil { res = append(res, err) } @@ -155,6 +162,23 @@ func (m *CreatePPMShipment) validatePickupAddress(formats strfmt.Registry) error return nil } +func (m *CreatePPMShipment) validatePpmType(formats strfmt.Registry) error { + if swag.IsZero(m.PpmType) { // not required + return nil + } + + if err := m.PpmType.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("ppmType") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("ppmType") + } + return err + } + + return nil +} + func (m *CreatePPMShipment) validateSecondaryDestinationAddress(formats strfmt.Registry) error { if swag.IsZero(m.SecondaryDestinationAddress) { // not required return nil @@ -252,6 +276,10 @@ func (m *CreatePPMShipment) ContextValidate(ctx context.Context, formats strfmt. res = append(res, err) } + if err := m.contextValidatePpmType(ctx, formats); err != nil { + res = append(res, err) + } + if err := m.contextValidateSecondaryDestinationAddress(ctx, formats); err != nil { res = append(res, err) } @@ -308,6 +336,24 @@ func (m *CreatePPMShipment) contextValidatePickupAddress(ctx context.Context, fo return nil } +func (m *CreatePPMShipment) contextValidatePpmType(ctx context.Context, formats strfmt.Registry) error { + + if swag.IsZero(m.PpmType) { // not required + return nil + } + + if err := m.PpmType.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("ppmType") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("ppmType") + } + return err + } + + return nil +} + func (m *CreatePPMShipment) contextValidateSecondaryDestinationAddress(ctx context.Context, formats strfmt.Registry) error { if m.SecondaryDestinationAddress != nil { diff --git a/pkg/gen/internalmessages/p_p_m_type.go b/pkg/gen/internalmessages/p_p_m_type.go index 4ba102b4a09..769a94b1c1b 100644 --- a/pkg/gen/internalmessages/p_p_m_type.go +++ b/pkg/gen/internalmessages/p_p_m_type.go @@ -77,16 +77,7 @@ func (m PPMType) Validate(formats strfmt.Registry) error { return nil } -// ContextValidate validate this p p m type based on the context it is used +// ContextValidate validates this p p m type based on context it is used func (m PPMType) ContextValidate(ctx context.Context, formats strfmt.Registry) error { - var res []error - - if err := validate.ReadOnly(ctx, "", "body", PPMType(m)); err != nil { - return err - } - - if len(res) > 0 { - return errors.CompositeValidationError(res...) - } return nil } diff --git a/pkg/gen/internalmessages/update_p_p_m_shipment.go b/pkg/gen/internalmessages/update_p_p_m_shipment.go index 2e1be3bd9cb..5b9402d8d2d 100644 --- a/pkg/gen/internalmessages/update_p_p_m_shipment.go +++ b/pkg/gen/internalmessages/update_p_p_m_shipment.go @@ -95,6 +95,9 @@ type UpdatePPMShipment struct { // pickup address PickupAddress *Address `json:"pickupAddress,omitempty"` + // ppm type + PpmType PPMType `json:"ppmType,omitempty"` + // pro gear weight ProGearWeight *int64 `json:"proGearWeight,omitempty"` @@ -148,6 +151,10 @@ func (m *UpdatePPMShipment) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validatePpmType(formats); err != nil { + res = append(res, err) + } + if err := m.validateSecondaryDestinationAddress(formats); err != nil { res = append(res, err) } @@ -260,6 +267,23 @@ func (m *UpdatePPMShipment) validatePickupAddress(formats strfmt.Registry) error return nil } +func (m *UpdatePPMShipment) validatePpmType(formats strfmt.Registry) error { + if swag.IsZero(m.PpmType) { // not required + return nil + } + + if err := m.PpmType.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("ppmType") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("ppmType") + } + return err + } + + return nil +} + func (m *UpdatePPMShipment) validateSecondaryDestinationAddress(formats strfmt.Registry) error { if swag.IsZero(m.SecondaryDestinationAddress) { // not required return nil @@ -371,6 +395,10 @@ func (m *UpdatePPMShipment) ContextValidate(ctx context.Context, formats strfmt. res = append(res, err) } + if err := m.contextValidatePpmType(ctx, formats); err != nil { + res = append(res, err) + } + if err := m.contextValidateSecondaryDestinationAddress(ctx, formats); err != nil { res = append(res, err) } @@ -448,6 +476,24 @@ func (m *UpdatePPMShipment) contextValidatePickupAddress(ctx context.Context, fo return nil } +func (m *UpdatePPMShipment) contextValidatePpmType(ctx context.Context, formats strfmt.Registry) error { + + if swag.IsZero(m.PpmType) { // not required + return nil + } + + if err := m.PpmType.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("ppmType") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("ppmType") + } + return err + } + + return nil +} + func (m *UpdatePPMShipment) contextValidateSecondaryDestinationAddress(ctx context.Context, formats strfmt.Registry) error { if m.SecondaryDestinationAddress != nil { diff --git a/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go b/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go index a874911a3b0..cf5bd394b46 100644 --- a/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go +++ b/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go @@ -987,6 +987,7 @@ func PPMShipment(_ storage.FileStorer, ppmShipment *models.PPMShipment) *ghcmess payloadPPMShipment := &ghcmessages.PPMShipment{ ID: *handlers.FmtUUID(ppmShipment.ID), + PpmType: ghcmessages.PPMType(ppmShipment.PPMType), ShipmentID: *handlers.FmtUUID(ppmShipment.ShipmentID), CreatedAt: strfmt.DateTime(ppmShipment.CreatedAt), UpdatedAt: strfmt.DateTime(ppmShipment.UpdatedAt), diff --git a/pkg/handlers/ghcapi/internal/payloads/payload_to_model.go b/pkg/handlers/ghcapi/internal/payloads/payload_to_model.go index bb05138dec6..4ce9d4c08db 100644 --- a/pkg/handlers/ghcapi/internal/payloads/payload_to_model.go +++ b/pkg/handlers/ghcapi/internal/payloads/payload_to_model.go @@ -320,6 +320,7 @@ func PPMShipmentModelFromCreate(ppmShipment *ghcmessages.CreatePPMShipment) *mod } model := &models.PPMShipment{ + PPMType: models.PPMType(ppmShipment.PpmType), Status: models.PPMShipmentStatusSubmitted, SITExpected: ppmShipment.SitExpected, EstimatedWeight: handlers.PoundPtrFromInt64Ptr(ppmShipment.EstimatedWeight), @@ -626,6 +627,7 @@ func PPMShipmentModelFromUpdate(ppmShipment *ghcmessages.UpdatePPMShipment) *mod return nil } model := &models.PPMShipment{ + PPMType: models.PPMType(ppmShipment.PpmType), ActualMoveDate: (*time.Time)(ppmShipment.ActualMoveDate), SITExpected: ppmShipment.SitExpected, EstimatedWeight: handlers.PoundPtrFromInt64Ptr(ppmShipment.EstimatedWeight), diff --git a/pkg/handlers/ghcapi/internal/payloads/payload_to_model_test.go b/pkg/handlers/ghcapi/internal/payloads/payload_to_model_test.go index d37aac4fb30..91ab4d66ea6 100644 --- a/pkg/handlers/ghcapi/internal/payloads/payload_to_model_test.go +++ b/pkg/handlers/ghcapi/internal/payloads/payload_to_model_test.go @@ -227,6 +227,7 @@ func (suite *PayloadsSuite) TestPPMShipmentModelWithOptionalDestinationStreet1Fr } ppmShipment := ghcmessages.CreatePPMShipment{ + PpmType: ghcmessages.PPMType(models.PPMTypeIncentiveBased), ExpectedDepartureDate: expectedDepartureDate, PickupAddress: struct{ ghcmessages.Address }{pickupAddress}, DestinationAddress: struct { @@ -239,6 +240,7 @@ func (suite *PayloadsSuite) TestPPMShipmentModelWithOptionalDestinationStreet1Fr suite.NotNil(model) suite.Equal(models.PPMShipmentStatusSubmitted, model.Status) suite.Equal(model.DestinationAddress.StreetAddress1, models.STREET_ADDRESS_1_NOT_PROVIDED) + suite.Equal(model.PPMType, models.PPMTypeIncentiveBased) suite.NotNil(model) // test when street address 1 contains white spaces diff --git a/pkg/handlers/ghcapi/mto_shipment_test.go b/pkg/handlers/ghcapi/mto_shipment_test.go index 353f4c655e0..035667e35c8 100644 --- a/pkg/handlers/ghcapi/mto_shipment_test.go +++ b/pkg/handlers/ghcapi/mto_shipment_test.go @@ -4091,6 +4091,7 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandlerUsingPPM() { MoveTaskOrderID: handlers.FmtUUID(move.ID), ShipmentType: &shipmentType, PpmShipment: &ghcmessages.CreatePPMShipment{ + PpmType: ghcmessages.PPMType(models.PPMTypeIncentiveBased), ExpectedDepartureDate: handlers.FmtDatePtr(expectedDepartureDate), PickupAddress: struct{ ghcmessages.Address }{pickupAddress}, SecondaryPickupAddress: struct{ ghcmessages.Address }{secondaryPickupAddress}, @@ -4148,6 +4149,7 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandlerUsingPPM() { if suite.NotNil(ppmPayload) { suite.NotZero(ppmPayload.ID) suite.NotEqual(uuid.Nil.String(), ppmPayload.ID.String()) + suite.NotNil(ppmPayload.PpmType) suite.EqualDatePtr(expectedDepartureDate, ppmPayload.ExpectedDepartureDate) suite.Equal(expectedPickupAddress.PostalCode, *ppmPayload.PickupAddress.PostalCode) suite.Equal(&expectedSecondaryPickupAddress.PostalCode, ppmPayload.SecondaryPickupAddress.PostalCode) diff --git a/pkg/handlers/internalapi/api.go b/pkg/handlers/internalapi/api.go index 192663228be..e64625b0e32 100644 --- a/pkg/handlers/internalapi/api.go +++ b/pkg/handlers/internalapi/api.go @@ -110,6 +110,7 @@ func NewInternalAPI(handlerConfig handlers.HandlerConfig) *internalops.MymoveAPI if err != nil { log.Fatalln(err) } + internalAPI.FeatureFlagsBooleanFeatureFlagUnauthenticatedHandler = BooleanFeatureFlagsUnauthenticatedHandler{handlerConfig} internalAPI.FeatureFlagsBooleanFeatureFlagForUserHandler = BooleanFeatureFlagsForUserHandler{handlerConfig} internalAPI.FeatureFlagsVariantFeatureFlagForUserHandler = VariantFeatureFlagsForUserHandler{handlerConfig} diff --git a/pkg/handlers/internalapi/feature_flag.go b/pkg/handlers/internalapi/feature_flag.go index 4f4d3d5d268..4cdd20716ea 100644 --- a/pkg/handlers/internalapi/feature_flag.go +++ b/pkg/handlers/internalapi/feature_flag.go @@ -4,11 +4,41 @@ import ( "github.com/go-openapi/runtime/middleware" "github.com/transcom/mymove/pkg/appcontext" + "github.com/transcom/mymove/pkg/apperror" ffop "github.com/transcom/mymove/pkg/gen/internalapi/internaloperations/feature_flags" "github.com/transcom/mymove/pkg/gen/internalmessages" "github.com/transcom/mymove/pkg/handlers" ) +// BooleanFeatureFlagsUnauthenticatedHandler handles evaluating boolean feature flags outside of authentication +type BooleanFeatureFlagsUnauthenticatedHandler struct { + handlers.HandlerConfig +} + +// Handle returns the boolean feature flag for an unauthenticated user +func (h BooleanFeatureFlagsUnauthenticatedHandler) Handle(params ffop.BooleanFeatureFlagUnauthenticatedParams) middleware.Responder { + return h.AuditableAppContextFromRequestWithErrors(params.HTTPRequest, + func(appCtx appcontext.AppContext) (middleware.Responder, error) { + // we are only allowing this to be called from the customer app + // since this is an open route outside of auth, we want to buckle down on validation here + if !appCtx.Session().IsMilApp() { + return ffop.NewBooleanFeatureFlagUnauthenticatedUnauthorized(), apperror.NewSessionError("Request is not from the customer app") + } + flag, err := h.FeatureFlagFetcher().GetBooleanFlag( + params.HTTPRequest.Context(), appCtx.Logger(), "customer", params.Key, params.FlagContext) + if err != nil { + return handlers.ResponseForError(appCtx.Logger(), err), err + } + flagPayload := internalmessages.FeatureFlagBoolean{ + Entity: &flag.Entity, + Key: ¶ms.Key, + Match: &flag.Match, + Namespace: &flag.Namespace, + } + return ffop.NewBooleanFeatureFlagUnauthenticatedOK().WithPayload(&flagPayload), nil + }) +} + // BooleanFeatureFlagsForUserHandler handles evaluating boolean feature flags for // users type BooleanFeatureFlagsForUserHandler struct { diff --git a/pkg/handlers/internalapi/feature_flag_test.go b/pkg/handlers/internalapi/feature_flag_test.go index aab6ecaeda6..438215244fd 100644 --- a/pkg/handlers/internalapi/feature_flag_test.go +++ b/pkg/handlers/internalapi/feature_flag_test.go @@ -5,11 +5,69 @@ import ( "github.com/go-openapi/strfmt" + "github.com/transcom/mymove/pkg/auth" "github.com/transcom/mymove/pkg/factory" ffop "github.com/transcom/mymove/pkg/gen/internalapi/internaloperations/feature_flags" "github.com/transcom/mymove/pkg/services" ) +func (suite *HandlerSuite) TestBooleanFeatureFlagUnauthenticatedHandler() { + suite.Run("success for unauthenticated user in the customer app", func() { + req := httptest.NewRequest("POST", "/open/feature-flags/boolean/test_ff", nil) + session := &auth.Session{ + ApplicationName: auth.MilApp, + } + ctx := auth.SetSessionInRequestContext(req, session) + + params := ffop.BooleanFeatureFlagUnauthenticatedParams{ + HTTPRequest: req.WithContext(ctx), + Key: "key", + FlagContext: map[string]string{ + "thing": "one", + }, + } + + handler := BooleanFeatureFlagsUnauthenticatedHandler{suite.HandlerConfig()} + + response := handler.Handle(params) + + okResponse, ok := response.(*ffop.BooleanFeatureFlagUnauthenticatedOK) + suite.True(ok) + suite.NoError(okResponse.Payload.Validate(strfmt.Default)) + expected := services.FeatureFlag{ + Entity: "user@example.com", + Key: params.Key, + Match: true, + Namespace: "test", + } + suite.Equal(expected.Entity, *okResponse.Payload.Entity) + suite.Equal(expected.Key, *okResponse.Payload.Key) + suite.Equal(expected.Match, *okResponse.Payload.Match) + suite.Equal(expected.Namespace, *okResponse.Payload.Namespace) + }) + suite.Run("error for unauthenticated user outside the customer app", func() { + req := httptest.NewRequest("POST", "/open/feature-flags/boolean/test_ff", nil) + session := &auth.Session{ + ApplicationName: auth.OfficeApp, + } + ctx := auth.SetSessionInRequestContext(req, session) + + params := ffop.BooleanFeatureFlagUnauthenticatedParams{ + HTTPRequest: req.WithContext(ctx), + Key: "key", + FlagContext: map[string]string{ + "thing": "one", + }, + } + + handler := BooleanFeatureFlagsUnauthenticatedHandler{suite.HandlerConfig()} + response := handler.Handle(params) + res, ok := response.(*ffop.BooleanFeatureFlagUnauthenticatedUnauthorized) + suite.True(ok) + suite.IsType(&ffop.BooleanFeatureFlagUnauthenticatedUnauthorized{}, res) + }) +} + func (suite *HandlerSuite) TestBooleanFeatureFlagForUserHandler() { user := factory.BuildDefaultUser(suite.DB()) diff --git a/pkg/handlers/internalapi/internal/payloads/model_to_payload.go b/pkg/handlers/internalapi/internal/payloads/model_to_payload.go index 26b25349e02..01db27b3a99 100644 --- a/pkg/handlers/internalapi/internal/payloads/model_to_payload.go +++ b/pkg/handlers/internalapi/internal/payloads/model_to_payload.go @@ -113,6 +113,7 @@ func PPMShipment(storer storage.FileStorer, ppmShipment *models.PPMShipment) *in payloadPPMShipment := &internalmessages.PPMShipment{ ID: *handlers.FmtUUID(ppmShipment.ID), + PpmType: internalmessages.PPMType(ppmShipment.PPMType), ShipmentID: *handlers.FmtUUID(ppmShipment.ShipmentID), CreatedAt: strfmt.DateTime(ppmShipment.CreatedAt), UpdatedAt: strfmt.DateTime(ppmShipment.UpdatedAt), diff --git a/pkg/handlers/internalapi/internal/payloads/model_to_payload_test.go b/pkg/handlers/internalapi/internal/payloads/model_to_payload_test.go index 5737d25db0d..834d3b7819f 100644 --- a/pkg/handlers/internalapi/internal/payloads/model_to_payload_test.go +++ b/pkg/handlers/internalapi/internal/payloads/model_to_payload_test.go @@ -42,6 +42,7 @@ func (suite *PayloadsSuite) TestFetchPPMShipment() { expectedPPMShipment := models.PPMShipment{ ID: ppmShipmentID, + PPMType: models.PPMTypeActualExpense, PickupAddress: &expectedAddress, DestinationAddress: &expectedAddress, IsActualExpenseReimbursement: &isActualExpenseReimbursement, @@ -69,6 +70,7 @@ func (suite *PayloadsSuite) TestFetchPPMShipment() { suite.Equal(&country.Country, returnedPPMShipment.DestinationAddress.Country) suite.Equal(&county, returnedPPMShipment.DestinationAddress.County) + suite.Equal(internalmessages.PPMType(models.PPMTypeActualExpense), returnedPPMShipment.PpmType) suite.True(*returnedPPMShipment.IsActualExpenseReimbursement) }) } diff --git a/pkg/handlers/internalapi/internal/payloads/payload_to_model.go b/pkg/handlers/internalapi/internal/payloads/payload_to_model.go index 37cd06f5811..3b2186793a5 100644 --- a/pkg/handlers/internalapi/internal/payloads/payload_to_model.go +++ b/pkg/handlers/internalapi/internal/payloads/payload_to_model.go @@ -187,6 +187,7 @@ func PPMShipmentModelFromCreate(ppmShipment *internalmessages.CreatePPMShipment) } model := &models.PPMShipment{ + PPMType: models.PPMType(ppmShipment.PpmType), SITExpected: ppmShipment.SitExpected, ExpectedDepartureDate: handlers.FmtDatePtrToPop(ppmShipment.ExpectedDepartureDate), } @@ -232,6 +233,7 @@ func UpdatePPMShipmentModel(ppmShipment *internalmessages.UpdatePPMShipment) *mo } ppmModel := &models.PPMShipment{ + PPMType: models.PPMType(ppmShipment.PpmType), ActualMoveDate: (*time.Time)(ppmShipment.ActualMoveDate), ActualPickupPostalCode: ppmShipment.ActualPickupPostalCode, ActualDestinationPostalCode: ppmShipment.ActualDestinationPostalCode, diff --git a/pkg/handlers/internalapi/internal/payloads/payload_to_model_test.go b/pkg/handlers/internalapi/internal/payloads/payload_to_model_test.go index 123a5494b46..21630911f5b 100644 --- a/pkg/handlers/internalapi/internal/payloads/payload_to_model_test.go +++ b/pkg/handlers/internalapi/internal/payloads/payload_to_model_test.go @@ -288,6 +288,7 @@ func (suite *PayloadsSuite) TestPPMShipmentModelFromUpdate() { } ppmShipment := internalmessages.UpdatePPMShipment{ + PpmType: internalmessages.PPMType(models.PPMTypeActualExpense), ExpectedDepartureDate: expectedDepartureDate, PickupAddress: &pickupAddress, SecondaryPickupAddress: &secondaryPickupAddress, @@ -317,6 +318,7 @@ func (suite *PayloadsSuite) TestPPMShipmentModelFromUpdate() { suite.Nil(model.HasTertiaryDestinationAddress) suite.True(*model.IsActualExpenseReimbursement) suite.NotNil(model) + suite.Equal(model.PPMType, models.PPMTypeActualExpense) } func (suite *PayloadsSuite) TestPPMShipmentModelWithOptionalDestinationStreet1FromCreate() { diff --git a/pkg/handlers/internalapi/orders.go b/pkg/handlers/internalapi/orders.go index 3471329d227..955743cdbe8 100644 --- a/pkg/handlers/internalapi/orders.go +++ b/pkg/handlers/internalapi/orders.go @@ -571,26 +571,47 @@ func (h UpdateOrdersHandler) Handle(params ordersop.UpdateOrdersParams) middlewa order.Entitlement = &entitlement // change actual expense reimbursement to 'true' for all PPM shipments if pay grade is civilian - if payload.Grade != nil && *payload.Grade == models.ServiceMemberGradeCIVILIANEMPLOYEE { + // if not, do the opposite and make the PPM type INCENTIVE_BASED + if payload.Grade != nil && *payload.Grade != *order.Grade { moves, fetchErr := models.FetchMovesByOrderID(appCtx.DB(), order.ID) if fetchErr != nil { appCtx.Logger().Error("failure encountered querying for move associated with the order", zap.Error(fetchErr)) } else { - move := moves[0] - for i := range move.MTOShipments { - shipment := &move.MTOShipments[i] - - if shipment.ShipmentType == models.MTOShipmentTypePPM { - if shipment.PPMShipment == nil { - appCtx.Logger().Warn("PPM shipment not found for MTO shipment", zap.String("shipmentID", shipment.ID.String())) - continue - } - // actual expense reimbursement is always true for civilian moves - shipment.PPMShipment.IsActualExpenseReimbursement = models.BoolPointer(true) + var move *models.Move + for i := range moves { + if moves[i].OrdersID == order.ID { + move = &moves[i] + break + } + } + if move == nil { + appCtx.Logger().Error("no move found matching order ID", zap.String("orderID", order.ID.String())) + } else { + // look at the values and see if the grade is CIVILIAN_EMPLOYEE + isCivilian := *payload.Grade == models.ServiceMemberGradeCIVILIANEMPLOYEE + reimbursementVal := isCivilian + var ppmType models.PPMType + // setting the default ppmType + if isCivilian { + ppmType = models.PPMTypeActualExpense + } else { + ppmType = models.PPMTypeIncentiveBased + } - if verrs, err := appCtx.DB().ValidateAndUpdate(shipment.PPMShipment); verrs.HasAny() || err != nil { - msg := "failure saving PPM shipment when updating orders" - appCtx.Logger().Error(msg, zap.Error(err)) + for i := range move.MTOShipments { + shipment := &move.MTOShipments[i] + if shipment.ShipmentType == models.MTOShipmentTypePPM { + if shipment.PPMShipment == nil { + appCtx.Logger().Warn("PPM shipment not found for MTO shipment", zap.String("shipmentID", shipment.ID.String())) + continue + } + shipment.PPMShipment.IsActualExpenseReimbursement = models.BoolPointer(reimbursementVal) + shipment.PPMShipment.PPMType = ppmType + + if verrs, err := appCtx.DB().ValidateAndUpdate(shipment.PPMShipment); verrs.HasAny() || err != nil { + msg := "failure saving PPM shipment when updating orders" + appCtx.Logger().Error(msg, zap.Error(err)) + } } } } diff --git a/pkg/handlers/internalapi/orders_test.go b/pkg/handlers/internalapi/orders_test.go index 5fa545fe4a2..77bd2247c84 100644 --- a/pkg/handlers/internalapi/orders_test.go +++ b/pkg/handlers/internalapi/orders_test.go @@ -985,6 +985,150 @@ func (suite *HandlerSuite) TestUpdateOrdersHandler() { suite.NotNil(updatedEntitlement.DependentsUnderTwelve) }) + suite.Run("Updating order grade to civilian changes PPM type to ACTUAL_EXPENSE", func() { + order := factory.BuildOrder(suite.DB(), []factory.Customization{ + { + Model: models.Order{ + Grade: models.ServiceMemberGradeE7.Pointer(), + }, + }, + }, nil) + + ppmShipment := factory.BuildPPMShipment(suite.DB(), []factory.Customization{ + { + Model: order, + LinkOnly: true, + }, + { + Model: models.PPMShipment{ + PPMType: models.PPMTypeIncentiveBased, + Status: models.PPMShipmentStatusDraft, + }, + }, + }, nil) + + newDutyLocation := factory.BuildDutyLocation(suite.DB(), nil, nil) + newOrdersType := internalmessages.OrdersTypePERMANENTCHANGEOFSTATION + newOrdersNumber := "123456" + issueDate := time.Date(2018, time.March, 10, 0, 0, 0, 0, time.UTC) + reportByDate := time.Date(2018, time.August, 1, 0, 0, 0, 0, time.UTC) + deptIndicator := internalmessages.DeptIndicatorARMY + payload := &internalmessages.CreateUpdateOrders{ + OrdersNumber: handlers.FmtString(newOrdersNumber), + OrdersType: &newOrdersType, + NewDutyLocationID: handlers.FmtUUID(newDutyLocation.ID), + OriginDutyLocationID: *handlers.FmtUUID(*order.OriginDutyLocationID), + IssueDate: handlers.FmtDate(issueDate), + ReportByDate: handlers.FmtDate(reportByDate), + DepartmentIndicator: &deptIndicator, + HasDependents: handlers.FmtBool(false), + SpouseHasProGear: handlers.FmtBool(false), + Grade: models.ServiceMemberGradeCIVILIANEMPLOYEE.Pointer(), + MoveID: *handlers.FmtUUID(ppmShipment.Shipment.MoveTaskOrderID), + CounselingOfficeID: handlers.FmtUUID(*newDutyLocation.TransportationOfficeID), + ServiceMemberID: handlers.FmtUUID(order.ServiceMemberID), + } + + path := fmt.Sprintf("/orders/%v", order.ID.String()) + req := httptest.NewRequest("PUT", path, nil) + req = suite.AuthenticateRequest(req, order.ServiceMember) + + params := ordersop.UpdateOrdersParams{ + HTTPRequest: req, + OrdersID: *handlers.FmtUUID(order.ID), + UpdateOrders: payload, + } + + fakeS3 := storageTest.NewFakeS3Storage(true) + handlerConfig := suite.HandlerConfig() + handlerConfig.SetFileStorer(fakeS3) + + handler := UpdateOrdersHandler{handlerConfig} + + response := handler.Handle(params) + + suite.IsType(&ordersop.UpdateOrdersOK{}, response) + okResponse := response.(*ordersop.UpdateOrdersOK) + suite.NoError(okResponse.Payload.Validate(strfmt.Default)) + + updatedPPM, err := models.FetchPPMShipmentByPPMShipmentID(suite.DB(), ppmShipment.ID) + suite.NoError(err) + suite.Equal(updatedPPM.PPMType, models.PPMTypeActualExpense) + suite.True(*updatedPPM.IsActualExpenseReimbursement) + }) + + suite.Run("Updating order grade FROM civilian to non-civilian changes PPM type to INCENTIVE_BASED", func() { + order := factory.BuildOrder(suite.DB(), []factory.Customization{ + { + Model: models.Order{ + Grade: models.ServiceMemberGradeCIVILIANEMPLOYEE.Pointer(), + }, + }, + }, nil) + + ppmShipment := factory.BuildPPMShipment(suite.DB(), []factory.Customization{ + { + Model: order, + LinkOnly: true, + }, + { + Model: models.PPMShipment{ + PPMType: models.PPMTypeActualExpense, + Status: models.PPMShipmentStatusDraft, + }, + }, + }, nil) + + newDutyLocation := factory.BuildDutyLocation(suite.DB(), nil, nil) + newOrdersType := internalmessages.OrdersTypePERMANENTCHANGEOFSTATION + newOrdersNumber := "123456" + issueDate := time.Date(2018, time.March, 10, 0, 0, 0, 0, time.UTC) + reportByDate := time.Date(2018, time.August, 1, 0, 0, 0, 0, time.UTC) + deptIndicator := internalmessages.DeptIndicatorARMY + payload := &internalmessages.CreateUpdateOrders{ + OrdersNumber: handlers.FmtString(newOrdersNumber), + OrdersType: &newOrdersType, + NewDutyLocationID: handlers.FmtUUID(newDutyLocation.ID), + OriginDutyLocationID: *handlers.FmtUUID(*order.OriginDutyLocationID), + IssueDate: handlers.FmtDate(issueDate), + ReportByDate: handlers.FmtDate(reportByDate), + DepartmentIndicator: &deptIndicator, + HasDependents: handlers.FmtBool(false), + SpouseHasProGear: handlers.FmtBool(false), + Grade: models.ServiceMemberGradeE7.Pointer(), + MoveID: *handlers.FmtUUID(ppmShipment.Shipment.MoveTaskOrderID), + CounselingOfficeID: handlers.FmtUUID(*newDutyLocation.TransportationOfficeID), + ServiceMemberID: handlers.FmtUUID(order.ServiceMemberID), + } + + path := fmt.Sprintf("/orders/%v", order.ID.String()) + req := httptest.NewRequest("PUT", path, nil) + req = suite.AuthenticateRequest(req, order.ServiceMember) + + params := ordersop.UpdateOrdersParams{ + HTTPRequest: req, + OrdersID: *handlers.FmtUUID(order.ID), + UpdateOrders: payload, + } + + fakeS3 := storageTest.NewFakeS3Storage(true) + handlerConfig := suite.HandlerConfig() + handlerConfig.SetFileStorer(fakeS3) + + handler := UpdateOrdersHandler{handlerConfig} + + response := handler.Handle(params) + + suite.IsType(&ordersop.UpdateOrdersOK{}, response) + okResponse := response.(*ordersop.UpdateOrdersOK) + suite.NoError(okResponse.Payload.Validate(strfmt.Default)) + + updatedPPM, err := models.FetchPPMShipmentByPPMShipmentID(suite.DB(), ppmShipment.ID) + suite.NoError(err) + suite.Equal(updatedPPM.PPMType, models.PPMTypeIncentiveBased) + suite.False(*updatedPPM.IsActualExpenseReimbursement) + }) + } func (suite *HandlerSuite) TestUpdateOrdersHandlerOriginPostalCodeAndGBLOC() { @@ -1044,6 +1188,12 @@ func (suite *HandlerSuite) TestUpdateOrdersHandlerOriginPostalCodeAndGBLOC() { }, }, nil) + factory.BuildMove(suite.DB(), []factory.Customization{ + { + Model: order, + LinkOnly: true, + }}, nil) + fetchedOrder, err := models.FetchOrder(suite.DB(), order.ID) suite.NoError(err) @@ -1187,8 +1337,6 @@ func (suite *HandlerSuite) TestUpdateOrdersHandlerWithCounselingOffice() { }, nil) newDutyLocation := factory.BuildDutyLocation(suite.DB(), nil, nil) - newTransportationOffice := factory.BuildTransportationOffice(suite.DB(), nil, nil) - newDutyLocation.TransportationOffice = newTransportationOffice newOrdersType := internalmessages.OrdersTypePERMANENTCHANGEOFSTATION newOrdersNumber := "123456" diff --git a/pkg/handlers/routing/routing_init.go b/pkg/handlers/routing/routing_init.go index 0abd6d434ca..f91fa88506a 100644 --- a/pkg/handlers/routing/routing_init.go +++ b/pkg/handlers/routing/routing_init.go @@ -495,6 +495,9 @@ func mountInternalAPI(appCtx appcontext.AppContext, routingConfig *Config, site rAuth.Use(customerAPIAuthMiddleware) tracingMiddleware := middleware.OpenAPITracing(api) rAuth.Mount("/", api.Serve(tracingMiddleware)) + r.Route("/open", func(rOpen chi.Router) { + rOpen.Mount("/", api.Serve(tracingMiddleware)) + }) }) }) } diff --git a/pkg/models/worksheet_shipment.go b/pkg/models/worksheet_shipment.go index 3fd8a5ea62c..b39a23e2455 100644 --- a/pkg/models/worksheet_shipment.go +++ b/pkg/models/worksheet_shipment.go @@ -72,4 +72,5 @@ type ShipmentSummaryFormData struct { SignedCertifications []*SignedCertification MaxSITStorageEntitlement int IsActualExpenseReimbursement bool + IsSmallPackageReimbursement bool } diff --git a/pkg/services/mto_service_item/mto_service_item_creator.go b/pkg/services/mto_service_item/mto_service_item_creator.go index f7c2fd3730b..ab587ece91f 100644 --- a/pkg/services/mto_service_item/mto_service_item_creator.go +++ b/pkg/services/mto_service_item/mto_service_item_creator.go @@ -637,7 +637,6 @@ func (o *mtoServiceItemCreator) CreateMTOServiceItem(appCtx appcontext.AppContex // if estimated weight for shipment provided by the prime, calculate the estimated prices for // DLH, DPK, DOP, DDP, DUPK - // NTS-release requested pickup dates are for handle out, their pricing is handled differently as their locations are based on storage facilities, not pickup locations if mtoShipment.PrimeEstimatedWeight != nil && mtoShipment.RequestedPickupDate != nil { serviceItemEstimatedPrice, err := o.FindEstimatedPrice(appCtx, serviceItem, mtoShipment) if serviceItemEstimatedPrice != 0 && err == nil { diff --git a/pkg/services/order/order_updater.go b/pkg/services/order/order_updater.go index 1338dc5c2e2..b9933335588 100644 --- a/pkg/services/order/order_updater.go +++ b/pkg/services/order/order_updater.go @@ -847,25 +847,46 @@ func updateOrderInTx(appCtx appcontext.AppContext, order models.Order, checks .. } // change actual expense reimbursement to 'true' for all PPM shipments if pay grade is civilian + // if not, do the opposite and make the PPM type INCENTIVE_BASED if order.Grade != nil && *order.Grade == models.ServiceMemberGradeCIVILIANEMPLOYEE { moves, fetchErr := models.FetchMovesByOrderID(appCtx.DB(), order.ID) - if fetchErr != nil || len(moves) == 0 { - appCtx.Logger().Error("failure encountered querying for move associated with the order", zap.Error(err)) + if fetchErr != nil { + appCtx.Logger().Error("failure encountered querying for move associated with the order", zap.Error(fetchErr)) } else { - move := moves[0] - for i := range move.MTOShipments { - shipment := &move.MTOShipments[i] - - if shipment.ShipmentType == models.MTOShipmentTypePPM { - if shipment.PPMShipment == nil { - appCtx.Logger().Warn("PPM shipment not found for MTO shipment", zap.String("shipmentID", shipment.ID.String())) - continue - } - shipment.PPMShipment.IsActualExpenseReimbursement = models.BoolPointer(true) + var move *models.Move + for i := range moves { + if moves[i].OrdersID == order.ID { + move = &moves[i] + break + } + } + if move == nil { + appCtx.Logger().Error("no move found matching order ID", zap.String("orderID", order.ID.String())) + } else { + // look at the values and see if the grade is CIVILIAN_EMPLOYEE + isCivilian := *order.Grade == models.ServiceMemberGradeCIVILIANEMPLOYEE + reimbursementVal := isCivilian + var ppmType models.PPMType + if isCivilian { + ppmType = models.PPMTypeActualExpense + } else { + ppmType = models.PPMTypeIncentiveBased + } - if verrs, err := appCtx.DB().ValidateAndUpdate(shipment.PPMShipment); verrs.HasAny() || err != nil { - msg := "failure saving PPM shipment when updating orders" - appCtx.Logger().Error(msg, zap.Error(err)) + for i := range move.MTOShipments { + shipment := &move.MTOShipments[i] + if shipment.ShipmentType == models.MTOShipmentTypePPM && shipment.Status == models.MTOShipmentStatusSubmitted && shipment.PPMShipment.PPMType == models.PPMTypeIncentiveBased { + if shipment.PPMShipment == nil { + appCtx.Logger().Warn("PPM shipment not found for MTO shipment", zap.String("shipmentID", shipment.ID.String())) + continue + } + shipment.PPMShipment.IsActualExpenseReimbursement = models.BoolPointer(reimbursementVal) + shipment.PPMShipment.PPMType = ppmType + + if verrs, err := appCtx.DB().ValidateAndUpdate(shipment.PPMShipment); verrs.HasAny() || err != nil { + msg := "failure saving PPM shipment when updating orders" + appCtx.Logger().Error(msg, zap.Error(err)) + } } } } diff --git a/pkg/services/order/order_updater_test.go b/pkg/services/order/order_updater_test.go index fe91426d7f6..337e7fbe1e5 100644 --- a/pkg/services/order/order_updater_test.go +++ b/pkg/services/order/order_updater_test.go @@ -485,7 +485,14 @@ func (suite *OrderServiceSuite) TestUpdateOrderAsCounselor() { moveRouter := move.NewMoveRouter(transportationoffice.NewTransportationOfficesFetcher()) orderUpdater := NewOrderUpdater(moveRouter) - ppmShipment := factory.BuildPPMShipmentThatNeedsCloseout(suite.DB(), nil, nil) + ppmShipment := factory.BuildPPMShipment(suite.DB(), []factory.Customization{ + { + Model: models.PPMShipment{ + PPMType: models.PPMTypeIncentiveBased, + Status: models.PPMShipmentStatusSubmitted, + }, + }, + }, nil) move := ppmShipment.Shipment.MoveTaskOrder order := move.Orders @@ -541,6 +548,41 @@ func (suite *OrderServiceSuite) TestUpdateOrderAsCounselor() { suite.Nil(updatedOrder) suite.IsType(apperror.InvalidInputError{}, err) }) + + suite.Run("Updating order grade to civilian changes submitted PPMs to PPM type ACTUAL_EXPENSE", func() { + moveRouter := move.NewMoveRouter(transportationoffice.NewTransportationOfficesFetcher()) + orderUpdater := NewOrderUpdater(moveRouter) + ppmShipment := factory.BuildPPMShipment(suite.DB(), []factory.Customization{ + { + Model: models.PPMShipment{ + PPMType: models.PPMTypeIncentiveBased, + Status: models.PPMShipmentStatusSubmitted, + }, + }, + }, nil) + move := ppmShipment.Shipment.MoveTaskOrder + order := move.Orders + + grade := ghcmessages.GradeCIVILIANEMPLOYEE + body := ghcmessages.CounselingUpdateOrderPayload{ + Grade: &grade, + } + eTag := etag.GenerateEtag(order.UpdatedAt) + + var moved models.Move + err := suite.DB().Find(&moved, move.ID) + suite.NoError(err) + + _, _, errs := orderUpdater.UpdateOrderAsCounselor(suite.AppContextForTest(), order.ID, body, eTag) + suite.NoError(errs) + + var updatedPPMShipment models.PPMShipment + err = suite.DB().Find(&updatedPPMShipment, ppmShipment.ID) + + suite.NoError(err) + suite.EqualValues(true, *updatedPPMShipment.IsActualExpenseReimbursement) + suite.Equal(updatedPPMShipment.PPMType, models.PPMTypeActualExpense) + }) } func (suite *OrderServiceSuite) TestUpdateAllowanceAsTOO() { diff --git a/pkg/services/ppmshipment/validation.go b/pkg/services/ppmshipment/validation.go index f2c27a597b6..17c9f20f165 100644 --- a/pkg/services/ppmshipment/validation.go +++ b/pkg/services/ppmshipment/validation.go @@ -66,6 +66,10 @@ func mergePPMShipment(newPPMShipment models.PPMShipment, oldPPMShipment *models. ppmShipment := *oldPPMShipment + if newPPMShipment.PPMType != "" { + ppmShipment.PPMType = newPPMShipment.PPMType + } + 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.") diff --git a/pkg/services/shipment_summary_worksheet.go b/pkg/services/shipment_summary_worksheet.go index d2bd339901c..85002c96611 100644 --- a/pkg/services/shipment_summary_worksheet.go +++ b/pkg/services/shipment_summary_worksheet.go @@ -56,50 +56,53 @@ type Page1Values struct { MileageTotal string MailingAddressW2 string IsActualExpenseReimbursement bool - GCCIsActualExpenseReimbursement string + IsSmallPackageReimbursement bool + GCCExpenseReimbursementType string } // Page2Values is an object representing a Shipment Summary Worksheet type Page2Values struct { - CUIBanner string - PreparationDate2 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 - Disbursement string - ShipmentPickupDates string - TrustedAgentName string - ServiceMemberSignature string - PPPOPPSORepresentative string - SignatureDate string - PPMRemainingEntitlement string + CUIBanner string + PreparationDate2 string + TAC string + SAC string + ContractedExpenseMemberPaid string + ContractedExpenseGTCCPaid string + RentalEquipmentMemberPaid string + RentalEquipmentGTCCPaid string + PackingMaterialsMemberPaid string + PackingMaterialsGTCCPaid string + SmallPackageExpenseMemberPaid string + SmallPackageExpenseGTCCPaid 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 + Disbursement string + ShipmentPickupDates string + TrustedAgentName string + ServiceMemberSignature string + PPPOPPSORepresentative string + SignatureDate string + PPMRemainingEntitlement string FormattedMovingExpenses FormattedOtherExpenses - IncentiveIsActualExpenseReimbursement string - HeaderIsActualExpenseReimbursement string + IncentiveExpenseReimbursementType string + HeaderExpenseReimbursementType string } // Page3Values is an object representing a Shipment Summary Worksheet @@ -117,30 +120,32 @@ type FormattedOtherExpenses struct { // FormattedMovingExpenses is an object representing the service member's moving expenses formatted for the SSW type FormattedMovingExpenses struct { - 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 + ContractedExpenseMemberPaid string + ContractedExpenseGTCCPaid string + RentalEquipmentMemberPaid string + RentalEquipmentGTCCPaid string + PackingMaterialsMemberPaid string + PackingMaterialsGTCCPaid string + SmallPackageExpenseMemberPaid string + SmallPackageExpenseGTCCPaid 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 } //go:generate mockery --name SSWPPMComputer diff --git a/pkg/services/shipment_summary_worksheet/shipment_summary_worksheet.go b/pkg/services/shipment_summary_worksheet/shipment_summary_worksheet.go index abd25465c44..fd546e341c6 100644 --- a/pkg/services/shipment_summary_worksheet/shipment_summary_worksheet.go +++ b/pkg/services/shipment_summary_worksheet/shipment_summary_worksheet.go @@ -306,7 +306,12 @@ func (s SSWPPMComputer) FormatValuesShipmentSummaryWorksheetFormPage1(data model // Fill out form fields related to Actual Expense Reimbursement status if data.PPMShipment.IsActualExpenseReimbursement != nil && *data.PPMShipment.IsActualExpenseReimbursement { page1.IsActualExpenseReimbursement = *data.PPMShipment.IsActualExpenseReimbursement - page1.GCCIsActualExpenseReimbursement = "Actual Expense Reimbursement" + page1.GCCExpenseReimbursementType = "Actual Expense Reimbursement" + } + + if data.PPMShipment.PPMType == models.PPMTypeSmallPackage { + page1.IsSmallPackageReimbursement = true + page1.GCCExpenseReimbursementType = "Small Package Reimbursement" } page1.SITDaysInStorage = formattedSIT.DaysInStorage @@ -373,6 +378,8 @@ func (s *SSWPPMComputer) FormatValuesShipmentSummaryWorksheetFormPage2(data mode page2.WeighingFeesGTCCPaid = FormatDollars(expensesMap["WeighingFeeGTCCPaid"]) page2.RentalEquipmentMemberPaid = FormatDollars(expensesMap["RentalEquipmentMemberPaid"]) page2.RentalEquipmentGTCCPaid = FormatDollars(expensesMap["RentalEquipmentGTCCPaid"]) + page2.SmallPackageExpenseMemberPaid = FormatDollars(expensesMap["SmallPackageExpenseMemberPaid"]) + page2.SmallPackageExpenseGTCCPaid = FormatDollars(expensesMap["SmallPackageExpenseGTCCPaid"]) page2.TollsMemberPaid = FormatDollars(expensesMap["TollsMemberPaid"]) page2.TollsGTCCPaid = FormatDollars(expensesMap["TollsGTCCPaid"]) page2.OilMemberPaid = FormatDollars(expensesMap["OilMemberPaid"]) @@ -394,8 +401,14 @@ func (s *SSWPPMComputer) FormatValuesShipmentSummaryWorksheetFormPage2(data mode page2.SignatureDate = certificationInfo.DateField if data.PPMShipment.IsActualExpenseReimbursement != nil && *data.PPMShipment.IsActualExpenseReimbursement { - page2.IncentiveIsActualExpenseReimbursement = "Actual Expense Reimbursement" - page2.HeaderIsActualExpenseReimbursement = `This PPM is being processed at actual expense reimbursement for valid expenses not to exceed the + page2.IncentiveExpenseReimbursementType = "Actual Expense Reimbursement" + page2.HeaderExpenseReimbursementType = `This PPM is being processed as actual expense reimbursement for valid expenses not to exceed the + government constructed cost (GCC).` + } + + if data.PPMShipment.PPMType == models.PPMTypeSmallPackage { + page2.IncentiveExpenseReimbursementType = "Small Package Reimbursement" + page2.HeaderExpenseReimbursementType = `This PPM is being processed as small package reimbursement for valid expenses not to exceed the government constructed cost (GCC).` } @@ -1154,6 +1167,11 @@ func (SSWPPMComputer *SSWPPMComputer) FetchDataShipmentSummaryWorksheetFormData( isActualExpenseReimbursement = true } + isSmallPackageReimbursement := false + if ppmShipment.PPMType == models.PPMTypeSmallPackage { + isSmallPackageReimbursement = true + } + ssd := models.ShipmentSummaryFormData{ AllShipments: ppmShipment.Shipment.MoveTaskOrder.MTOShipments, ServiceMember: serviceMember, @@ -1170,6 +1188,7 @@ func (SSWPPMComputer *SSWPPMComputer) FetchDataShipmentSummaryWorksheetFormData( SignedCertifications: signedCertifications, MaxSITStorageEntitlement: maxSit, IsActualExpenseReimbursement: isActualExpenseReimbursement, + IsSmallPackageReimbursement: isSmallPackageReimbursement, } return &ssd, nil } @@ -1249,6 +1268,11 @@ func (SSWPPMGenerator *SSWPPMGenerator) FillSSWPDFForm(Page1Values services.Page isActualExpenseReimbursement = true } + isSmallPackageReimbursement := false + if Page1Values.IsSmallPackageReimbursement { + isSmallPackageReimbursement = true + } + var sswCheckbox = []checkbox{ { Pages: []int{2}, @@ -1266,6 +1290,14 @@ func (SSWPPMGenerator *SSWPPMGenerator) FillSSWPDFForm(Page1Values services.Page Default: isActualExpenseReimbursement, Locked: false, }, + { + Pages: []int{1}, + ID: "555", + Name: "IsSmallPackageReimbursement", + Value: true, + Default: isSmallPackageReimbursement, + Locked: false, + }, } formData := pdFData{ // This is unique to each PDF template, must be found for new templates using PDFCPU's export function used on the template (can be done through CLI) 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 a3bd17fa425..b8208c450b2 100644 --- a/pkg/services/shipment_summary_worksheet/shipment_summary_worksheet_test.go +++ b/pkg/services/shipment_summary_worksheet/shipment_summary_worksheet_test.go @@ -290,226 +290,451 @@ func (suite *ShipmentSummaryWorksheetServiceSuite) TestFetchDataShipmentSummaryW } func (suite *ShipmentSummaryWorksheetServiceSuite) TestFormatValuesShipmentSummaryWorksheetFormPage1() { - yuma := factory.FetchOrBuildCurrentDutyLocation(suite.DB()) - fortGordon := factory.FetchOrBuildOrdersDutyLocation(suite.DB()) - wtgEntitlements := models.SSWMaxWeightEntitlement{ - Entitlement: 15000, - ProGear: 2000, - SpouseProGear: 500, - TotalWeight: 17500, - } + suite.Run("PPM Type Actual Expense Reimbursement - Success", func() { + yuma := factory.FetchOrBuildCurrentDutyLocation(suite.DB()) + fortGordon := factory.FetchOrBuildOrdersDutyLocation(suite.DB()) + wtgEntitlements := models.SSWMaxWeightEntitlement{ + Entitlement: 15000, + ProGear: 2000, + SpouseProGear: 500, + TotalWeight: 17500, + } - serviceMemberID, _ := uuid.NewV4() - serviceBranch := models.AffiliationAIRFORCE - grade := models.ServiceMemberGradeE9 - serviceMember := models.ServiceMember{ - ID: serviceMemberID, - FirstName: models.StringPointer("Marcus"), - MiddleName: models.StringPointer("Joseph"), - LastName: models.StringPointer("Jenkins"), - Suffix: models.StringPointer("Jr."), - Telephone: models.StringPointer("444-555-8888"), - PersonalEmail: models.StringPointer("michael+ppm-expansion_1@truss.works"), - Edipi: models.StringPointer("1234567890"), - Affiliation: &serviceBranch, - } + serviceMemberID, _ := uuid.NewV4() + serviceBranch := models.AffiliationAIRFORCE + grade := models.ServiceMemberGradeE9 + serviceMember := models.ServiceMember{ + ID: serviceMemberID, + FirstName: models.StringPointer("Marcus"), + MiddleName: models.StringPointer("Joseph"), + LastName: models.StringPointer("Jenkins"), + Suffix: models.StringPointer("Jr."), + Telephone: models.StringPointer("444-555-8888"), + PersonalEmail: models.StringPointer("michael+ppm-expansion_1@truss.works"), + Edipi: models.StringPointer("1234567890"), + Affiliation: &serviceBranch, + } - orderIssueDate := time.Date(2018, time.December, 21, 0, 0, 0, 0, time.UTC) - order := models.Order{ - IssueDate: orderIssueDate, - OrdersType: internalmessages.OrdersTypePERMANENTCHANGEOFSTATION, - OrdersNumber: models.StringPointer("012345"), - NewDutyLocationID: fortGordon.ID, - TAC: models.StringPointer("NTA4"), - SAC: models.StringPointer("SAC"), - HasDependents: true, - SpouseHasProGear: true, - Grade: &grade, - } - expectedPickupDate := time.Date(2019, time.January, 11, 0, 0, 0, 0, time.UTC) - actualPickupDate := time.Date(2019, time.February, 11, 0, 0, 0, 0, time.UTC) - netWeight := unit.Pound(4000) - cents := unit.Cents(1000) - locator := "ABCDEF-01" - estIncentive := unit.Cents(1000000) - maxIncentive := unit.Cents(2000000) - PPMShipments := models.PPMShipment{ - ExpectedDepartureDate: expectedPickupDate, - ActualMoveDate: &actualPickupDate, - Status: models.PPMShipmentStatusWaitingOnCustomer, - EstimatedWeight: &netWeight, - AdvanceAmountRequested: ¢s, - EstimatedIncentive: &estIncentive, - MaxIncentive: &maxIncentive, - Shipment: models.MTOShipment{ - ShipmentLocator: &locator, - }, - IsActualExpenseReimbursement: models.BoolPointer(true), - } - ssd := models.ShipmentSummaryFormData{ - ServiceMember: serviceMember, - Order: order, - CurrentDutyLocation: yuma, - NewDutyLocation: fortGordon, - PPMRemainingEntitlement: 3000, - WeightAllotment: wtgEntitlements, - PreparationDate: time.Date(2019, 1, 1, 1, 1, 1, 1, time.UTC), - PPMShipment: PPMShipments, - } + orderIssueDate := time.Date(2018, time.December, 21, 0, 0, 0, 0, time.UTC) + order := models.Order{ + IssueDate: orderIssueDate, + OrdersType: internalmessages.OrdersTypePERMANENTCHANGEOFSTATION, + OrdersNumber: models.StringPointer("012345"), + NewDutyLocationID: fortGordon.ID, + TAC: models.StringPointer("NTA4"), + SAC: models.StringPointer("SAC"), + HasDependents: true, + SpouseHasProGear: true, + Grade: &grade, + } + expectedPickupDate := time.Date(2019, time.January, 11, 0, 0, 0, 0, time.UTC) + actualPickupDate := time.Date(2019, time.February, 11, 0, 0, 0, 0, time.UTC) + netWeight := unit.Pound(4000) + cents := unit.Cents(1000) + locator := "ABCDEF-01" + estIncentive := unit.Cents(1000000) + maxIncentive := unit.Cents(2000000) + PPMShipments := models.PPMShipment{ + PPMType: models.PPMTypeActualExpense, + ExpectedDepartureDate: expectedPickupDate, + ActualMoveDate: &actualPickupDate, + Status: models.PPMShipmentStatusWaitingOnCustomer, + EstimatedWeight: &netWeight, + AdvanceAmountRequested: ¢s, + EstimatedIncentive: &estIncentive, + MaxIncentive: &maxIncentive, + Shipment: models.MTOShipment{ + ShipmentLocator: &locator, + }, + IsActualExpenseReimbursement: models.BoolPointer(true), + } + ssd := models.ShipmentSummaryFormData{ + ServiceMember: serviceMember, + Order: order, + CurrentDutyLocation: yuma, + NewDutyLocation: fortGordon, + PPMRemainingEntitlement: 3000, + WeightAllotment: wtgEntitlements, + PreparationDate: time.Date(2019, 1, 1, 1, 1, 1, 1, time.UTC), + PPMShipment: PPMShipments, + } - mockPPMCloseoutFetcher := &mocks.PPMCloseoutFetcher{} - sswPPMComputer := NewSSWPPMComputer(mockPPMCloseoutFetcher) - sswPage1, err := sswPPMComputer.FormatValuesShipmentSummaryWorksheetFormPage1(ssd, false) - suite.NoError(err) - suite.Equal(FormatDate(time.Now()), sswPage1.PreparationDate1) - - suite.Equal("Jenkins Jr., Marcus Joseph", sswPage1.ServiceMemberName) - suite.Equal("E-9", sswPage1.RankGrade) - suite.Equal("Air Force", sswPage1.ServiceBranch) - 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) - suite.Equal("444-555-8888", sswPage1.PreferredPhoneNumber) - suite.Equal("michael+ppm-expansion_1@truss.works", sswPage1.PreferredEmail) - suite.Equal("1234567890", sswPage1.DODId) - suite.Equal("Air Force", sswPage1.IssuingBranchOrAgency) - suite.Equal("21-Dec-2018", sswPage1.OrdersIssueDate) - suite.Equal("PCS/012345", sswPage1.OrdersTypeAndOrdersNumber) - suite.Equal("Fort Eisenhower, GA 30813", sswPage1.NewDutyAssignment) - suite.Equal("15,000", sswPage1.WeightAllotment) - suite.Equal("2,000", sswPage1.WeightAllotmentProGear) - suite.Equal("500", sswPage1.WeightAllotmentProgearSpouse) - suite.Equal("17,500", sswPage1.TotalWeightAllotment) - - suite.Equal(locator+" PPM", sswPage1.ShipmentNumberAndTypes) - suite.Equal("11-Jan-2019", sswPage1.ShipmentPickUpDates) - suite.Equal("4,000 lbs - Estimated", sswPage1.ShipmentWeights) - suite.Equal("Waiting On Customer", sswPage1.ShipmentCurrentShipmentStatuses) - suite.Equal("17,500", sswPage1.TotalWeightAllotmentRepeat) - suite.Equal("15,000 lbs; $20,000.00", sswPage1.MaxObligationGCC100) - suite.True(sswPage1.IsActualExpenseReimbursement) - suite.Equal("Actual Expense Reimbursement", sswPage1.GCCIsActualExpenseReimbursement) - - // quick test when there is no PPM actual move date - PPMShipmentWithoutActualMoveDate := models.PPMShipment{ - Status: models.PPMShipmentStatusWaitingOnCustomer, - EstimatedWeight: &netWeight, - AdvanceAmountRequested: ¢s, - Shipment: models.MTOShipment{ - ShipmentLocator: &locator, - }, - } + mockPPMCloseoutFetcher := &mocks.PPMCloseoutFetcher{} + sswPPMComputer := NewSSWPPMComputer(mockPPMCloseoutFetcher) + sswPage1, err := sswPPMComputer.FormatValuesShipmentSummaryWorksheetFormPage1(ssd, false) + suite.NoError(err) + suite.Equal(FormatDate(time.Now()), sswPage1.PreparationDate1) + + suite.Equal("Jenkins Jr., Marcus Joseph", sswPage1.ServiceMemberName) + suite.Equal("E-9", sswPage1.RankGrade) + suite.Equal("Air Force", sswPage1.ServiceBranch) + 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) + suite.Equal("444-555-8888", sswPage1.PreferredPhoneNumber) + suite.Equal("michael+ppm-expansion_1@truss.works", sswPage1.PreferredEmail) + suite.Equal("1234567890", sswPage1.DODId) + suite.Equal("Air Force", sswPage1.IssuingBranchOrAgency) + suite.Equal("21-Dec-2018", sswPage1.OrdersIssueDate) + suite.Equal("PCS/012345", sswPage1.OrdersTypeAndOrdersNumber) + suite.Equal("Fort Eisenhower, GA 30813", sswPage1.NewDutyAssignment) + suite.Equal("15,000", sswPage1.WeightAllotment) + suite.Equal("2,000", sswPage1.WeightAllotmentProGear) + suite.Equal("500", sswPage1.WeightAllotmentProgearSpouse) + suite.Equal("17,500", sswPage1.TotalWeightAllotment) + + suite.Equal(locator+" PPM", sswPage1.ShipmentNumberAndTypes) + suite.Equal("11-Jan-2019", sswPage1.ShipmentPickUpDates) + suite.Equal("4,000 lbs - Estimated", sswPage1.ShipmentWeights) + suite.Equal("Waiting On Customer", sswPage1.ShipmentCurrentShipmentStatuses) + suite.Equal("17,500", sswPage1.TotalWeightAllotmentRepeat) + suite.Equal("15,000 lbs; $20,000.00", sswPage1.MaxObligationGCC100) + suite.True(sswPage1.IsActualExpenseReimbursement) + suite.False(sswPage1.IsSmallPackageReimbursement) + suite.Equal("Actual Expense Reimbursement", sswPage1.GCCExpenseReimbursementType) + + // quick test when there is no PPM actual move date + PPMShipmentWithoutActualMoveDate := models.PPMShipment{ + Status: models.PPMShipmentStatusWaitingOnCustomer, + EstimatedWeight: &netWeight, + AdvanceAmountRequested: ¢s, + Shipment: models.MTOShipment{ + ShipmentLocator: &locator, + }, + } - ssdWithoutPPMActualMoveDate := models.ShipmentSummaryFormData{ - ServiceMember: serviceMember, - Order: order, - CurrentDutyLocation: yuma, - NewDutyLocation: fortGordon, - PPMRemainingEntitlement: 3000, - WeightAllotment: wtgEntitlements, - PreparationDate: time.Date(2019, 1, 1, 1, 1, 1, 1, time.UTC), - PPMShipment: PPMShipmentWithoutActualMoveDate, - } - sswPage1NoActualMoveDate, err := sswPPMComputer.FormatValuesShipmentSummaryWorksheetFormPage1(ssdWithoutPPMActualMoveDate, false) - suite.NoError(err) - suite.Equal("N/A", sswPage1NoActualMoveDate.ShipmentPickUpDates) + ssdWithoutPPMActualMoveDate := models.ShipmentSummaryFormData{ + ServiceMember: serviceMember, + Order: order, + CurrentDutyLocation: yuma, + NewDutyLocation: fortGordon, + PPMRemainingEntitlement: 3000, + WeightAllotment: wtgEntitlements, + PreparationDate: time.Date(2019, 1, 1, 1, 1, 1, 1, time.UTC), + PPMShipment: PPMShipmentWithoutActualMoveDate, + } + sswPage1NoActualMoveDate, err := sswPPMComputer.FormatValuesShipmentSummaryWorksheetFormPage1(ssdWithoutPPMActualMoveDate, false) + suite.NoError(err) + suite.Equal("N/A", sswPage1NoActualMoveDate.ShipmentPickUpDates) + }) + + suite.Run("PPM Type Small Package Reimbursement - Success", func() { + yuma := factory.FetchOrBuildCurrentDutyLocation(suite.DB()) + fortGordon := factory.FetchOrBuildOrdersDutyLocation(suite.DB()) + wtgEntitlements := models.SSWMaxWeightEntitlement{ + Entitlement: 15000, + ProGear: 2000, + SpouseProGear: 500, + TotalWeight: 17500, + } + + serviceMemberID, _ := uuid.NewV4() + serviceBranch := models.AffiliationAIRFORCE + grade := models.ServiceMemberGradeE9 + serviceMember := models.ServiceMember{ + ID: serviceMemberID, + FirstName: models.StringPointer("Marcus"), + MiddleName: models.StringPointer("Joseph"), + LastName: models.StringPointer("Jenkins"), + Suffix: models.StringPointer("Jr."), + Telephone: models.StringPointer("444-555-8888"), + PersonalEmail: models.StringPointer("michael+ppm-expansion_1@truss.works"), + Edipi: models.StringPointer("1234567890"), + Affiliation: &serviceBranch, + } + + orderIssueDate := time.Date(2018, time.December, 21, 0, 0, 0, 0, time.UTC) + order := models.Order{ + IssueDate: orderIssueDate, + OrdersType: internalmessages.OrdersTypePERMANENTCHANGEOFSTATION, + OrdersNumber: models.StringPointer("012345"), + NewDutyLocationID: fortGordon.ID, + TAC: models.StringPointer("NTA4"), + SAC: models.StringPointer("SAC"), + HasDependents: true, + SpouseHasProGear: true, + Grade: &grade, + } + expectedPickupDate := time.Date(2019, time.January, 11, 0, 0, 0, 0, time.UTC) + actualPickupDate := time.Date(2019, time.February, 11, 0, 0, 0, 0, time.UTC) + netWeight := unit.Pound(4000) + cents := unit.Cents(1000) + locator := "ABCDEF-01" + estIncentive := unit.Cents(1000000) + maxIncentive := unit.Cents(2000000) + PPMShipments := models.PPMShipment{ + PPMType: models.PPMTypeSmallPackage, + ExpectedDepartureDate: expectedPickupDate, + ActualMoveDate: &actualPickupDate, + Status: models.PPMShipmentStatusWaitingOnCustomer, + EstimatedWeight: &netWeight, + AdvanceAmountRequested: ¢s, + EstimatedIncentive: &estIncentive, + MaxIncentive: &maxIncentive, + Shipment: models.MTOShipment{ + ShipmentLocator: &locator, + }, + IsActualExpenseReimbursement: models.BoolPointer(false), + } + ssd := models.ShipmentSummaryFormData{ + ServiceMember: serviceMember, + Order: order, + CurrentDutyLocation: yuma, + NewDutyLocation: fortGordon, + PPMRemainingEntitlement: 3000, + WeightAllotment: wtgEntitlements, + PreparationDate: time.Date(2019, 1, 1, 1, 1, 1, 1, time.UTC), + PPMShipment: PPMShipments, + } + + mockPPMCloseoutFetcher := &mocks.PPMCloseoutFetcher{} + sswPPMComputer := NewSSWPPMComputer(mockPPMCloseoutFetcher) + sswPage1, err := sswPPMComputer.FormatValuesShipmentSummaryWorksheetFormPage1(ssd, false) + suite.NoError(err) + suite.Equal(FormatDate(time.Now()), sswPage1.PreparationDate1) + + suite.Equal("Jenkins Jr., Marcus Joseph", sswPage1.ServiceMemberName) + suite.Equal("E-9", sswPage1.RankGrade) + suite.Equal("Air Force", sswPage1.ServiceBranch) + 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) + suite.Equal("444-555-8888", sswPage1.PreferredPhoneNumber) + suite.Equal("michael+ppm-expansion_1@truss.works", sswPage1.PreferredEmail) + suite.Equal("1234567890", sswPage1.DODId) + suite.Equal("Air Force", sswPage1.IssuingBranchOrAgency) + suite.Equal("21-Dec-2018", sswPage1.OrdersIssueDate) + suite.Equal("PCS/012345", sswPage1.OrdersTypeAndOrdersNumber) + suite.Equal("Fort Eisenhower, GA 30813", sswPage1.NewDutyAssignment) + suite.Equal("15,000", sswPage1.WeightAllotment) + suite.Equal("2,000", sswPage1.WeightAllotmentProGear) + suite.Equal("500", sswPage1.WeightAllotmentProgearSpouse) + suite.Equal("17,500", sswPage1.TotalWeightAllotment) + + suite.Equal(locator+" PPM", sswPage1.ShipmentNumberAndTypes) + suite.Equal("11-Jan-2019", sswPage1.ShipmentPickUpDates) + suite.Equal("4,000 lbs - Estimated", sswPage1.ShipmentWeights) + suite.Equal("Waiting On Customer", sswPage1.ShipmentCurrentShipmentStatuses) + suite.Equal("17,500", sswPage1.TotalWeightAllotmentRepeat) + suite.Equal("15,000 lbs; $20,000.00", sswPage1.MaxObligationGCC100) + suite.False(sswPage1.IsActualExpenseReimbursement) + suite.True(sswPage1.IsSmallPackageReimbursement) + suite.Equal("Small Package Reimbursement", sswPage1.GCCExpenseReimbursementType) + + // quick test when there is no PPM actual move date + PPMShipmentWithoutActualMoveDate := models.PPMShipment{ + Status: models.PPMShipmentStatusWaitingOnCustomer, + EstimatedWeight: &netWeight, + AdvanceAmountRequested: ¢s, + Shipment: models.MTOShipment{ + ShipmentLocator: &locator, + }, + } + + ssdWithoutPPMActualMoveDate := models.ShipmentSummaryFormData{ + ServiceMember: serviceMember, + Order: order, + CurrentDutyLocation: yuma, + NewDutyLocation: fortGordon, + PPMRemainingEntitlement: 3000, + WeightAllotment: wtgEntitlements, + PreparationDate: time.Date(2019, 1, 1, 1, 1, 1, 1, time.UTC), + PPMShipment: PPMShipmentWithoutActualMoveDate, + } + sswPage1NoActualMoveDate, err := sswPPMComputer.FormatValuesShipmentSummaryWorksheetFormPage1(ssdWithoutPPMActualMoveDate, false) + suite.NoError(err) + suite.Equal("N/A", sswPage1NoActualMoveDate.ShipmentPickUpDates) + }) } func (suite *ShipmentSummaryWorksheetServiceSuite) TestFormatValuesShipmentSummaryWorksheetFormPage2() { - fortGordon := factory.FetchOrBuildOrdersDutyLocation(suite.DB()) - orderIssueDate := time.Date(2018, time.December, 21, 0, 0, 0, 0, time.UTC) - locator := "ABCDEF-01" - shipment := models.PPMShipment{ - Shipment: models.MTOShipment{ - ShipmentLocator: &locator, - }, - IsActualExpenseReimbursement: models.BoolPointer(true), - } + suite.Run("PPM Type Actual Expense Reimbursement - Success", func() { + fortGordon := factory.FetchOrBuildOrdersDutyLocation(suite.DB()) + orderIssueDate := time.Date(2018, time.December, 21, 0, 0, 0, 0, time.UTC) + locator := "ABCDEF-01" + shipment := models.PPMShipment{ + Shipment: models.MTOShipment{ + ShipmentLocator: &locator, + }, + PPMType: models.PPMTypeActualExpense, + IsActualExpenseReimbursement: models.BoolPointer(true), + } - order := models.Order{ - IssueDate: orderIssueDate, - OrdersType: internalmessages.OrdersTypePERMANENTCHANGEOFSTATION, - OrdersNumber: models.StringPointer("012345"), - NewDutyLocationID: fortGordon.ID, - TAC: models.StringPointer("NTA4"), - SAC: models.StringPointer("SAC"), - HasDependents: true, - SpouseHasProGear: true, - } - paidWithGTCCFalse := false - paidWithGTCCTrue := true - tollExpense := models.MovingExpenseReceiptTypeTolls - oilExpense := models.MovingExpenseReceiptTypeOil - amount := unit.Cents(10000) - statusApproved := models.PPMDocumentStatusApproved - movingExpenses := models.MovingExpenses{ - { - MovingExpenseType: &tollExpense, - Amount: &amount, - PaidWithGTCC: &paidWithGTCCFalse, - Status: &statusApproved, - }, - { - MovingExpenseType: &oilExpense, - Amount: &amount, - PaidWithGTCC: &paidWithGTCCFalse, - Status: &statusApproved, - }, - { - MovingExpenseType: &oilExpense, - Amount: &amount, - PaidWithGTCC: &paidWithGTCCTrue, - Status: &statusApproved, - }, - { - MovingExpenseType: &oilExpense, - Amount: &amount, - PaidWithGTCC: &paidWithGTCCFalse, - Status: &statusApproved, - }, - { - MovingExpenseType: &tollExpense, - Amount: &amount, - PaidWithGTCC: &paidWithGTCCTrue, - Status: &statusApproved, - }, - { - MovingExpenseType: &tollExpense, - Amount: &amount, - PaidWithGTCC: &paidWithGTCCTrue, - Status: &statusApproved, - }, - { - MovingExpenseType: &tollExpense, - Amount: &amount, - PaidWithGTCC: &paidWithGTCCFalse, - Status: &statusApproved, - }, - } + order := models.Order{ + IssueDate: orderIssueDate, + OrdersType: internalmessages.OrdersTypePERMANENTCHANGEOFSTATION, + OrdersNumber: models.StringPointer("012345"), + NewDutyLocationID: fortGordon.ID, + TAC: models.StringPointer("NTA4"), + SAC: models.StringPointer("SAC"), + HasDependents: true, + SpouseHasProGear: true, + } + paidWithGTCCFalse := false + paidWithGTCCTrue := true + tollExpense := models.MovingExpenseReceiptTypeTolls + oilExpense := models.MovingExpenseReceiptTypeOil + amount := unit.Cents(10000) + statusApproved := models.PPMDocumentStatusApproved + movingExpenses := models.MovingExpenses{ + { + MovingExpenseType: &tollExpense, + Amount: &amount, + PaidWithGTCC: &paidWithGTCCFalse, + Status: &statusApproved, + }, + { + MovingExpenseType: &oilExpense, + Amount: &amount, + PaidWithGTCC: &paidWithGTCCFalse, + Status: &statusApproved, + }, + { + MovingExpenseType: &oilExpense, + Amount: &amount, + PaidWithGTCC: &paidWithGTCCTrue, + Status: &statusApproved, + }, + { + MovingExpenseType: &oilExpense, + Amount: &amount, + PaidWithGTCC: &paidWithGTCCFalse, + Status: &statusApproved, + }, + { + MovingExpenseType: &tollExpense, + Amount: &amount, + PaidWithGTCC: &paidWithGTCCTrue, + Status: &statusApproved, + }, + { + MovingExpenseType: &tollExpense, + Amount: &amount, + PaidWithGTCC: &paidWithGTCCTrue, + Status: &statusApproved, + }, + { + MovingExpenseType: &tollExpense, + Amount: &amount, + PaidWithGTCC: &paidWithGTCCFalse, + Status: &statusApproved, + }, + } - ssd := models.ShipmentSummaryFormData{ - Order: order, - MovingExpenses: movingExpenses, - PPMShipment: shipment, - } + ssd := models.ShipmentSummaryFormData{ + Order: order, + MovingExpenses: movingExpenses, + PPMShipment: shipment, + } - mockPPMCloseoutFetcher := &mocks.PPMCloseoutFetcher{} - sswPPMComputer := NewSSWPPMComputer(mockPPMCloseoutFetcher) - expensesMap := SubTotalExpenses(ssd.MovingExpenses) - sswPage2, err := sswPPMComputer.FormatValuesShipmentSummaryWorksheetFormPage2(ssd, false, expensesMap) - suite.NoError(err) - 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) - suite.Equal("Actual Expense Reimbursement", sswPage2.IncentiveIsActualExpenseReimbursement) - suite.Equal(`This PPM is being processed at actual expense reimbursement for valid expenses not to exceed the - government constructed cost (GCC).`, sswPage2.HeaderIsActualExpenseReimbursement) + mockPPMCloseoutFetcher := &mocks.PPMCloseoutFetcher{} + sswPPMComputer := NewSSWPPMComputer(mockPPMCloseoutFetcher) + expensesMap := SubTotalExpenses(ssd.MovingExpenses) + sswPage2, err := sswPPMComputer.FormatValuesShipmentSummaryWorksheetFormPage2(ssd, false, expensesMap) + suite.NoError(err) + 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) + suite.Equal("Actual Expense Reimbursement", sswPage2.IncentiveExpenseReimbursementType) + suite.Equal(`This PPM is being processed as actual expense reimbursement for valid expenses not to exceed the + government constructed cost (GCC).`, sswPage2.HeaderExpenseReimbursementType) + }) + + suite.Run("PPM Type Small Package Reimbursement - Success", func() { + fortGordon := factory.FetchOrBuildOrdersDutyLocation(suite.DB()) + orderIssueDate := time.Date(2018, time.December, 21, 0, 0, 0, 0, time.UTC) + locator := "ABCDEF-01" + shipment := models.PPMShipment{ + Shipment: models.MTOShipment{ + ShipmentLocator: &locator, + }, + PPMType: models.PPMTypeSmallPackage, + IsActualExpenseReimbursement: models.BoolPointer(false), + } + + order := models.Order{ + IssueDate: orderIssueDate, + OrdersType: internalmessages.OrdersTypePERMANENTCHANGEOFSTATION, + OrdersNumber: models.StringPointer("012345"), + NewDutyLocationID: fortGordon.ID, + TAC: models.StringPointer("NTA4"), + SAC: models.StringPointer("SAC"), + HasDependents: true, + SpouseHasProGear: true, + } + paidWithGTCCFalse := false + paidWithGTCCTrue := true + tollExpense := models.MovingExpenseReceiptTypeTolls + oilExpense := models.MovingExpenseReceiptTypeOil + amount := unit.Cents(10000) + movingExpenses := models.MovingExpenses{ + { + MovingExpenseType: &tollExpense, + Amount: &amount, + PaidWithGTCC: &paidWithGTCCFalse, + }, + { + MovingExpenseType: &oilExpense, + Amount: &amount, + PaidWithGTCC: &paidWithGTCCFalse, + }, + { + MovingExpenseType: &oilExpense, + Amount: &amount, + PaidWithGTCC: &paidWithGTCCTrue, + }, + { + MovingExpenseType: &oilExpense, + Amount: &amount, + PaidWithGTCC: &paidWithGTCCFalse, + }, + { + MovingExpenseType: &tollExpense, + Amount: &amount, + PaidWithGTCC: &paidWithGTCCTrue, + }, + { + MovingExpenseType: &tollExpense, + Amount: &amount, + PaidWithGTCC: &paidWithGTCCTrue, + }, + { + MovingExpenseType: &tollExpense, + Amount: &amount, + PaidWithGTCC: &paidWithGTCCFalse, + }, + } + + ssd := models.ShipmentSummaryFormData{ + Order: order, + MovingExpenses: movingExpenses, + PPMShipment: shipment, + } + + mockPPMCloseoutFetcher := &mocks.PPMCloseoutFetcher{} + sswPPMComputer := NewSSWPPMComputer(mockPPMCloseoutFetcher) + expensesMap := SubTotalExpenses(ssd.MovingExpenses) + sswPage2, err := sswPPMComputer.FormatValuesShipmentSummaryWorksheetFormPage2(ssd, false, expensesMap) + suite.NoError(err) + 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) + suite.Equal("Small Package Reimbursement", sswPage2.IncentiveExpenseReimbursementType) + suite.Equal(`This PPM is being processed as small package reimbursement for valid expenses not to exceed the + government constructed cost (GCC).`, sswPage2.HeaderExpenseReimbursementType) + }) } func (suite *ShipmentSummaryWorksheetServiceSuite) TestFormatValuesShipmentSummaryWorksheetFormPage2ExcludeRejectedOrExcludedExpensesFromTotal() { diff --git a/playwright/tests/office/txo/tooQueueFilters.spec.js b/playwright/tests/office/txo/tooQueueFilters.spec.js new file mode 100644 index 00000000000..908f5346c19 --- /dev/null +++ b/playwright/tests/office/txo/tooQueueFilters.spec.js @@ -0,0 +1,206 @@ +// @ts-check +import { test, expect } from '../../utils/office/officeTest'; + +import TooFlowPage from './tooTestFixture'; + +const waitForFilterInput = async (page, testId, value) => { + await page.waitForFunction( + (args) => { + /** @type {HTMLInputElement} */ + const input = document.querySelector(`[data-testid="${args.testId}"] [data-testid="TextBoxFilter"]`); + if (input && input.value === args.value) return true; + if (input) { + input.value = args.value; + const keyupEvent = new KeyboardEvent('keyup', { + code: 'Enter', + key: 'Enter', + bubbles: true, + cancelable: true, + }); + input.dispatchEvent(keyupEvent); + } + return false; + }, + { testId, value }, + { polling: 500, timeout: 5000 }, + ); +}; + +test.describe('TOO user queue filters - Move Queue', async () => { + let testMove; + let testMoveNotAssigned; + let tooFlowPage; + + test.beforeEach(async ({ officePage }) => { + testMove = await officePage.testHarness.buildMoveWithNTSShipmentsForTOO(); + testMoveNotAssigned = await officePage.testHarness.buildMoveWithNTSShipmentsForTOO(); + await officePage.signInAsNewTOOUser(); + tooFlowPage = new TooFlowPage(officePage, testMove); + await tooFlowPage.waitForLoading(); + }); + + test('filters out all moves with nonsense assigned user', async ({ page }) => { + // We should still see all moves + await expect(page.getByRole('heading', { level: 1 })).not.toContainText('All moves (0)'); + + // Add nonsense string to our filter (so now we're searching for 'abcde') + await waitForFilterInput(page, 'assignedTo', 'abcde'); + + // Now we shouldn't see any results + await expect(page.getByRole('heading', { level: 1 })).toContainText('All moves (0)'); + await expect(page.getByRole('row').getByText(testMove.locator)).not.toBeVisible(); + await expect(page.getByRole('row').getByText(testMoveNotAssigned.locator)).not.toBeVisible(); + }); + + test('filters all moves EXCEPT with assigned user, restores all moves when filter removed', async ({ page }) => { + await Promise.all([ + page.waitForResponse((res) => res.url().includes('/ghc/v1/queues/moves')), + page.getByText('KKFA moves').click(), + ]); + + await expect(page.getByRole('heading', { level: 1 })).not.toContainText('All moves (0)'); + const allMoves = await page.getByRole('heading', { level: 1 }).innerHTML(); + + // assign user to test move + await waitForFilterInput(page, 'locator', testMove.locator); + + await expect(page.getByTestId('locator').getByTestId('TextBoxFilter')).toHaveValue(testMove.locator); + await expect(page.getByRole('row').getByText(testMove.locator)).toBeVisible(); + + expect( + await page + .getByTestId('assignedTo-0') + .getByTestId('dropdown') + .getByRole('option', { selected: true }) + .textContent(), + ).toEqual('—'); + + await Promise.all([ + page.waitForResponse((res) => res.url().includes('assignOfficeUser')), + page.getByTestId('assignedTo-0').getByTestId('dropdown').selectOption({ index: 1 }), + ]); + + const assigned = ( + await page + .getByTestId('assignedTo-0') + .getByTestId('dropdown') + .getByRole('option', { selected: true }) + .textContent() + ) + .split(',') + .at(0); + + // search for moves with assigned user filter only + await waitForFilterInput(page, 'locator', ''); + + await expect(page.getByTestId('locator').getByTestId('TextBoxFilter')).toBeEmpty(); + await expect(page.getByRole('heading', { level: 1 })).toContainText(allMoves); + + await waitForFilterInput(page, 'assignedTo', assigned); + + // assigned moves for user show in queue + await expect(page.getByRole('table').getByText(testMove.locator)).toBeVisible(); + await expect(page.getByRole('heading', { level: 1 })).not.toContainText(allMoves); + await expect(page.getByRole('heading', { level: 1 })).not.toContainText('All moves (0)'); + await expect(page.getByRole('row').getByText(testMove.locator)).toBeVisible(); + await expect(page.getByRole('row').getByText(testMoveNotAssigned.locator)).not.toBeVisible(); + + // Now, remove filter and ensure retores all moves in queue + const removeFilterButton = page.getByTestId('remove-filters-assignedTo'); + await expect(removeFilterButton).toBeVisible(); + await removeFilterButton.click(); + await expect(page.getByRole('heading', { level: 1 })).toContainText(allMoves); + }); +}); + +test.describe('TOO user queue filters - Destination Requests Queue', async () => { + let testMove; + let testMoveNotAssigned; + let tooFlowPage; + + test.beforeEach(async ({ officePage }) => { + testMove = await officePage.testHarness.buildHHGMoveInSITWithPendingExtension(); + testMoveNotAssigned = await officePage.testHarness.buildHHGMoveInSITWithPendingExtension(); + await officePage.signInAsNewTOOUser(); + tooFlowPage = new TooFlowPage(officePage, testMove); + await tooFlowPage.waitForLoading(); + }); + + test('filters out all moves with nonsense assigned user', async ({ page }) => { + // We should still see all moves + await Promise.all([ + page.waitForResponse((res) => res.url().includes('/ghc/v1/queues/destination-requests')), + page.getByTestId('destination-requests-tab-link').click(), + ]); + await expect(page.getByRole('heading', { level: 1 })).not.toContainText('Destination requests (0)'); + + // Add nonsense string to our filter (so now we're searching for 'abcde') + await waitForFilterInput(page, 'assignedTo', 'abcde'); + + // Now we shouldn't see any results + await expect(page.getByRole('heading', { level: 1 })).toContainText('Destination requests (0)'); + await expect(page.getByRole('row').getByText(testMove.locator)).not.toBeVisible(); + await expect(page.getByRole('row').getByText(testMoveNotAssigned.locator)).not.toBeVisible(); + }); + + test('filters all moves EXCEPT with assigned user, restores all moves when filter removed', async ({ page }) => { + // We should still see all moves + await Promise.all([ + page.waitForResponse((res) => res.url().includes('/ghc/v1/queues/destination-requests')), + page.getByTestId('destination-requests-tab-link').click(), + ]); + + await expect(page.getByRole('heading', { level: 1 })).not.toContainText('Destination requests (0)'); + const destinationRequests = await page.getByRole('heading', { level: 1 }).innerHTML(); + + // assign user to test move + await waitForFilterInput(page, 'locator', testMove.locator); + + await expect(page.getByTestId('locator').getByTestId('TextBoxFilter')).toHaveValue(testMove.locator); + await expect(page.getByRole('row').getByText(testMove.locator)).toBeVisible(); + + expect( + await page + .getByTestId('assignedTo-0') + .getByTestId('dropdown') + .getByRole('option', { selected: true }) + .textContent(), + ).toEqual('—'); + + await Promise.all([ + page.waitForResponse((res) => res.url().includes('assignOfficeUser')), + page.getByTestId('assignedTo-0').getByTestId('dropdown').selectOption({ index: 1 }), + ]); + + const assigned = ( + await page + .getByTestId('assignedTo-0') + .getByTestId('dropdown') + .getByRole('option', { selected: true }) + .textContent() + ) + .split(',') + .at(0); + + // search for moves with assigned user filter only + await waitForFilterInput(page, 'locator', ''); + + await expect(page.getByTestId('locator').getByTestId('TextBoxFilter')).toBeEmpty(); + await expect(page.getByRole('heading', { level: 1 })).toContainText(destinationRequests); + + await waitForFilterInput(page, 'assignedTo', assigned); + + // assigned moves for user show in queue + await expect(page.getByRole('table').getByText(testMove.locator)).toBeVisible(); + await expect(page.getByRole('heading', { level: 1 })).not.toContainText(destinationRequests); + await expect(page.getByRole('heading', { level: 1 })).not.toContainText('Destination requests (0)'); + await expect(page.getByRole('row').getByText(testMove.locator)).toBeVisible(); + await expect(page.getByRole('row').getByText(testMoveNotAssigned.locator)).not.toBeVisible(); + + // Now, remove filter and ensure retores all moves in queue + const removeFilterButton = page.getByTestId('remove-filters-assignedTo'); + await expect(removeFilterButton).toBeVisible(); + await removeFilterButton.click(); + await expect(page.getByRole('heading', { level: 1 })).toContainText(destinationRequests); + }); +}); diff --git a/src/components/Office/DefinitionLists/PPMShipmentInfoList.jsx b/src/components/Office/DefinitionLists/PPMShipmentInfoList.jsx index 7032cf46561..9a78bc612e4 100644 --- a/src/components/Office/DefinitionLists/PPMShipmentInfoList.jsx +++ b/src/components/Office/DefinitionLists/PPMShipmentInfoList.jsx @@ -18,6 +18,7 @@ import { permissionTypes } from 'constants/permissions'; import Restricted from 'components/Restricted/Restricted'; import { downloadPPMAOAPacket, downloadPPMPaymentPacket } from 'services/ghcApi'; import { isBooleanFlagEnabled } from 'utils/featureFlags'; +import { getPPMTypeLabel } from 'shared/constants'; const PPMShipmentInfoList = ({ className, @@ -30,6 +31,7 @@ const PPMShipmentInfoList = ({ onErrorModalToggle, }) => { const { + ppmType, hasRequestedAdvance, advanceAmountRequested, advanceStatus, @@ -87,6 +89,14 @@ const PPMShipmentInfoList = ({ return (isExpanded || elementFlags.alwaysShow) && !elementFlags.hideRow; }; + const ppmTypeElementFlags = getDisplayFlags('ppmType'); + const ppmTypeElement = ( +