diff --git a/pkg/gen/ghcapi/embedded_spec.go b/pkg/gen/ghcapi/embedded_spec.go index 9c048579f7f..f275ddad7ed 100644 --- a/pkg/gen/ghcapi/embedded_spec.go +++ b/pkg/gen/ghcapi/embedded_spec.go @@ -4145,7 +4145,8 @@ func init() { "closeoutInitiated", "closeoutLocation", "ppmStatus", - "counselingOffice" + "counselingOffice", + "assignedTo" ], "type": "string", "description": "field that results should be sorted by", @@ -4298,6 +4299,12 @@ func init() { "description": "Used to return a queue for a GBLOC other than the default of the current user. Requires the HQ role. The parameter is ignored if the requesting user does not have the necessary role.\n", "name": "viewAsGBLOC", "in": "query" + }, + { + "type": "string", + "description": "Used to illustrate which user is assigned to this payment request.\n", + "name": "assignedTo", + "in": "query" } ], "responses": { @@ -4386,7 +4393,8 @@ func init() { "originDutyLocation", "destinationDutyLocation", "requestedMoveDate", - "appearedInTooAt" + "appearedInTooAt", + "assignedTo" ], "type": "string", "description": "field that results should be sorted by", @@ -6369,13 +6377,13 @@ func init() { "firstName": { "type": "string" }, - "id": { + "lastName": { + "type": "string" + }, + "officeUserId": { "type": "string", "format": "uuid", "example": "c56a4180-65aa-42ec-a945-5fd21dec0538" - }, - "lastName": { - "type": "string" } } }, @@ -11736,6 +11744,9 @@ func init() { "x-nullable": true, "$ref": "#/definitions/AssignedOfficeUser" }, + "availableOfficeUsers": { + "$ref": "#/definitions/AvailableOfficeUsers" + }, "closeoutInitiated": { "type": "string", "format": "date-time", @@ -11828,9 +11839,6 @@ func init() { "QueueMovesResult": { "type": "object", "properties": { - "availableOfficeUsers": { - "$ref": "#/definitions/AvailableOfficeUsers" - }, "page": { "type": "integer" }, @@ -11853,6 +11861,9 @@ func init() { "type": "number", "format": "double" }, + "availableOfficeUsers": { + "$ref": "#/definitions/AvailableOfficeUsers" + }, "customer": { "$ref": "#/definitions/Customer" }, @@ -11918,9 +11929,6 @@ func init() { "QueuePaymentRequestsResult": { "type": "object", "properties": { - "availableOfficeUsers": { - "$ref": "#/definitions/AvailableOfficeUsers" - }, "page": { "type": "integer" }, @@ -19439,7 +19447,8 @@ func init() { "closeoutInitiated", "closeoutLocation", "ppmStatus", - "counselingOffice" + "counselingOffice", + "assignedTo" ], "type": "string", "description": "field that results should be sorted by", @@ -19592,6 +19601,12 @@ func init() { "description": "Used to return a queue for a GBLOC other than the default of the current user. Requires the HQ role. The parameter is ignored if the requesting user does not have the necessary role.\n", "name": "viewAsGBLOC", "in": "query" + }, + { + "type": "string", + "description": "Used to illustrate which user is assigned to this payment request.\n", + "name": "assignedTo", + "in": "query" } ], "responses": { @@ -19692,7 +19707,8 @@ func init() { "originDutyLocation", "destinationDutyLocation", "requestedMoveDate", - "appearedInTooAt" + "appearedInTooAt", + "assignedTo" ], "type": "string", "description": "field that results should be sorted by", @@ -22045,13 +22061,13 @@ func init() { "firstName": { "type": "string" }, - "id": { + "lastName": { + "type": "string" + }, + "officeUserId": { "type": "string", "format": "uuid", "example": "c56a4180-65aa-42ec-a945-5fd21dec0538" - }, - "lastName": { - "type": "string" } } }, @@ -27491,6 +27507,9 @@ func init() { "x-nullable": true, "$ref": "#/definitions/AssignedOfficeUser" }, + "availableOfficeUsers": { + "$ref": "#/definitions/AvailableOfficeUsers" + }, "closeoutInitiated": { "type": "string", "format": "date-time", @@ -27583,9 +27602,6 @@ func init() { "QueueMovesResult": { "type": "object", "properties": { - "availableOfficeUsers": { - "$ref": "#/definitions/AvailableOfficeUsers" - }, "page": { "type": "integer" }, @@ -27608,6 +27624,9 @@ func init() { "type": "number", "format": "double" }, + "availableOfficeUsers": { + "$ref": "#/definitions/AvailableOfficeUsers" + }, "customer": { "$ref": "#/definitions/Customer" }, @@ -27673,9 +27692,6 @@ func init() { "QueuePaymentRequestsResult": { "type": "object", "properties": { - "availableOfficeUsers": { - "$ref": "#/definitions/AvailableOfficeUsers" - }, "page": { "type": "integer" }, diff --git a/pkg/gen/ghcapi/ghcoperations/queues/get_moves_queue_parameters.go b/pkg/gen/ghcapi/ghcoperations/queues/get_moves_queue_parameters.go index 9257f37c8ff..3f41b22702b 100644 --- a/pkg/gen/ghcapi/ghcoperations/queues/get_moves_queue_parameters.go +++ b/pkg/gen/ghcapi/ghcoperations/queues/get_moves_queue_parameters.go @@ -519,7 +519,7 @@ func (o *GetMovesQueueParams) bindSort(rawData []string, hasKey bool, formats st // validateSort carries on validations for parameter Sort func (o *GetMovesQueueParams) validateSort(formats strfmt.Registry) error { - if err := validate.EnumCase("sort", "query", *o.Sort, []interface{}{"lastName", "dodID", "emplid", "branch", "locator", "status", "originDutyLocation", "destinationDutyLocation", "requestedMoveDate", "appearedInTooAt"}, true); err != nil { + if err := validate.EnumCase("sort", "query", *o.Sort, []interface{}{"lastName", "dodID", "emplid", "branch", "locator", "status", "originDutyLocation", "destinationDutyLocation", "requestedMoveDate", "appearedInTooAt", "assignedTo"}, true); err != nil { return err } diff --git a/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_parameters.go b/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_parameters.go index 6d1745d74fd..20744a2c85b 100644 --- a/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_parameters.go +++ b/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_parameters.go @@ -34,6 +34,11 @@ type GetServicesCounselingQueueParams struct { // HTTP Request Object HTTPRequest *http.Request `json:"-"` + /*Used to illustrate which user is assigned to this payment request. + + In: query + */ + AssignedTo *string /*filters by the branch of the move's service member In: query */ @@ -143,6 +148,11 @@ func (o *GetServicesCounselingQueueParams) BindRequest(r *http.Request, route *m qs := runtime.Values(r.URL.Query()) + qAssignedTo, qhkAssignedTo, _ := qs.GetOK("assignedTo") + if err := o.bindAssignedTo(qAssignedTo, qhkAssignedTo, route.Formats); err != nil { + res = append(res, err) + } + qBranch, qhkBranch, _ := qs.GetOK("branch") if err := o.bindBranch(qBranch, qhkBranch, route.Formats); err != nil { res = append(res, err) @@ -263,6 +273,24 @@ func (o *GetServicesCounselingQueueParams) BindRequest(r *http.Request, route *m return nil } +// bindAssignedTo binds and validates parameter AssignedTo from query. +func (o *GetServicesCounselingQueueParams) bindAssignedTo(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + o.AssignedTo = &raw + + return nil +} + // bindBranch binds and validates parameter Branch from query. func (o *GetServicesCounselingQueueParams) bindBranch(rawData []string, hasKey bool, formats strfmt.Registry) error { var raw string @@ -723,7 +751,7 @@ func (o *GetServicesCounselingQueueParams) bindSort(rawData []string, hasKey boo // validateSort carries on validations for parameter Sort func (o *GetServicesCounselingQueueParams) validateSort(formats strfmt.Registry) error { - if err := validate.EnumCase("sort", "query", *o.Sort, []interface{}{"lastName", "dodID", "emplid", "branch", "locator", "status", "requestedMoveDate", "submittedAt", "originGBLOC", "originDutyLocation", "destinationDutyLocation", "ppmType", "closeoutInitiated", "closeoutLocation", "ppmStatus", "counselingOffice"}, true); err != nil { + if err := validate.EnumCase("sort", "query", *o.Sort, []interface{}{"lastName", "dodID", "emplid", "branch", "locator", "status", "requestedMoveDate", "submittedAt", "originGBLOC", "originDutyLocation", "destinationDutyLocation", "ppmType", "closeoutInitiated", "closeoutLocation", "ppmStatus", "counselingOffice", "assignedTo"}, true); err != nil { return err } diff --git a/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_urlbuilder.go b/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_urlbuilder.go index c9549f94ac2..5f1d7d96c2c 100644 --- a/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_urlbuilder.go +++ b/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_urlbuilder.go @@ -16,6 +16,7 @@ import ( // GetServicesCounselingQueueURL generates an URL for the get services counseling queue operation type GetServicesCounselingQueueURL struct { + AssignedTo *string Branch *string CloseoutInitiated *strfmt.DateTime CloseoutLocation *string @@ -74,6 +75,14 @@ func (o *GetServicesCounselingQueueURL) Build() (*url.URL, error) { qs := make(url.Values) + var assignedToQ string + if o.AssignedTo != nil { + assignedToQ = *o.AssignedTo + } + if assignedToQ != "" { + qs.Set("assignedTo", assignedToQ) + } + var branchQ string if o.Branch != nil { branchQ = *o.Branch diff --git a/pkg/gen/ghcmessages/assigned_office_user.go b/pkg/gen/ghcmessages/assigned_office_user.go index 60d6a5b687a..596b4f192f5 100644 --- a/pkg/gen/ghcmessages/assigned_office_user.go +++ b/pkg/gen/ghcmessages/assigned_office_user.go @@ -22,20 +22,20 @@ type AssignedOfficeUser struct { // first name FirstName string `json:"firstName,omitempty"` - // id - // Example: c56a4180-65aa-42ec-a945-5fd21dec0538 - // Format: uuid - ID strfmt.UUID `json:"id,omitempty"` - // last name LastName string `json:"lastName,omitempty"` + + // office user Id + // Example: c56a4180-65aa-42ec-a945-5fd21dec0538 + // Format: uuid + OfficeUserID strfmt.UUID `json:"officeUserId,omitempty"` } // Validate validates this assigned office user func (m *AssignedOfficeUser) Validate(formats strfmt.Registry) error { var res []error - if err := m.validateID(formats); err != nil { + if err := m.validateOfficeUserID(formats); err != nil { res = append(res, err) } @@ -45,12 +45,12 @@ func (m *AssignedOfficeUser) Validate(formats strfmt.Registry) error { return nil } -func (m *AssignedOfficeUser) validateID(formats strfmt.Registry) error { - if swag.IsZero(m.ID) { // not required +func (m *AssignedOfficeUser) validateOfficeUserID(formats strfmt.Registry) error { + if swag.IsZero(m.OfficeUserID) { // not required return nil } - if err := validate.FormatOf("id", "body", "uuid", m.ID.String(), formats); err != nil { + if err := validate.FormatOf("officeUserId", "body", "uuid", m.OfficeUserID.String(), formats); err != nil { return err } diff --git a/pkg/gen/ghcmessages/queue_move.go b/pkg/gen/ghcmessages/queue_move.go index f7c831bff29..ba4c5370b52 100644 --- a/pkg/gen/ghcmessages/queue_move.go +++ b/pkg/gen/ghcmessages/queue_move.go @@ -27,6 +27,9 @@ type QueueMove struct { // assigned to AssignedTo *AssignedOfficeUser `json:"assignedTo,omitempty"` + // available office users + AvailableOfficeUsers AvailableOfficeUsers `json:"availableOfficeUsers,omitempty"` + // closeout initiated // Format: date-time CloseoutInitiated *strfmt.DateTime `json:"closeoutInitiated,omitempty"` @@ -107,6 +110,10 @@ func (m *QueueMove) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validateAvailableOfficeUsers(formats); err != nil { + res = append(res, err) + } + if err := m.validateCloseoutInitiated(formats); err != nil { res = append(res, err) } @@ -204,6 +211,23 @@ func (m *QueueMove) validateAssignedTo(formats strfmt.Registry) error { return nil } +func (m *QueueMove) validateAvailableOfficeUsers(formats strfmt.Registry) error { + if swag.IsZero(m.AvailableOfficeUsers) { // not required + return nil + } + + if err := m.AvailableOfficeUsers.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("availableOfficeUsers") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("availableOfficeUsers") + } + return err + } + + return nil +} + func (m *QueueMove) validateCloseoutInitiated(formats strfmt.Registry) error { if swag.IsZero(m.CloseoutInitiated) { // not required return nil @@ -472,6 +496,10 @@ func (m *QueueMove) ContextValidate(ctx context.Context, formats strfmt.Registry res = append(res, err) } + if err := m.contextValidateAvailableOfficeUsers(ctx, formats); err != nil { + res = append(res, err) + } + if err := m.contextValidateCustomer(ctx, formats); err != nil { res = append(res, err) } @@ -531,6 +559,20 @@ func (m *QueueMove) contextValidateAssignedTo(ctx context.Context, formats strfm return nil } +func (m *QueueMove) contextValidateAvailableOfficeUsers(ctx context.Context, formats strfmt.Registry) error { + + if err := m.AvailableOfficeUsers.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("availableOfficeUsers") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("availableOfficeUsers") + } + return err + } + + return nil +} + func (m *QueueMove) contextValidateCustomer(ctx context.Context, formats strfmt.Registry) error { if m.Customer != nil { diff --git a/pkg/gen/ghcmessages/queue_moves_result.go b/pkg/gen/ghcmessages/queue_moves_result.go index c4ebe97ec54..a0117f55989 100644 --- a/pkg/gen/ghcmessages/queue_moves_result.go +++ b/pkg/gen/ghcmessages/queue_moves_result.go @@ -18,9 +18,6 @@ import ( // swagger:model QueueMovesResult type QueueMovesResult struct { - // available office users - AvailableOfficeUsers AvailableOfficeUsers `json:"availableOfficeUsers,omitempty"` - // page Page int64 `json:"page,omitempty"` @@ -38,10 +35,6 @@ type QueueMovesResult struct { func (m *QueueMovesResult) Validate(formats strfmt.Registry) error { var res []error - if err := m.validateAvailableOfficeUsers(formats); err != nil { - res = append(res, err) - } - if err := m.validateQueueMoves(formats); err != nil { res = append(res, err) } @@ -52,23 +45,6 @@ func (m *QueueMovesResult) Validate(formats strfmt.Registry) error { return nil } -func (m *QueueMovesResult) validateAvailableOfficeUsers(formats strfmt.Registry) error { - if swag.IsZero(m.AvailableOfficeUsers) { // not required - return nil - } - - if err := m.AvailableOfficeUsers.Validate(formats); err != nil { - if ve, ok := err.(*errors.Validation); ok { - return ve.ValidateName("availableOfficeUsers") - } else if ce, ok := err.(*errors.CompositeError); ok { - return ce.ValidateName("availableOfficeUsers") - } - return err - } - - return nil -} - func (m *QueueMovesResult) validateQueueMoves(formats strfmt.Registry) error { if swag.IsZero(m.QueueMoves) { // not required return nil @@ -90,10 +66,6 @@ func (m *QueueMovesResult) validateQueueMoves(formats strfmt.Registry) error { func (m *QueueMovesResult) ContextValidate(ctx context.Context, formats strfmt.Registry) error { var res []error - if err := m.contextValidateAvailableOfficeUsers(ctx, formats); err != nil { - res = append(res, err) - } - if err := m.contextValidateQueueMoves(ctx, formats); err != nil { res = append(res, err) } @@ -104,20 +76,6 @@ func (m *QueueMovesResult) ContextValidate(ctx context.Context, formats strfmt.R return nil } -func (m *QueueMovesResult) contextValidateAvailableOfficeUsers(ctx context.Context, formats strfmt.Registry) error { - - if err := m.AvailableOfficeUsers.ContextValidate(ctx, formats); err != nil { - if ve, ok := err.(*errors.Validation); ok { - return ve.ValidateName("availableOfficeUsers") - } else if ce, ok := err.(*errors.CompositeError); ok { - return ce.ValidateName("availableOfficeUsers") - } - return err - } - - return nil -} - func (m *QueueMovesResult) contextValidateQueueMoves(ctx context.Context, formats strfmt.Registry) error { if err := m.QueueMoves.ContextValidate(ctx, formats); err != nil { diff --git a/pkg/gen/ghcmessages/queue_payment_request.go b/pkg/gen/ghcmessages/queue_payment_request.go index 10e354e5081..737d1198f5d 100644 --- a/pkg/gen/ghcmessages/queue_payment_request.go +++ b/pkg/gen/ghcmessages/queue_payment_request.go @@ -22,6 +22,9 @@ type QueuePaymentRequest struct { // Days since the payment request has been requested. Decimal representation will allow more accurate sorting. Age float64 `json:"age,omitempty"` + // available office users + AvailableOfficeUsers AvailableOfficeUsers `json:"availableOfficeUsers,omitempty"` + // customer Customer *Customer `json:"customer,omitempty"` @@ -68,6 +71,10 @@ type QueuePaymentRequest struct { func (m *QueuePaymentRequest) Validate(formats strfmt.Registry) error { var res []error + if err := m.validateAvailableOfficeUsers(formats); err != nil { + res = append(res, err) + } + if err := m.validateCustomer(formats); err != nil { res = append(res, err) } @@ -114,6 +121,23 @@ func (m *QueuePaymentRequest) Validate(formats strfmt.Registry) error { return nil } +func (m *QueuePaymentRequest) validateAvailableOfficeUsers(formats strfmt.Registry) error { + if swag.IsZero(m.AvailableOfficeUsers) { // not required + return nil + } + + if err := m.AvailableOfficeUsers.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("availableOfficeUsers") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("availableOfficeUsers") + } + return err + } + + return nil +} + func (m *QueuePaymentRequest) validateCustomer(formats strfmt.Registry) error { if swag.IsZero(m.Customer) { // not required return nil @@ -269,6 +293,10 @@ func (m *QueuePaymentRequest) validateSubmittedAt(formats strfmt.Registry) error func (m *QueuePaymentRequest) ContextValidate(ctx context.Context, formats strfmt.Registry) error { var res []error + if err := m.contextValidateAvailableOfficeUsers(ctx, formats); err != nil { + res = append(res, err) + } + if err := m.contextValidateCustomer(ctx, formats); err != nil { res = append(res, err) } @@ -295,6 +323,20 @@ func (m *QueuePaymentRequest) ContextValidate(ctx context.Context, formats strfm return nil } +func (m *QueuePaymentRequest) contextValidateAvailableOfficeUsers(ctx context.Context, formats strfmt.Registry) error { + + if err := m.AvailableOfficeUsers.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("availableOfficeUsers") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("availableOfficeUsers") + } + return err + } + + return nil +} + func (m *QueuePaymentRequest) contextValidateCustomer(ctx context.Context, formats strfmt.Registry) error { if m.Customer != nil { diff --git a/pkg/gen/ghcmessages/queue_payment_requests_result.go b/pkg/gen/ghcmessages/queue_payment_requests_result.go index 3516bfe634b..be3af16ade8 100644 --- a/pkg/gen/ghcmessages/queue_payment_requests_result.go +++ b/pkg/gen/ghcmessages/queue_payment_requests_result.go @@ -18,9 +18,6 @@ import ( // swagger:model QueuePaymentRequestsResult type QueuePaymentRequestsResult struct { - // available office users - AvailableOfficeUsers AvailableOfficeUsers `json:"availableOfficeUsers,omitempty"` - // page Page int64 `json:"page,omitempty"` @@ -38,10 +35,6 @@ type QueuePaymentRequestsResult struct { func (m *QueuePaymentRequestsResult) Validate(formats strfmt.Registry) error { var res []error - if err := m.validateAvailableOfficeUsers(formats); err != nil { - res = append(res, err) - } - if err := m.validateQueuePaymentRequests(formats); err != nil { res = append(res, err) } @@ -52,23 +45,6 @@ func (m *QueuePaymentRequestsResult) Validate(formats strfmt.Registry) error { return nil } -func (m *QueuePaymentRequestsResult) validateAvailableOfficeUsers(formats strfmt.Registry) error { - if swag.IsZero(m.AvailableOfficeUsers) { // not required - return nil - } - - if err := m.AvailableOfficeUsers.Validate(formats); err != nil { - if ve, ok := err.(*errors.Validation); ok { - return ve.ValidateName("availableOfficeUsers") - } else if ce, ok := err.(*errors.CompositeError); ok { - return ce.ValidateName("availableOfficeUsers") - } - return err - } - - return nil -} - func (m *QueuePaymentRequestsResult) validateQueuePaymentRequests(formats strfmt.Registry) error { if swag.IsZero(m.QueuePaymentRequests) { // not required return nil @@ -90,10 +66,6 @@ func (m *QueuePaymentRequestsResult) validateQueuePaymentRequests(formats strfmt func (m *QueuePaymentRequestsResult) ContextValidate(ctx context.Context, formats strfmt.Registry) error { var res []error - if err := m.contextValidateAvailableOfficeUsers(ctx, formats); err != nil { - res = append(res, err) - } - if err := m.contextValidateQueuePaymentRequests(ctx, formats); err != nil { res = append(res, err) } @@ -104,20 +76,6 @@ func (m *QueuePaymentRequestsResult) ContextValidate(ctx context.Context, format return nil } -func (m *QueuePaymentRequestsResult) contextValidateAvailableOfficeUsers(ctx context.Context, formats strfmt.Registry) error { - - if err := m.AvailableOfficeUsers.ContextValidate(ctx, formats); err != nil { - if ve, ok := err.(*errors.Validation); ok { - return ve.ValidateName("availableOfficeUsers") - } else if ce, ok := err.(*errors.CompositeError); ok { - return ce.ValidateName("availableOfficeUsers") - } - return err - } - - return nil -} - func (m *QueuePaymentRequestsResult) contextValidateQueuePaymentRequests(ctx context.Context, formats strfmt.Registry) error { if err := m.QueuePaymentRequests.ContextValidate(ctx, formats); err != nil { diff --git a/pkg/gen/internalapi/embedded_spec.go b/pkg/gen/internalapi/embedded_spec.go index eaffc63273a..cd537076ec3 100644 --- a/pkg/gen/internalapi/embedded_spec.go +++ b/pkg/gen/internalapi/embedded_spec.go @@ -4144,6 +4144,11 @@ func init() { "title": "Orders date", "example": "2018-04-26" }, + "move_id": { + "type": "string", + "format": "uuid", + "example": "cf1addea-a4f9-4173-8506-2bb82a064cb7" + }, "new_duty_location_id": { "type": "string", "format": "uuid", @@ -4494,6 +4499,9 @@ func init() { "closeoutOffice": { "$ref": "#/definitions/TransportationOffice" }, + "counselingOffice": { + "$ref": "#/definitions/TransportationOffice" + }, "createdAt": { "type": "string", "format": "date-time", @@ -5141,6 +5149,9 @@ func init() { "closeout_office": { "$ref": "#/definitions/TransportationOffice" }, + "counseling_office": { + "$ref": "#/definitions/TransportationOffice" + }, "created_at": { "type": "string", "format": "date-time" @@ -12760,6 +12771,11 @@ func init() { "title": "Orders date", "example": "2018-04-26" }, + "move_id": { + "type": "string", + "format": "uuid", + "example": "cf1addea-a4f9-4173-8506-2bb82a064cb7" + }, "new_duty_location_id": { "type": "string", "format": "uuid", @@ -13112,6 +13128,9 @@ func init() { "closeoutOffice": { "$ref": "#/definitions/TransportationOffice" }, + "counselingOffice": { + "$ref": "#/definitions/TransportationOffice" + }, "createdAt": { "type": "string", "format": "date-time", @@ -13761,6 +13780,9 @@ func init() { "closeout_office": { "$ref": "#/definitions/TransportationOffice" }, + "counseling_office": { + "$ref": "#/definitions/TransportationOffice" + }, "created_at": { "type": "string", "format": "date-time" diff --git a/pkg/gen/internalmessages/create_update_orders.go b/pkg/gen/internalmessages/create_update_orders.go index dbf96f2969b..ad3cd16e743 100644 --- a/pkg/gen/internalmessages/create_update_orders.go +++ b/pkg/gen/internalmessages/create_update_orders.go @@ -42,6 +42,11 @@ type CreateUpdateOrders struct { // Format: date IssueDate *strfmt.Date `json:"issue_date"` + // move id + // Example: cf1addea-a4f9-4173-8506-2bb82a064cb7 + // Format: uuid + MoveID strfmt.UUID `json:"move_id,omitempty"` + // new duty location id // Example: c56a4180-65aa-42ec-a945-5fd21dec0538 // Required: true @@ -115,6 +120,10 @@ func (m *CreateUpdateOrders) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validateMoveID(formats); err != nil { + res = append(res, err) + } + if err := m.validateNewDutyLocationID(formats); err != nil { res = append(res, err) } @@ -221,6 +230,18 @@ func (m *CreateUpdateOrders) validateIssueDate(formats strfmt.Registry) error { return nil } +func (m *CreateUpdateOrders) validateMoveID(formats strfmt.Registry) error { + if swag.IsZero(m.MoveID) { // not required + return nil + } + + if err := validate.FormatOf("move_id", "body", "uuid", m.MoveID.String(), formats); err != nil { + return err + } + + return nil +} + func (m *CreateUpdateOrders) validateNewDutyLocationID(formats strfmt.Registry) error { if err := validate.Required("new_duty_location_id", "body", m.NewDutyLocationID); err != nil { diff --git a/pkg/gen/internalmessages/internal_move.go b/pkg/gen/internalmessages/internal_move.go index 1c8885a1a78..3a2bc0e44f0 100644 --- a/pkg/gen/internalmessages/internal_move.go +++ b/pkg/gen/internalmessages/internal_move.go @@ -22,6 +22,9 @@ type InternalMove struct { // closeout office CloseoutOffice *TransportationOffice `json:"closeoutOffice,omitempty"` + // counseling office + CounselingOffice *TransportationOffice `json:"counselingOffice,omitempty"` + // created at // Read Only: true // Format: date-time @@ -80,6 +83,10 @@ func (m *InternalMove) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validateCounselingOffice(formats); err != nil { + res = append(res, err) + } + if err := m.validateCreatedAt(formats); err != nil { res = append(res, err) } @@ -133,6 +140,25 @@ func (m *InternalMove) validateCloseoutOffice(formats strfmt.Registry) error { return nil } +func (m *InternalMove) validateCounselingOffice(formats strfmt.Registry) error { + if swag.IsZero(m.CounselingOffice) { // not required + return nil + } + + if m.CounselingOffice != nil { + if err := m.CounselingOffice.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("counselingOffice") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("counselingOffice") + } + return err + } + } + + return nil +} + func (m *InternalMove) validateCreatedAt(formats strfmt.Registry) error { if swag.IsZero(m.CreatedAt) { // not required return nil @@ -230,6 +256,10 @@ func (m *InternalMove) ContextValidate(ctx context.Context, formats strfmt.Regis res = append(res, err) } + if err := m.contextValidateCounselingOffice(ctx, formats); err != nil { + res = append(res, err) + } + if err := m.contextValidateCreatedAt(ctx, formats); err != nil { res = append(res, err) } @@ -289,6 +319,27 @@ func (m *InternalMove) contextValidateCloseoutOffice(ctx context.Context, format return nil } +func (m *InternalMove) contextValidateCounselingOffice(ctx context.Context, formats strfmt.Registry) error { + + if m.CounselingOffice != nil { + + if swag.IsZero(m.CounselingOffice) { // not required + return nil + } + + if err := m.CounselingOffice.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("counselingOffice") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("counselingOffice") + } + return err + } + } + + return nil +} + func (m *InternalMove) contextValidateCreatedAt(ctx context.Context, formats strfmt.Registry) error { if err := validate.ReadOnly(ctx, "createdAt", "body", strfmt.DateTime(m.CreatedAt)); err != nil { diff --git a/pkg/gen/internalmessages/move_payload.go b/pkg/gen/internalmessages/move_payload.go index a7d8d834419..464d094fca0 100644 --- a/pkg/gen/internalmessages/move_payload.go +++ b/pkg/gen/internalmessages/move_payload.go @@ -29,6 +29,9 @@ type MovePayload struct { // closeout office CloseoutOffice *TransportationOffice `json:"closeout_office,omitempty"` + // counseling office + CounselingOffice *TransportationOffice `json:"counseling_office,omitempty"` + // created at // Required: true // Format: date-time @@ -94,6 +97,10 @@ func (m *MovePayload) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validateCounselingOffice(formats); err != nil { + res = append(res, err) + } + if err := m.validateCreatedAt(formats); err != nil { res = append(res, err) } @@ -182,6 +189,25 @@ func (m *MovePayload) validateCloseoutOffice(formats strfmt.Registry) error { return nil } +func (m *MovePayload) validateCounselingOffice(formats strfmt.Registry) error { + if swag.IsZero(m.CounselingOffice) { // not required + return nil + } + + if m.CounselingOffice != nil { + if err := m.CounselingOffice.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("counseling_office") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("counseling_office") + } + return err + } + } + + return nil +} + func (m *MovePayload) validateCreatedAt(formats strfmt.Registry) error { if err := validate.Required("created_at", "body", m.CreatedAt); err != nil { @@ -334,6 +360,10 @@ func (m *MovePayload) ContextValidate(ctx context.Context, formats strfmt.Regist res = append(res, err) } + if err := m.contextValidateCounselingOffice(ctx, formats); err != nil { + res = append(res, err) + } + if err := m.contextValidateMtoShipments(ctx, formats); err != nil { res = append(res, err) } @@ -398,6 +428,27 @@ func (m *MovePayload) contextValidateCloseoutOffice(ctx context.Context, formats return nil } +func (m *MovePayload) contextValidateCounselingOffice(ctx context.Context, formats strfmt.Registry) error { + + if m.CounselingOffice != nil { + + if swag.IsZero(m.CounselingOffice) { // not required + return nil + } + + if err := m.CounselingOffice.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("counseling_office") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("counseling_office") + } + return err + } + } + + return nil +} + func (m *MovePayload) contextValidateMtoShipments(ctx context.Context, formats strfmt.Registry) error { if err := m.MtoShipments.ContextValidate(ctx, formats); err != nil { diff --git a/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go b/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go index 63521590ca1..79c46d780fd 100644 --- a/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go +++ b/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go @@ -54,9 +54,9 @@ func OfficeUser(officeUser *models.OfficeUser) *ghcmessages.LockedOfficeUser { func AssignedOfficeUser(officeUser *models.OfficeUser) *ghcmessages.AssignedOfficeUser { if officeUser != nil { payload := ghcmessages.AssignedOfficeUser{ - ID: strfmt.UUID(officeUser.ID.String()), - FirstName: officeUser.FirstName, - LastName: officeUser.LastName, + OfficeUserID: strfmt.UUID(officeUser.ID.String()), + FirstName: officeUser.FirstName, + LastName: officeUser.LastName, } return &payload } @@ -2034,7 +2034,7 @@ func QueueAvailableOfficeUsers(officeUsers []models.OfficeUser) *ghcmessages.Ava } // QueueMoves payload -func QueueMoves(moves []models.Move) *ghcmessages.QueueMoves { +func QueueMoves(moves []models.Move, officeUsers []models.OfficeUser) *ghcmessages.QueueMoves { queueMoves := make(ghcmessages.QueueMoves, len(moves)) for i, move := range moves { customer := move.Orders.ServiceMember @@ -2116,6 +2116,7 @@ func QueueMoves(moves []models.Move) *ghcmessages.QueueMoves { PpmStatus: ghcmessages.PPMStatus(ppmStatus), CounselingOffice: &transportationOffice, AssignedTo: AssignedOfficeUser(move.SCAssignedUser), + AvailableOfficeUsers: *QueueAvailableOfficeUsers(officeUsers), } } return &queueMoves @@ -2184,7 +2185,7 @@ func queuePaymentRequestStatus(paymentRequest models.PaymentRequest) string { } // QueuePaymentRequests payload -func QueuePaymentRequests(paymentRequests *models.PaymentRequests) *ghcmessages.QueuePaymentRequests { +func QueuePaymentRequests(paymentRequests *models.PaymentRequests, officeUsers []models.OfficeUser) *ghcmessages.QueuePaymentRequests { queuePaymentRequests := make(ghcmessages.QueuePaymentRequests, len(*paymentRequests)) for i, paymentRequest := range *paymentRequests { @@ -2208,6 +2209,7 @@ func QueuePaymentRequests(paymentRequests *models.PaymentRequests) *ghcmessages. OrderType: (*string)(orders.OrdersType.Pointer()), LockedByOfficeUserID: handlers.FmtUUIDPtr(moveTaskOrder.LockedByOfficeUserID), LockExpiresAt: handlers.FmtDateTimePtr(moveTaskOrder.LockExpiresAt), + AvailableOfficeUsers: *QueueAvailableOfficeUsers(officeUsers), } if orders.DepartmentIndicator != nil { diff --git a/pkg/handlers/ghcapi/move_test.go b/pkg/handlers/ghcapi/move_test.go index 28a957f81a0..999f5393c77 100644 --- a/pkg/handlers/ghcapi/move_test.go +++ b/pkg/handlers/ghcapi/move_test.go @@ -20,7 +20,6 @@ import ( "github.com/transcom/mymove/pkg/services" movelocker "github.com/transcom/mymove/pkg/services/lock_move" "github.com/transcom/mymove/pkg/services/mocks" - move "github.com/transcom/mymove/pkg/services/move" moveservice "github.com/transcom/mymove/pkg/services/move" officeuser "github.com/transcom/mymove/pkg/services/office_user" transportationoffice "github.com/transcom/mymove/pkg/services/transportation_office" @@ -707,7 +706,7 @@ func (suite *HandlerSuite) TestUpdateMoveCloseoutOfficeHandler() { func (suite *HandlerSuite) TestUploadAdditionalDocumentsHander() { fakeS3 := storageTest.NewFakeS3Storage(true) uploadCreator := upload.NewUploadCreator(fakeS3) - additionalDocumentsUploader := move.NewMoveAdditionalDocumentsUploader(uploadCreator) + additionalDocumentsUploader := moveservice.NewMoveAdditionalDocumentsUploader(uploadCreator) setupRequestAndParams := func(move models.Move) *moveops.UploadAdditionalDocumentsParams { endpoint := fmt.Sprintf("/moves/%v/upload_additional_documents", move.ID) @@ -805,7 +804,7 @@ func (suite *HandlerSuite) TestUpdateAssignedOfficeUserHandler() { payload := response.(*moveops.UpdateAssignedOfficeUserOK).Payload suite.NoError(payload.Validate(strfmt.Default)) - suite.Equal(officeUserID, payload.SCAssignedUser.ID) + suite.Equal(officeUserID, payload.SCAssignedUser.OfficeUserID) }) suite.Run("Successful update of a move's TOO", func() { req, handler, move, officeUser := setupTestData() @@ -828,7 +827,7 @@ func (suite *HandlerSuite) TestUpdateAssignedOfficeUserHandler() { payload := response.(*moveops.UpdateAssignedOfficeUserOK).Payload suite.NoError(payload.Validate(strfmt.Default)) - suite.Equal(officeUserID, payload.TOOAssignedUser.ID) + suite.Equal(officeUserID, payload.TOOAssignedUser.OfficeUserID) }) suite.Run("Successful update of a move's TIO", func() { req, handler, move, officeUser := setupTestData() @@ -851,7 +850,7 @@ func (suite *HandlerSuite) TestUpdateAssignedOfficeUserHandler() { payload := response.(*moveops.UpdateAssignedOfficeUserOK).Payload suite.NoError(payload.Validate(strfmt.Default)) - suite.Equal(officeUserID, payload.TIOAssignedUser.ID) + suite.Equal(officeUserID, payload.TIOAssignedUser.OfficeUserID) }) suite.Run("Successful unassign of an office user", func() { move = factory.BuildMove(suite.DB(), nil, nil) diff --git a/pkg/handlers/ghcapi/mto_service_items_test.go b/pkg/handlers/ghcapi/mto_service_items_test.go index 069f7d1ea91..3b0b3e74d1b 100644 --- a/pkg/handlers/ghcapi/mto_service_items_test.go +++ b/pkg/handlers/ghcapi/mto_service_items_test.go @@ -24,7 +24,6 @@ import ( "github.com/transcom/mymove/pkg/services/ghcrateengine" mobilehomeshipment "github.com/transcom/mymove/pkg/services/mobile_home_shipment" "github.com/transcom/mymove/pkg/services/mocks" - moverouter "github.com/transcom/mymove/pkg/services/move" moveservices "github.com/transcom/mymove/pkg/services/move" mtoserviceitem "github.com/transcom/mymove/pkg/services/mto_service_item" mtoshipment "github.com/transcom/mymove/pkg/services/mto_shipment" @@ -555,7 +554,7 @@ func (suite *HandlerSuite) TestUpdateMTOServiceItemStatusHandler() { } fetcher := fetch.NewFetcher(queryBuilder) - moveRouter := moverouter.NewMoveRouter() + moveRouter := moveservices.NewMoveRouter() shipmentFetcher := mtoshipment.NewMTOShipmentFetcher() addressCreator := address.NewAddressCreator() planner := &routemocks.Planner{} @@ -594,7 +593,7 @@ func (suite *HandlerSuite) TestUpdateMTOServiceItemStatusHandler() { // by the handler is working as expected. suite.Run("Successful status update of MTO service item and event trigger", func() { queryBuilder := query.NewQueryBuilder() - moveRouter := moverouter.NewMoveRouter() + moveRouter := moveservices.NewMoveRouter() shipmentFetcher := mtoshipment.NewMTOShipmentFetcher() mtoServiceItem, availableMove := suite.createServiceItem() requestUser := factory.BuildUser(nil, nil, nil) diff --git a/pkg/handlers/ghcapi/queues.go b/pkg/handlers/ghcapi/queues.go index 8178ddb8515..7aba9de57ed 100644 --- a/pkg/handlers/ghcapi/queues.go +++ b/pkg/handlers/ghcapi/queues.go @@ -133,15 +133,13 @@ func (h GetMovesQueueHandler) Handle(params queues.GetMovesQueueParams) middlewa } } - queueMoves := payloads.QueueMoves(moves) - availableOfficeUsers := payloads.QueueAvailableOfficeUsers(officeUsers) + queueMoves := payloads.QueueMoves(moves, officeUsers) result := &ghcmessages.QueueMovesResult{ - Page: *ListOrderParams.Page, - PerPage: *ListOrderParams.PerPage, - TotalCount: int64(count), - QueueMoves: *queueMoves, - AvailableOfficeUsers: *availableOfficeUsers, + Page: *ListOrderParams.Page, + PerPage: *ListOrderParams.PerPage, + TotalCount: int64(count), + QueueMoves: *queueMoves, } return queues.NewGetMovesQueueOK().WithPayload(result), nil @@ -308,15 +306,13 @@ func (h GetPaymentRequestsQueueHandler) Handle( } } - queuePaymentRequests := payloads.QueuePaymentRequests(paymentRequests) - availableOfficeUsers := payloads.QueueAvailableOfficeUsers(officeUsers) + queuePaymentRequests := payloads.QueuePaymentRequests(paymentRequests, officeUsers) result := &ghcmessages.QueuePaymentRequestsResult{ TotalCount: int64(count), Page: int64(*listPaymentRequestParams.Page), PerPage: int64(*listPaymentRequestParams.PerPage), QueuePaymentRequests: *queuePaymentRequests, - AvailableOfficeUsers: *availableOfficeUsers, } return queues.NewGetPaymentRequestsQueueOK().WithPayload(result), nil @@ -368,6 +364,7 @@ func (h GetServicesCounselingQueueHandler) Handle( OrderType: params.OrderType, PPMStatus: params.PpmStatus, CounselingOffice: params.CounselingOffice, + SCAssignedUser: params.AssignedTo, } if params.NeedsPPMCloseout != nil && *params.NeedsPPMCloseout { @@ -445,15 +442,13 @@ func (h GetServicesCounselingQueueHandler) Handle( } } - queueMoves := payloads.QueueMoves(moves) - availableOfficeUsers := payloads.QueueAvailableOfficeUsers(officeUsers) + queueMoves := payloads.QueueMoves(moves, officeUsers) result := &ghcmessages.QueueMovesResult{ - Page: *ListOrderParams.Page, - PerPage: *ListOrderParams.PerPage, - TotalCount: int64(count), - QueueMoves: *queueMoves, - AvailableOfficeUsers: *availableOfficeUsers, + Page: *ListOrderParams.Page, + PerPage: *ListOrderParams.PerPage, + TotalCount: int64(count), + QueueMoves: *queueMoves, } return queues.NewGetServicesCounselingQueueOK().WithPayload(result), nil diff --git a/pkg/handlers/ghcapi/queues_test.go b/pkg/handlers/ghcapi/queues_test.go index 4cc6aaaa1da..fbace4c29d8 100644 --- a/pkg/handlers/ghcapi/queues_test.go +++ b/pkg/handlers/ghcapi/queues_test.go @@ -91,8 +91,8 @@ func (suite *HandlerSuite) TestGetMoveQueuesHandler() { // Validate outgoing payload suite.NoError(payload.Validate(strfmt.Default)) - suite.Len(payload.AvailableOfficeUsers, 1) - suite.Equal(payload.AvailableOfficeUsers[0].OfficeUserID.String(), officeUser.ID.String()) + suite.Len(payload.QueueMoves[0].AvailableOfficeUsers, 1) + suite.Equal(payload.QueueMoves[0].AvailableOfficeUsers[0].OfficeUserID.String(), officeUser.ID.String()) order := hhgMove.Orders result := payload.QueueMoves[0] @@ -1066,8 +1066,8 @@ func (suite *HandlerSuite) TestGetPaymentRequestsQueueHandler() { suite.NoError(payload.Validate(strfmt.Default)) suite.Len(payload.QueuePaymentRequests, 1) - suite.Len(payload.AvailableOfficeUsers, 1) - suite.Equal(payload.AvailableOfficeUsers[0].OfficeUserID.String(), officeUser.ID.String()) + suite.Len(payload.QueuePaymentRequests[0].AvailableOfficeUsers, 1) + suite.Equal(payload.QueuePaymentRequests[0].AvailableOfficeUsers[0].OfficeUserID.String(), officeUser.ID.String()) paymentRequest := *payload.QueuePaymentRequests[0] @@ -1520,8 +1520,8 @@ func (suite *HandlerSuite) TestGetServicesCounselingQueueHandler() { result1 := payload.QueueMoves[0] result2 := payload.QueueMoves[1] - suite.Len(payload.AvailableOfficeUsers, 1) - suite.Equal(subtestData.officeUser.ID.String(), payload.AvailableOfficeUsers[0].OfficeUserID.String()) + suite.Len(payload.QueueMoves[0].AvailableOfficeUsers, 1) + suite.Equal(subtestData.officeUser.ID.String(), payload.QueueMoves[0].AvailableOfficeUsers[0].OfficeUserID.String()) suite.Len(payload.QueueMoves, 2) suite.Equal(order.ServiceMember.ID.String(), result1.Customer.ID.String()) diff --git a/pkg/handlers/internalapi/moves.go b/pkg/handlers/internalapi/moves.go index 6188b30153f..a0893609bd1 100644 --- a/pkg/handlers/internalapi/moves.go +++ b/pkg/handlers/internalapi/moves.go @@ -70,6 +70,9 @@ func payloadForMoveModel(storer storage.FileStorer, order models.Order, move mod if move.PrimeCounselingCompletedAt != nil { movePayload.PrimeCounselingCompletedAt = *handlers.FmtDateTime(*move.PrimeCounselingCompletedAt) } + if move.CounselingOffice != nil { + movePayload.CounselingOffice = payloads.TransportationOffice(*move.CounselingOffice) + } return movePayload, nil } @@ -118,6 +121,10 @@ func payloadForInternalMove(storer storage.FileStorer, list models.Moves) []*int currentMove.PrimeCounselingCompletedAt = *handlers.FmtDateTime(*move.PrimeCounselingCompletedAt) } + if move.CounselingOffice != nil { + currentMove.CounselingOffice = payloads.TransportationOffice(*move.CounselingOffice) + } + convertedCurrentMovesList = append(convertedCurrentMovesList, currentMove) } return convertedCurrentMovesList diff --git a/pkg/handlers/internalapi/orders.go b/pkg/handlers/internalapi/orders.go index 00fe1c97f96..101e7950fd0 100644 --- a/pkg/handlers/internalapi/orders.go +++ b/pkg/handlers/internalapi/orders.go @@ -340,6 +340,31 @@ func (h UpdateOrdersHandler) Handle(params ordersop.UpdateOrdersParams) middlewa } order.OriginDutyLocation = &originDutyLocation order.OriginDutyLocationID = &originDutyLocationID + + if payload.MoveID != "" { + + moveID, err := uuid.FromString(payload.MoveID.String()) + if err != nil { + return handlers.ResponseForError(appCtx.Logger(), err), err + } + move, err := models.FetchMove(appCtx.DB(), appCtx.Session(), moveID) + if err != nil { + return handlers.ResponseForError(appCtx.Logger(), err), err + } + if originDutyLocation.ProvidesServicesCounseling { + counselingOfficeID, err := uuid.FromString(payload.CounselingOfficeID.String()) + if err != nil { + return handlers.ResponseForError(appCtx.Logger(), err), err + } + move.CounselingOfficeID = &counselingOfficeID + } else { + move.CounselingOfficeID = nil + } + verrs, err := models.SaveMoveDependencies(appCtx.DB(), move) + if err != nil || verrs.HasAny() { + return handlers.ResponseForError(appCtx.Logger(), err), err + } + } } if payload.OrdersType == nil { diff --git a/pkg/handlers/internalapi/orders_test.go b/pkg/handlers/internalapi/orders_test.go index 81528bda62a..3f9a9bfc730 100644 --- a/pkg/handlers/internalapi/orders_test.go +++ b/pkg/handlers/internalapi/orders_test.go @@ -443,8 +443,13 @@ func (suite *HandlerSuite) TestUploadAmendedOrdersHandlerIntegration() { } func (suite *HandlerSuite) TestUpdateOrdersHandler() { - dutyLocation := factory.BuildDutyLocation(suite.DB(), nil, nil) - + dutyLocation := factory.BuildDutyLocation(suite.DB(), []factory.Customization{ + { + Model: models.DutyLocation{ + ProvidesServicesCounseling: false, + }, + }, + }, nil) order := factory.BuildOrder(suite.DB(), []factory.Customization{ { Model: dutyLocation, @@ -452,6 +457,11 @@ func (suite *HandlerSuite) TestUpdateOrdersHandler() { Type: &factory.DutyLocations.OriginDutyLocation, }, }, nil) + move := factory.BuildMove(suite.DB(), []factory.Customization{ + { + Model: order, + LinkOnly: true, + }}, nil) newDutyLocation := factory.BuildDutyLocation(suite.DB(), nil, nil) newTransportationOffice := factory.BuildTransportationOffice(suite.DB(), nil, nil) @@ -474,6 +484,8 @@ func (suite *HandlerSuite) TestUpdateOrdersHandler() { HasDependents: handlers.FmtBool(false), SpouseHasProGear: handlers.FmtBool(false), Grade: models.ServiceMemberGradeE4.Pointer(), + MoveID: *handlers.FmtUUID(move.ID), + CounselingOfficeID: handlers.FmtUUID(*newDutyLocation.TransportationOfficeID), } path := fmt.Sprintf("/orders/%v", order.ID.String()) @@ -521,3 +533,76 @@ func (suite *HandlerSuite) TestUpdateOrdersHandler() { suite.Equal(expectedOriginalOrderAuthorizedWeight, 5000) // The order was created as an E1. Ensure that the E1 authorized weight is 5000. suite.Equal(string(newOrdersType), string(updatedOrder.OrdersType)) } + +func (suite *HandlerSuite) TestUpdateOrdersHandlerWithCounselingOffice() { + originDutyLocation := factory.BuildDutyLocation(suite.DB(), []factory.Customization{ + { + Model: models.DutyLocation{ + ProvidesServicesCounseling: true, + }, + }, + }, nil) + + order := factory.BuildOrder(suite.DB(), []factory.Customization{ + { + Model: originDutyLocation, + LinkOnly: true, + Type: &factory.DutyLocations.OriginDutyLocation, + }, + }, nil) + + newDutyLocation := factory.BuildDutyLocation(suite.DB(), nil, nil) + newTransportationOffice := factory.BuildTransportationOffice(suite.DB(), nil, nil) + newDutyLocation.TransportationOffice = newTransportationOffice + + 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.DeptIndicatorAIRANDSPACEFORCE + move := factory.BuildMove(suite.DB(), []factory.Customization{ + { + Model: order, + LinkOnly: true, + }}, nil) + 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.ServiceMemberGradeE4.Pointer(), + MoveID: *handlers.FmtUUID(move.ID), + CounselingOfficeID: handlers.FmtUUID(*newDutyLocation.TransportationOfficeID), + } + + 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)) + suite.Equal(string(newOrdersType), string(*okResponse.Payload.OrdersType)) + suite.Equal(newOrdersNumber, *okResponse.Payload.OrdersNumber) + +} diff --git a/pkg/models/move.go b/pkg/models/move.go index 249776cff40..4fdbf959247 100644 --- a/pkg/models/move.go +++ b/pkg/models/move.go @@ -141,6 +141,7 @@ func FetchMove(db *pop.Connection, session *auth.Session, id uuid.UUID) (*Move, "LockedByOfficeUser", "AdditionalDocuments", "AdditionalDocuments.UserUploads", + "CounselingOffice", ).Where("show = TRUE").Find(&move, id) if err != nil { @@ -487,6 +488,7 @@ func FetchMovesByOrderID(db *pop.Connection, orderID uuid.UUID) (Moves, error) { "Orders.NewDutyLocation.TransportationOffice.Address", "CloseoutOffice", "CloseoutOffice.Address", + "CounselingOffice", ).All(&moves) if err != nil { return moves, err diff --git a/pkg/services/move/move_router.go b/pkg/services/move/move_router.go index 771ed557fa1..c510023e5a7 100644 --- a/pkg/services/move/move_router.go +++ b/pkg/services/move/move_router.go @@ -207,8 +207,10 @@ func (router moveRouter) sendToServiceCounselor(appCtx appcontext.AppContext, mo return apperror.NewInvalidInputError(move.MTOShipments[i].PPMShipment.ID, err, verrs, msg) } } - // update status for boat shipment - if move.MTOShipments[i].ShipmentType == models.MTOShipmentTypeBoatHaulAway || move.MTOShipments[i].ShipmentType == models.MTOShipmentTypeBoatTowAway { + // update status for boat or mobile home shipment + if move.MTOShipments[i].ShipmentType == models.MTOShipmentTypeBoatHaulAway || + move.MTOShipments[i].ShipmentType == models.MTOShipmentTypeBoatTowAway || + move.MTOShipments[i].ShipmentType == models.MTOShipmentTypeMobileHome { move.MTOShipments[i].Status = models.MTOShipmentStatusSubmitted if verrs, err := appCtx.DB().ValidateAndUpdate(&move.MTOShipments[i]); verrs.HasAny() || err != nil { diff --git a/pkg/services/mto_shipment/mto_shipment_creator.go b/pkg/services/mto_shipment/mto_shipment_creator.go index 7592341c3f1..4fee6272468 100644 --- a/pkg/services/mto_shipment/mto_shipment_creator.go +++ b/pkg/services/mto_shipment/mto_shipment_creator.go @@ -157,7 +157,7 @@ func (f mtoShipmentCreator) CreateMTOShipment(appCtx appcontext.AppContext, ship // Populate the destination address fields with the new duty location's address when // we have an HHG or Boat with no destination address, but don't copy over any street fields. - if (shipment.ShipmentType == models.MTOShipmentTypeHHG || isBoatShipment) && shipment.DestinationAddress == nil { + if (shipment.ShipmentType == models.MTOShipmentTypeHHG || isBoatShipment || isMobileHomeShipment) && shipment.DestinationAddress == nil { err = appCtx.DB().Load(&move, "Orders.NewDutyLocation.Address") if err != nil { return nil, apperror.NewQueryError("Orders", err, "") diff --git a/pkg/services/order.go b/pkg/services/order.go index 7a133fe0bae..5ecac664a7a 100644 --- a/pkg/services/order.go +++ b/pkg/services/order.go @@ -69,4 +69,5 @@ type ListOrderParams struct { PPMStatus *string ViewAsGBLOC *string CounselingOffice *string + SCAssignedUser *string } diff --git a/pkg/services/order/order_fetcher.go b/pkg/services/order/order_fetcher.go index 3b1489d2f6f..cc1277ba247 100644 --- a/pkg/services/order/order_fetcher.go +++ b/pkg/services/order/order_fetcher.go @@ -116,10 +116,12 @@ func (f orderFetcher) ListOrders(appCtx appcontext.AppContext, officeUserID uuid closeoutLocationQuery := closeoutLocationFilter(params.CloseoutLocation, ppmCloseoutGblocs) ppmTypeQuery := ppmTypeFilter(params.PPMType) ppmStatusQuery := ppmStatusFilter(params.PPMStatus) + SCAssignedUserQuery := SCAssignedUserFilter(params.SCAssignedUser) sortOrderQuery := sortOrder(params.Sort, params.Order, ppmCloseoutGblocs) counselingQuery := counselingOfficeFilter(params.CounselingOffice) // Adding to an array so we can iterate over them and apply the filters after the query structure is set below - options := [18]QueryOption{branchQuery, locatorQuery, dodIDQuery, emplidQuery, lastNameQuery, originDutyLocationQuery, destinationDutyLocationQuery, moveStatusQuery, gblocQuery, submittedAtQuery, appearedInTOOAtQuery, requestedMoveDateQuery, ppmTypeQuery, closeoutInitiatedQuery, closeoutLocationQuery, ppmStatusQuery, sortOrderQuery, counselingQuery} + options := [19]QueryOption{branchQuery, locatorQuery, dodIDQuery, emplidQuery, lastNameQuery, originDutyLocationQuery, destinationDutyLocationQuery, moveStatusQuery, gblocQuery, submittedAtQuery, appearedInTOOAtQuery, requestedMoveDateQuery, ppmTypeQuery, closeoutInitiatedQuery, closeoutLocationQuery, ppmStatusQuery, sortOrderQuery, SCAssignedUserQuery, counselingQuery} + var query *pop.Query if ppmCloseoutGblocs { query = appCtx.DB().Q().Scope(utilities.ExcludeDeletedScope(models.MTOShipment{})).EagerPreload( @@ -157,8 +159,8 @@ func (f orderFetcher) ListOrders(appCtx appcontext.AppContext, officeUserID uuid "MTOShipments.PPMShipment", "CloseoutOffice", "LockedByOfficeUser", - "SCAssignedUser", "CounselingOffice", + "SCAssignedUser", ).InnerJoin("orders", "orders.id = moves.orders_id"). InnerJoin("service_members", "orders.service_member_id = service_members.id"). InnerJoin("mto_shipments", "moves.id = mto_shipments.move_id"). @@ -171,12 +173,12 @@ func (f orderFetcher) ListOrders(appCtx appcontext.AppContext, officeUserID uuid LeftJoin("duty_locations as dest_dl", "dest_dl.id = orders.new_duty_location_id"). LeftJoin("office_users", "office_users.id = moves.locked_by"). LeftJoin("transportation_offices", "moves.counseling_transportation_office_id = transportation_offices.id"). + LeftJoin("office_users as assigned_user", "moves.sc_assigned_id = assigned_user.id"). Where("show = ?", models.BoolPointer(true)) if !privileges.HasPrivilege(models.PrivilegeTypeSafety) { query.Where("orders.orders_type != (?)", "SAFETY") } - if params.NeedsPPMCloseout != nil { if *params.NeedsPPMCloseout { query.InnerJoin("ppm_shipments", "ppm_shipments.shipment_id = mto_shipments.id"). @@ -236,6 +238,9 @@ func (f orderFetcher) ListOrders(appCtx appcontext.AppContext, officeUserID uuid if params.Sort != nil && *params.Sort == "counselingOffice" { groupByColumms = append(groupByColumms, "transportation_offices.id") } + if params.Sort != nil && *params.Sort == "assignedTo" { + groupByColumms = append(groupByColumms, "assigned_user.last_name", "assigned_user.first_name") + } err = query.GroupBy("moves.id", groupByColumms...).Paginate(int(*params.Page), int(*params.PerPage)).All(&moves) if err != nil { @@ -621,6 +626,13 @@ func ppmStatusFilter(ppmStatus *string) QueryOption { } } } +func SCAssignedUserFilter(scAssigned *string) QueryOption { + return func(query *pop.Query) { + if scAssigned != nil { + query.Where("f_unaccent(lower(?)) % searchable_full_name(assigned_user.first_name, assigned_user.last_name)", *scAssigned) + } + } +} func closeoutLocationFilter(closeoutLocation *string, ppmCloseoutGblocs bool) QueryOption { return func(query *pop.Query) { @@ -703,6 +715,7 @@ func sortOrder(sort *string, order *string, ppmCloseoutGblocs bool) QueryOption "closeoutLocation": "closeout_to.name", "closeoutInitiated": "MAX(ppm_shipments.submitted_at)", "counselingOffice": "transportation_offices.name", + "assignedTo": "assigned_user.last_name,assigned_user.first_name", } return func(query *pop.Query) { @@ -716,6 +729,8 @@ func sortOrder(sort *string, order *string, ppmCloseoutGblocs bool) QueryOption if sortTerm, ok := parameters[*sort]; ok { if *sort == "lastName" { query.Order(fmt.Sprintf("service_members.last_name %s, service_members.first_name %s", *order, *order)) + } else if *sort == "assignedTo" { + query.Order(fmt.Sprintf("assigned_user.last_name %s, assigned_user.first_name %s", *order, *order)) } else { query.Order(fmt.Sprintf("%s %s", sortTerm, *order)) } diff --git a/pkg/services/order/order_fetcher_test.go b/pkg/services/order/order_fetcher_test.go index f6956d7fd87..20b9fa04166 100644 --- a/pkg/services/order/order_fetcher_test.go +++ b/pkg/services/order/order_fetcher_test.go @@ -1,6 +1,7 @@ package order import ( + "fmt" "time" "github.com/gofrs/uuid" @@ -10,6 +11,7 @@ import ( "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/models/roles" "github.com/transcom/mymove/pkg/services" + moveservice "github.com/transcom/mymove/pkg/services/move" "github.com/transcom/mymove/pkg/testdatagen" ) @@ -74,6 +76,7 @@ func (suite *OrderServiceSuite) TestListOrders() { agfmPostalCode := "06001" setupTestData := func() (models.OfficeUser, models.Move, auth.Session) { + // Make an office user → GBLOC X officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) session := auth.Session{ @@ -575,7 +578,41 @@ func (suite *OrderServiceSuite) TestListOrders() { suite.Equal(createdPPM.Shipment.MoveTaskOrder.Locator, moves[0].Locator) }) } +func (suite *OrderServiceSuite) TestListOrderWithAssignedUserSingle() { + // Under test: ListOrders + // Set up: Make a move, assign one to an SC office user + // Expected outcome: Only the one move with the assigned user should be returned + assignedOfficeUserUpdater := moveservice.NewAssignedOfficeUserUpdater(moveservice.NewMoveFetcher()) + scUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeServicesCounselor}) + var orderFetcherTest orderFetcher + session := auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: scUser.User.Roles, + OfficeUserID: scUser.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } + + appCtx := suite.AppContextWithSessionForTest(&session) + createdMove := factory.BuildMoveWithShipment(suite.DB(), nil, nil) + createdMove.SCAssignedID = &scUser.ID + createdMove.SCAssignedUser = &scUser + _, updateError := assignedOfficeUserUpdater.UpdateAssignedOfficeUser(appCtx, createdMove.ID, &scUser, roles.RoleTypeServicesCounselor) + + searchString := fmt.Sprintf("%s, %s", scUser.LastName, scUser.FirstName) + moves, _, err := orderFetcherTest.ListOrders(suite.AppContextWithSessionForTest(&session), scUser.ID, &services.ListOrderParams{ + SCAssignedUser: &searchString, + }) + + suite.FatalNoError(err) + suite.FatalNoError(updateError) + suite.Equal(1, len(moves)) + suite.Equal(moves[0].SCAssignedID, createdMove.SCAssignedID) + suite.Equal(createdMove.SCAssignedUser.ID, moves[0].SCAssignedUser.ID) + suite.Equal(createdMove.SCAssignedUser.FirstName, moves[0].SCAssignedUser.FirstName) + suite.Equal(createdMove.SCAssignedUser.LastName, moves[0].SCAssignedUser.LastName) +} func (suite *OrderServiceSuite) TestListOrdersUSMCGBLOC() { orderFetcher := NewOrderFetcher() diff --git a/playwright/tests/my/mymove/mobileHome.spec.js b/playwright/tests/my/mymove/mobileHome.spec.js new file mode 100644 index 00000000000..e87642c224d --- /dev/null +++ b/playwright/tests/my/mymove/mobileHome.spec.js @@ -0,0 +1,141 @@ +import { test, expect } from '../../utils/my/customerTest'; + +const multiMoveEnabled = process.env.FEATURE_FLAG_MULTI_MOVE; + +test.describe('Mobile Home shipment', () => { + test.skip(multiMoveEnabled === 'true', 'Skip if MultiMove workflow is enabled.'); + + test('A customer can create a Mobile Home shipment', async ({ page, customerPage }) => { + // Generate a new onboarded user with orders and log in + const move = await customerPage.testHarness.buildMoveWithOrders(); + const userId = move.Orders.ServiceMember.user_id; + await customerPage.signInAsExistingCustomer(userId); + + // Navigate to create a new shipment + await customerPage.waitForPage.home(); + await page.getByTestId('shipment-selection-btn').click(); + await customerPage.waitForPage.aboutShipments(); + await customerPage.navigateForward(); + await customerPage.waitForPage.selectShipmentType(); + + // Create an Mobile Home shipment + await page.getByText('Move a Mobile Home').click(); + await customerPage.navigateForward(); + + // Fill in form to create Mobile Home shipment + await customerPage.waitForPage.mobileHomeShipment(); + await page.getByLabel('Year').fill('2022'); + await page.getByLabel('Make').fill('make'); + await page.getByLabel('Model').fill('model'); + await page.getByTestId('lengthFeet').fill('22'); + await page.getByTestId('widthFeet').fill('22'); + await page.getByTestId('heightFeet').fill('22'); + await page.getByRole('button', { name: 'Continue' }).click(); + + await expect(page.getByTestId('tag')).toContainText('Mobile Home'); + + await expect(page.getByText('Pickup info')).toBeVisible(); + await page.getByLabel('Preferred pickup date').fill('25 Dec 2022'); + await page.getByLabel('Preferred pickup date').blur(); + await page.getByText('Use my current address').click(); + await page.getByLabel('Preferred delivery date').fill('25 Dec 2022'); + await page.getByLabel('Preferred delivery date').blur(); + await page.getByRole('button', { name: 'Save & Continue' }).click(); + await customerPage.waitForPage.reviewShipments(); + }); +}); + +test.describe('(MultiMove) Mobile Home shipment', () => { + test.skip(multiMoveEnabled === 'false', 'Skip if MultiMove workflow is not enabled.'); + + test('A customer can create a Mobile Home shipment', async ({ page, customerPage }) => { + // Generate a new onboarded user with orders and log in + const move = await customerPage.testHarness.buildMoveWithOrders(); + const userId = move.Orders.ServiceMember.user_id; + await customerPage.signInAsExistingCustomer(userId); + + // Navigate from MM Dashboard to Move + await customerPage.navigateFromMMDashboardToMove(move); + + // Navigate to create a new shipment + await customerPage.waitForPage.home(); + await page.getByTestId('shipment-selection-btn').click(); + await customerPage.waitForPage.aboutShipments(); + await customerPage.navigateForward(); + await customerPage.waitForPage.selectShipmentType(); + + // Create an Mobile Home shipment + await page.getByText('Move a mobile home').click(); + await customerPage.navigateForward(); + + // Fill in form to create Mobile Home shipment + await customerPage.waitForPage.mobileHomeShipment(); + await page.getByLabel('Year').fill('2022'); + await page.getByLabel('Make').fill('make'); + await page.getByLabel('Model').fill('model'); + await page.getByTestId('lengthFeet').fill('22'); + await page.getByTestId('widthFeet').fill('22'); + await page.getByTestId('heightFeet').fill('22'); + await page.getByRole('button', { name: 'Continue' }).click(); + + await expect(page.getByTestId('tag')).toContainText('Mobile Home'); + + await expect(page.getByText('Pickup info')).toBeVisible(); + await page.getByLabel('Preferred pickup date').fill('25 Dec 2022'); + await page.getByLabel('Preferred pickup date').blur(); + await page.getByText('Use my current address').click(); + await page.getByLabel('Preferred delivery date').fill('25 Dec 2022'); + await page.getByLabel('Preferred delivery date').blur(); + await page.getByRole('button', { name: 'Save & Continue' }).click(); + await customerPage.waitForPage.reviewShipments(); + }); + + test('Is able to delete a Mobile Home shipment', async ({ page, customerPage }) => { + // Generate a new onboarded user with orders and log in + const move = await customerPage.testHarness.buildMoveWithOrders(); + const userId = move.Orders.ServiceMember.user_id; + await customerPage.signInAsExistingCustomer(userId); + + // Navigate from MM Dashboard to Move + await customerPage.navigateFromMMDashboardToMove(move); + + // Navigate to create a new shipment + await customerPage.waitForPage.home(); + await page.getByTestId('shipment-selection-btn').click(); + await customerPage.waitForPage.aboutShipments(); + await customerPage.navigateForward(); + await customerPage.waitForPage.selectShipmentType(); + + // Create an Mobile Home shipment + await page.getByText('Move a mobile home').click(); + await customerPage.navigateForward(); + + // Fill in form to create Mobile Home shipment + await customerPage.waitForPage.mobileHomeShipment(); + await page.getByLabel('Year').fill('2022'); + await page.getByLabel('Make').fill('make'); + await page.getByLabel('Model').fill('model'); + await page.getByTestId('lengthFeet').fill('22'); + await page.getByTestId('widthFeet').fill('22'); + await page.getByTestId('heightFeet').fill('22'); + await page.getByRole('button', { name: 'Continue' }).click(); + + await expect(page.getByTestId('tag')).toContainText('Mobile Home'); + + await expect(page.getByText('Pickup info')).toBeVisible(); + await page.getByLabel('Preferred pickup date').fill('25 Dec 2022'); + await page.getByLabel('Preferred pickup date').blur(); + await page.getByText('Use my current address').click(); + await page.getByLabel('Preferred delivery date').fill('25 Dec 2022'); + await page.getByLabel('Preferred delivery date').blur(); + await page.getByRole('button', { name: 'Save & Continue' }).click(); + await customerPage.waitForPage.reviewShipments(); + + await expect(page.getByRole('heading', { name: 'Mobile Home 1' })).toBeVisible(); + await page.getByTestId('deleteShipmentButton').click(); + await expect(page.getByRole('heading', { name: 'Delete this?' })).toBeVisible(); + await page.getByText('Yes, Delete').click(); + + await expect(page.getByRole('heading', { name: 'Mobile Home 1' })).not.toBeVisible(); + }); +}); diff --git a/playwright/tests/utils/my/waitForCustomerPage.js b/playwright/tests/utils/my/waitForCustomerPage.js index 18d76ac5d95..20fa227d9b0 100644 --- a/playwright/tests/utils/my/waitForCustomerPage.js +++ b/playwright/tests/utils/my/waitForCustomerPage.js @@ -193,6 +193,15 @@ export class WaitForCustomerPage extends WaitForPage { await this.runAccessibilityAudit(); } + /** + * @returns {Promise} + */ + async mobileHomeShipment() { + await this.runAccessibilityAudit(); + await base.expect(this.page.getByRole('heading', { level: 1 })).toHaveText('Mobile Home details and measurements'); + await this.runAccessibilityAudit(); + } + /** * @returns {Promise} */ diff --git a/src/components/Customer/EditOrdersForm/EditOrdersForm.jsx b/src/components/Customer/EditOrdersForm/EditOrdersForm.jsx index e5881f15fda..bcbf1a86e3f 100644 --- a/src/components/Customer/EditOrdersForm/EditOrdersForm.jsx +++ b/src/components/Customer/EditOrdersForm/EditOrdersForm.jsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import PropTypes from 'prop-types'; import { Formik, Field } from 'formik'; import * as Yup from 'yup'; @@ -20,6 +20,7 @@ import WizardNavigation from 'components/Customer/WizardNavigation/WizardNavigat import Callout from 'components/Callout'; import { formatLabelReportByDate, dropdownInputOptions } from 'utils/formatters'; import formStyles from 'styles/form.module.scss'; +import { showCounselingOffices } from 'services/internalApi'; const EditOrdersForm = ({ createUpload, @@ -31,6 +32,8 @@ const EditOrdersForm = ({ ordersTypeOptions, onCancel, }) => { + const [officeOptions, setOfficeOptions] = useState(null); + const [dutyLocation, setDutyLocation] = useState(initialValues.origin_duty_location); const validationSchema = Yup.object().shape({ orders_type: Yup.mixed() .oneOf(ordersTypeOptions.map((i) => i.key)) @@ -56,6 +59,9 @@ const EditOrdersForm = ({ .min(1), grade: Yup.mixed().oneOf(Object.keys(ORDERS_PAY_GRADE_OPTIONS)).required('Required'), origin_duty_location: Yup.object().nullable().required('Required'), + counseling_office_id: dutyLocation?.provides_services_counseling + ? Yup.string().required('Required') + : Yup.string().notRequired(), }); const enableDelete = () => { @@ -68,6 +74,18 @@ const EditOrdersForm = ({ let originMeta; let newDutyMeta = ''; + useEffect(() => { + showCounselingOffices(dutyLocation?.id).then((fetchedData) => { + if (fetchedData.body) { + const counselingOffices = fetchedData.body.map((item) => ({ + key: item.id, + value: item.name, + })); + setOfficeOptions(counselingOffices); + } + }); + }, [dutyLocation]); + return ( { + setDutyLocation(e); + }} required metaOverride={originMeta} /> - + {dutyLocation?.provides_services_counseling && ( +
+ + +
+ )} {isRetirementOrSeparation ? ( <>

Where are you entitled to move?

diff --git a/src/components/Customer/EditOrdersForm/EditOrdersForm.stories.jsx b/src/components/Customer/EditOrdersForm/EditOrdersForm.stories.jsx index 2b7d6117470..1a5d075f2ba 100644 --- a/src/components/Customer/EditOrdersForm/EditOrdersForm.stories.jsx +++ b/src/components/Customer/EditOrdersForm/EditOrdersForm.stories.jsx @@ -25,6 +25,24 @@ const testInitialValues = { name: 'Yuma AFB', updated_at: '2020-10-19T17:01:16.114Z', }, + origin_duty_location: { + address: { + city: 'Des Moines', + country: 'US', + id: 'a4b30b99-4e82-48a6-b736-01662b499d6a', + postalCode: '50309', + state: 'IA', + streetAddress1: '987 Other Avenue', + streetAddress2: 'P.O. Box 1234', + streetAddress3: 'c/o Another Person', + }, + address_id: 'a4b30b99-4e82-48a6-b736-01662b499d6a', + affiliation: 'AIR_FORCE', + created_at: '2020-10-19T17:01:16.114Z', + id: 'f9299768-16d2-4a13-ae39-7087a58b1f62', + name: 'Yuma AFB', + updated_at: '2020-10-19T17:01:16.114Z', + }, grade: 'E_1', uploaded_orders: [ { @@ -57,6 +75,7 @@ const testProps = { report_by_date: '', has_dependents: '', new_duty_location: {}, + origin_duty_location: {}, grade: '', uploaded_orders: [ { @@ -81,6 +100,7 @@ const testProps = { export const EmptyValues = (argTypes) => ( ({ + ...jest.requireActual('services/internalApi'), + showCounselingOffices: jest.fn().mockImplementation(() => + Promise.resolve({ + body: [ + { + id: '3e937c1f-5539-4919-954d-017989130584', + name: 'Albuquerque AFB', + }, + { + id: 'fa51dab0-4553-4732-b843-1f33407f77bc', + name: 'Glendale Luke AFB', + }, + ], + }), + ), +})); jest.mock('components/LocationSearchBox/api', () => ({ ShowAddress: jest.fn().mockImplementation(() => Promise.resolve({ @@ -138,6 +156,9 @@ const testProps = { has_dependents: '', new_duty_location: {}, uploaded_orders: [], + origin_duty_location: { + provides_services_counseling: true, + }, }, onCancel: jest.fn(), onUploadComplete: jest.fn(), @@ -160,6 +181,7 @@ const initialValues = { report_by_date: '2020-11-26', has_dependents: 'No', origin_duty_location: { + provides_services_counseling: true, address: { city: 'Des Moines', country: 'US', @@ -229,6 +251,8 @@ describe('EditOrdersForm component', () => { }); it('rendering the upload area', async () => { + showCounselingOffices.mockImplementation(() => Promise.resolve({})); + render(); expect(await screen.findByText(documentSizeLimitMsg)).toBeInTheDocument(); @@ -261,6 +285,9 @@ describe('EditOrdersForm component', () => { {...testProps} currentDutyLocation={{ name: 'Luke AFB' }} initialValues={{ + origin_duty_location: { + provides_services_counseling: true, + }, uploaded_orders: [ { id: '123', @@ -332,6 +359,9 @@ describe('EditOrdersForm component', () => { { name: 'Yuma AFB', updated_at: '2020-10-19T17:01:16.114Z', }, + origin_duty_location: { + provides_services_counseling: true, + }, uploaded_orders: [ { id: '123', diff --git a/src/components/Customer/MobileHomeShipment/MobileHomeShipmentForm/MobileHomeShipmentForm.jsx b/src/components/Customer/MobileHomeShipment/MobileHomeShipmentForm/MobileHomeShipmentForm.jsx index 4972d74b387..51364fc0d3a 100644 --- a/src/components/Customer/MobileHomeShipment/MobileHomeShipmentForm/MobileHomeShipmentForm.jsx +++ b/src/components/Customer/MobileHomeShipment/MobileHomeShipmentForm/MobileHomeShipmentForm.jsx @@ -110,7 +110,7 @@ const MobileHomeShipmentForm = ({ mtoShipment, onBack, onSubmit }) => {
-

Mobile home Information

+

Mobile Home Information

{

Mobile Home Dimensions

-

Enter all of the dimensions of the mobile home.

+

Enter the total outside dimensions (in Feet and Inches) of the Mobile Home.

@@ -268,14 +268,12 @@ const MobileHomeShipmentForm = ({ mtoShipment, onBack, onSubmit }) => { - Examples + Example
  • - Dimensions of the mobile home on the trailer are significantly different than one would expect - given their individual dimensions + Is there additional information you feel is pertinent to the processing of your mobile home + shipment?(e.g., ‘wrecker service requested’ and ‘crane service needed’).
  • - -
  • Access info for your origin or destination address/marina
diff --git a/src/components/Customer/MtoShipmentForm/MtoShipmentForm.jsx b/src/components/Customer/MtoShipmentForm/MtoShipmentForm.jsx index 20c62106ef0..efb155918d2 100644 --- a/src/components/Customer/MtoShipmentForm/MtoShipmentForm.jsx +++ b/src/components/Customer/MtoShipmentForm/MtoShipmentForm.jsx @@ -628,7 +628,7 @@ class MtoShipmentForm extends Component { )} - {!isBoat && ( + {!isBoat && !isMobileHome && (
Remarks
}>
+
SIT Departure Date
SIT Requested Delivery
- SIT Customer Contacted + SIT Customer Contacted
+ Update Reason

@@ -68,6 +70,16 @@ const PrimeUIUpdateOriginSITForm = ({ initialValues, onSubmit, serviceItem }) =>
+
{ const [errorMessage, setErrorMessage] = useState(); @@ -80,8 +81,17 @@ const PrimeUIUpdateSitServiceItem = ({ setFlashMessage }) => { // sending the data submitted in the form to the API // if any of the dates are skipped or not filled with values, we'll just make them null const onSubmit = (values) => { - const { sitCustomerContacted, sitDepartureDate, sitRequestedDelivery, mtoServiceItemID, reServiceCode, eTag } = - values; + const { + sitCustomerContacted, + sitDepartureDate, + sitRequestedDelivery, + updateReason, + mtoServiceItemID, + reServiceCode, + eTag, + } = values; + + const requestApprovalsRequestedStatus = serviceItem?.status === SERVICE_ITEM_STATUSES.REJECTED; const body = { sitDepartureDate: sitDepartureDate === 'Invalid date' ? null : formatDateForSwagger(sitDepartureDate), @@ -89,6 +99,8 @@ const PrimeUIUpdateSitServiceItem = ({ setFlashMessage }) => { sitCustomerContacted: sitCustomerContacted === 'Invalid date' ? null : formatDateForSwagger(sitCustomerContacted), reServiceCode, modelType: 'UpdateMTOServiceItemSIT', + updateReason, + requestApprovalsRequestedStatus, }; createUpdateSITServiceItemRequestMutation({ mtoServiceItemID, eTag, body }); diff --git a/src/pages/PrimeUI/UpdateServiceItems/PrimeUpdateSitServiceItem.test.jsx b/src/pages/PrimeUI/UpdateServiceItems/PrimeUpdateSitServiceItem.test.jsx index bbd211134e3..bc15daf7363 100644 --- a/src/pages/PrimeUI/UpdateServiceItems/PrimeUpdateSitServiceItem.test.jsx +++ b/src/pages/PrimeUI/UpdateServiceItems/PrimeUpdateSitServiceItem.test.jsx @@ -71,6 +71,7 @@ describe('PrimeUIUpdateSitServiceItems page', () => { expect(screen.getByRole('textbox', { name: 'SIT Departure Date' })).toBeInTheDocument(); expect(screen.getByRole('textbox', { name: 'SIT Requested Delivery' })).toBeInTheDocument(); expect(screen.getByRole('textbox', { name: 'SIT Customer Contacted' })).toBeInTheDocument(); + expect(screen.getByRole('textbox', { name: 'Update Reason' })).toBeInTheDocument(); }); it('renders the origin sit service item form', async () => { @@ -131,5 +132,6 @@ describe('PrimeUIUpdateSitServiceItems page', () => { expect(screen.getByRole('textbox', { name: 'SIT Departure Date' })).toBeInTheDocument(); expect(screen.getByRole('textbox', { name: 'SIT Requested Delivery' })).toBeInTheDocument(); expect(screen.getByRole('textbox', { name: 'SIT Customer Contacted' })).toBeInTheDocument(); + expect(screen.getByRole('textbox', { name: 'Update Reason' })).toBeInTheDocument(); }); }); diff --git a/src/services/ghcApi.js b/src/services/ghcApi.js index 29f1d4aba92..f3476b7c253 100644 --- a/src/services/ghcApi.js +++ b/src/services/ghcApi.js @@ -844,20 +844,6 @@ export async function patchPPMSIT({ ppmShipmentId, payload, eTag }) { ); } -export async function updateAssignedOfficeUserForMove({ moveID, officeUserId, roleType }) { - return makeGHCRequest('move.updateAssignedOfficeUser', { - moveID, - body: { officeUserId, roleType }, - }); -} - -export async function deleteAssignedOfficeUserForMove({ moveID, roleType }) { - return makeGHCRequest('move.deleteAssignedOfficeUser', { - moveID, - body: { roleType }, - }); -} - export async function bulkDownloadPaymentRequest(paymentRequestID) { return makeGHCRequestRaw('paymentRequests.bulkDownload', { paymentRequestID }); } @@ -872,3 +858,17 @@ export async function dateSelectionIsWeekendHoliday(countryCode, date) { { normalize: false }, ); } + +export async function updateAssignedOfficeUserForMove({ moveID, officeUserId, roleType }) { + return makeGHCRequest('move.updateAssignedOfficeUser', { + moveID, + body: { officeUserId, roleType }, + }); +} + +export async function deleteAssignedOfficeUserForMove({ moveID, roleType }) { + return makeGHCRequest('move.deleteAssignedOfficeUser', { + moveID, + body: { roleType }, + }); +} diff --git a/src/shared/ToolTip/ToolTip.jsx b/src/shared/ToolTip/ToolTip.jsx index 5f613fa13d8..4ec66b3ecd4 100644 --- a/src/shared/ToolTip/ToolTip.jsx +++ b/src/shared/ToolTip/ToolTip.jsx @@ -2,7 +2,7 @@ import React, { useEffect, useRef, useState } from 'react'; import styles from './ToolTip.module.scss'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -const ToolTip = ({ text, position, icon, color, closeOnLeave }) => { +const ToolTip = ({ text, position, icon, color, closeOnLeave, title }) => { // this state determines if the text is visible on mousehover/leave const [isVisible, setIsVisible] = useState(false); const tooltipRef = useRef(null); @@ -54,7 +54,12 @@ const ToolTip = ({ text, position, icon, color, closeOnLeave }) => { ref={tooltipRef} > - {isVisible &&
{text}
} + {isVisible && ( +
+ {title &&
{title}
} +
{text}
+
+ )}
); }; diff --git a/src/shared/ToolTip/ToolTip.module.scss b/src/shared/ToolTip/ToolTip.module.scss index ba6dbfafbf7..b494cc4c9e5 100644 --- a/src/shared/ToolTip/ToolTip.module.scss +++ b/src/shared/ToolTip/ToolTip.module.scss @@ -7,39 +7,141 @@ .tooltipTextLeft, .tooltipTextBottom { position: absolute; - background-color: rgba($color: #000000, $alpha: 0.8); - color: white; - padding: 10px; + background-color: white; + border: 1px solid rgba(0, 0, 0, 0.2); + color: #333; + padding: 0; border-radius: 4px; - width: 200px; - text-align: center; - white-space: pre-wrap; - overflow: auto; - height: 80px; + width: 220px; z-index: 1; - text-align: center; + box-shadow: 0px 5px 10px rgba(0, 0, 0, 0.1); + text-align: left; + display: flex; + flex-direction: column; + + &::before, + &::after { + content: ''; + position: absolute; + width: 0; + height: 0; + border-style: solid; + } + + /* The shadow or outline for the arrow */ + &::before { + z-index: -1; + border-color: rgba(0, 0, 0, 0.1); + } + + /* The visible arrow */ + &::after { + z-index: 1; + border-color: white; + } &.tooltipTextTop { - top: calc(-100% - 65px); + bottom: calc(100% + 10px); + left: 50%; transform: translateX(-50%); + + &::before { + top: 100%; + left: 50%; + transform: translateX(-50%); + border-width: 12px 12px 0 12px; + border-color: rgba(0, 0, 0, 0.1) transparent transparent transparent; + } + + &::after { + top: 100%; + left: 50%; + transform: translateX(-50%); + border-width: 10px 10px 0 10px; + border-color: white transparent transparent transparent; + } } &.tooltipTextRight { top: 50%; left: calc(100% + 10px); transform: translateY(-50%); + + &::before { + top: 50%; + left: -12px; + transform: translateY(-50%); + border-width: 12px 12px 12px 0; + border-color: transparent rgba(0, 0, 0, 0.1) transparent transparent; + } + + &::after { + top: 50%; + left: -10px; + transform: translateY(-50%); + border-width: 10px 10px 10px 0; + border-color: transparent white transparent transparent; + } } &.tooltipTextLeft { top: 50%; right: calc(100% + 10px); transform: translateY(-50%); + + &::before { + top: 50%; + right: -12px; + transform: translateY(-50%); + border-width: 12px 0 12px 12px; + border-color: transparent transparent transparent rgba(0, 0, 0, 0.1); + } + + &::after { + top: 50%; + right: -10px; + transform: translateY(-50%); + border-width: 10px 0 10px 10px; + border-color: transparent transparent transparent white; + } } &.tooltipTextBottom { - top: calc(100% + 6px); + top: calc(100% + 10px); left: 50%; transform: translateX(-50%); + + &::before { + bottom: 100%; + left: 50%; + transform: translateX(-50%); + border-width: 0 12px 12px 12px; + border-color: transparent transparent rgba(0, 0, 0, 0.1) transparent; + } + + &::after { + bottom: 100%; + left: 50%; + transform: translateX(-50%); + border-width: 0 10px 10px 10px; + border-color: transparent transparent white transparent; + } } } + + .popoverHeader { + background-color: #f7f7f7; + padding: 8px 10px; + border-bottom: 1px solid rgba(0, 0, 0, 0.1); + font-weight: bold; + font-size: 14px; + border-top-left-radius: 4px; + border-top-right-radius: 4px; + } + + .popoverBody { + padding: 10px; + font-size: 14px; + white-space: pre-wrap; + } } diff --git a/src/shared/ToolTip/ToolTip.test.jsx b/src/shared/ToolTip/ToolTip.test.jsx index 63c0a2f1912..6ff1df1574b 100644 --- a/src/shared/ToolTip/ToolTip.test.jsx +++ b/src/shared/ToolTip/ToolTip.test.jsx @@ -56,4 +56,33 @@ describe('ToolTip', () => { // Assert that the tooltip content is displayed expect(tooltipContent.text()).toBe(text); }); + it('should display tooltip with title when provided', () => { + const titleText = 'Tooltip Title'; + const bodyText = 'Tooltip Body'; + + const component = mount(); + + component.find('.tooltipContainer').simulate('click'); + + const tooltipTitle = component.find('.popoverHeader'); + const tooltipBody = component.find('.popoverBody'); + + expect(tooltipTitle.text()).toBe(titleText); + expect(tooltipBody.text()).toBe(bodyText); + }); + + it('should not display title when it is not provided', () => { + const bodyText = 'Tooltip Body'; + + const component = mount(); + + component.find('.tooltipContainer').simulate('click'); + + // Find the tooltip content and assert only the body is displayed + const tooltipTitle = component.find('.popoverHeader'); + const tooltipBody = component.find('.popoverBody'); + + expect(tooltipTitle.exists()).toBe(false); + expect(tooltipBody.text()).toBe(bodyText); + }); }); diff --git a/src/shared/constants.js b/src/shared/constants.js index 21589b38e48..c536312156d 100644 --- a/src/shared/constants.js +++ b/src/shared/constants.js @@ -120,7 +120,7 @@ export const shipmentOptionLabels = [ { key: SHIPMENT_OPTIONS.HHG, label: 'HHG' }, { key: SHIPMENT_OPTIONS.PPM, label: 'PPM' }, { key: SHIPMENT_OPTIONS.BOAT, label: 'Boat' }, - { key: SHIPMENT_OPTIONS.MOBILE_HOME, label: 'MobileHome' }, + { key: SHIPMENT_OPTIONS.MOBILE_HOME, label: 'Mobile Home' }, { key: SHIPMENT_TYPES.BOAT_HAUL_AWAY, label: 'Boat' }, { key: SHIPMENT_TYPES.BOAT_TOW_AWAY, label: 'Boat' }, ]; diff --git a/src/utils/queues.jsx b/src/utils/queues.jsx index d6503be6e66..c50ae1920d3 100644 --- a/src/utils/queues.jsx +++ b/src/utils/queues.jsx @@ -1,24 +1,21 @@ import React from 'react'; +import { deleteAssignedOfficeUserForMove, updateAssignedOfficeUserForMove } from 'services/ghcApi'; import { DEFAULT_EMPTY_VALUE } from 'shared/constants'; -const addAssignedOfficeUser = (users, assignedTo) => { - const newAvailableOfficeUsers = users.slice(); - const { lastName, firstName, id } = assignedTo; - newAvailableOfficeUsers.push({ - label: `${lastName}, ${firstName}`, - value: id, - }); - return newAvailableOfficeUsers; -}; - export const formatOfficeUser = (user) => { const fullName = `${user?.lastName}, ${user?.firstName}`; return { label: fullName, value: user.officeUserId }; }; +const addAssignedOfficeUser = (users, assignedTo) => { + const newAvailableOfficeUsers = users.slice(); + newAvailableOfficeUsers.push(formatOfficeUser(assignedTo)); + return newAvailableOfficeUsers; +}; + export const formatAvailableOfficeUsers = (users, isSupervisor, currentUserId) => { - if (!users.length || isSupervisor === undefined || currentUserId === undefined) return []; + if (!users?.length || isSupervisor === undefined || currentUserId === undefined) return []; // instantiate array with empty value for unassign purposes down the road const newAvailableOfficeUsers = [{ label: DEFAULT_EMPTY_VALUE, value: null }]; @@ -40,19 +37,23 @@ export const formatAvailableOfficeUsers = (users, isSupervisor, currentUserId) = return newAvailableOfficeUsers; }; -export const formatAvailableOfficeUsersForRow = (row) => { +export const formatAvailableOfficeUsersForRow = (originalRow, supervisor, currentUserId) => { // dupe the row to avoid issues with passing office user array by reference - const updatedRow = { ...row }; + const row = { ...originalRow }; + row.availableOfficeUsers = formatAvailableOfficeUsers(row.availableOfficeUsers, supervisor, currentUserId); // if the move is assigned to a user not present in availableOfficeUsers // lets push them onto the end - if (row.assignedTo !== undefined && !row.availableOfficeUsers?.some((user) => user.value === row.assignedTo.id)) { - updatedRow.availableOfficeUsers = addAssignedOfficeUser(row.availableOfficeUsers, row.assignedTo); + if ( + row.assignedTo !== undefined && + !row.availableOfficeUsers?.some((user) => user.value === row.assignedTo.officeUserId) + ) { + row.availableOfficeUsers = addAssignedOfficeUser(row.availableOfficeUsers, row.assignedTo); } - const { assignedTo, availableOfficeUsers } = updatedRow; + const { assignedTo, availableOfficeUsers } = row; // if there is an assigned user, assign to a variable so we can set a default value below - const assignedToUser = availableOfficeUsers.find((user) => user.value === assignedTo?.id); + const assignedToUser = availableOfficeUsers.find((user) => user.value === assignedTo?.officeUserId); const formattedAvailableOfficeUsers = availableOfficeUsers.map(({ value, label }) => (