From 6a2aa93f1c6b7404339394df414cc907a22fe152 Mon Sep 17 00:00:00 2001 From: loganwc Date: Wed, 15 Jan 2025 16:48:07 +0000 Subject: [PATCH 01/35] most backend work done, frontend doesn't work --- pkg/gen/ghcapi/configure_mymove.go | 5 + pkg/gen/ghcapi/embedded_spec.go | 173 ++++++++++++++++ pkg/gen/ghcapi/ghcoperations/mymove_api.go | 12 ++ .../queues/post_bulk_assignment_data.go | 58 ++++++ .../post_bulk_assignment_data_parameters.go | 128 ++++++++++++ .../post_bulk_assignment_data_responses.go | 194 ++++++++++++++++++ .../post_bulk_assignment_data_urlbuilder.go | 103 ++++++++++ .../ghcmessages/bulk_assignment_for_user.go | 77 +++++++ .../ghcmessages/bulk_assignment_move_data.go | 38 ++++ .../bulk_assignment_save_payload.go | 175 ++++++++++++++++ pkg/handlers/ghcapi/api.go | 7 + pkg/handlers/ghcapi/queues.go | 67 ++++++ pkg/services/move.go | 9 + pkg/services/move/move_assignment.go | 68 ++++++ pkg/services/move/move_fetcher.go | 117 +++++++++++ src/components/Table/TableQueue.jsx | 7 +- src/hooks/queries.js | 16 ++ src/services/ghcApi.js | 4 + swagger-def/ghc.yaml | 56 +++++ swagger/ghc.yaml | 57 +++++ 20 files changed, 1370 insertions(+), 1 deletion(-) create mode 100644 pkg/gen/ghcapi/ghcoperations/queues/post_bulk_assignment_data.go create mode 100644 pkg/gen/ghcapi/ghcoperations/queues/post_bulk_assignment_data_parameters.go create mode 100644 pkg/gen/ghcapi/ghcoperations/queues/post_bulk_assignment_data_responses.go create mode 100644 pkg/gen/ghcapi/ghcoperations/queues/post_bulk_assignment_data_urlbuilder.go create mode 100644 pkg/gen/ghcmessages/bulk_assignment_for_user.go create mode 100644 pkg/gen/ghcmessages/bulk_assignment_move_data.go create mode 100644 pkg/gen/ghcmessages/bulk_assignment_save_payload.go create mode 100644 pkg/services/move/move_assignment.go diff --git a/pkg/gen/ghcapi/configure_mymove.go b/pkg/gen/ghcapi/configure_mymove.go index 2c7449ec836..204cd592560 100644 --- a/pkg/gen/ghcapi/configure_mymove.go +++ b/pkg/gen/ghcapi/configure_mymove.go @@ -412,6 +412,11 @@ func configureAPI(api *ghcoperations.MymoveAPI) http.Handler { return middleware.NotImplemented("operation move.MoveCanceler has not yet been implemented") }) } + if api.QueuesPostBulkAssignmentDataHandler == nil { + api.QueuesPostBulkAssignmentDataHandler = queues.PostBulkAssignmentDataHandlerFunc(func(params queues.PostBulkAssignmentDataParams) middleware.Responder { + return middleware.NotImplemented("operation queues.PostBulkAssignmentData has not yet been implemented") + }) + } if api.ShipmentRejectShipmentHandler == nil { api.ShipmentRejectShipmentHandler = shipment.RejectShipmentHandlerFunc(func(params shipment.RejectShipmentParams) middleware.Responder { return middleware.NotImplemented("operation shipment.RejectShipment has not yet been implemented") diff --git a/pkg/gen/ghcapi/embedded_spec.go b/pkg/gen/ghcapi/embedded_spec.go index 14b3a43903c..09d0577eb7d 100644 --- a/pkg/gen/ghcapi/embedded_spec.go +++ b/pkg/gen/ghcapi/embedded_spec.go @@ -4356,6 +4356,55 @@ func init() { } } }, + "/queues/bulk-assignment/assign": { + "post": { + "description": "Supervisor office users are able to assign moves. This endpoint saves office user assignments to multiple moves.\n", + "tags": [ + "queues" + ], + "summary": "Assigns one or more moves to one or more office users", + "operationId": "postBulkAssignmentData", + "parameters": [ + { + "name": "bulkAssignmentSavePayload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/BulkAssignmentSavePayload" + } + }, + { + "enum": [ + "COUNSELING", + "CLOSEOUT", + "TASK_ORDER", + "PAYMENT_REQUEST" + ], + "type": "string", + "description": "A string corresponding to the queue type", + "name": "queueType", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Successfully returned bulk assignment data", + "schema": { + "$ref": "#/definitions/BulkAssignmentData" + } + }, + "401": { + "$ref": "#/responses/PermissionDenied" + }, + "404": { + "$ref": "#/responses/NotFound" + }, + "500": { + "$ref": "#/responses/ServerError" + } + } + } + }, "/queues/counseling": { "get": { "description": "An office services counselor user will be assigned a transportation office that will determine which moves are displayed in their queue based on the origin duty location. GHC moves will show up here onced they have reached the NEEDS SERVICE COUNSELING status after submission from a customer or created on a customer's behalf.\n", @@ -6882,6 +6931,22 @@ func init() { } } }, + "BulkAssignmentForUser": { + "type": "object", + "properties": { + "moveAssignments": { + "type": "integer" + }, + "userId": { + "type": "string", + "format": "uuid" + } + } + }, + "BulkAssignmentMoveData": { + "type": "string", + "format": "uuid" + }, "BulkAssignmentMoveID": { "type": "string", "format": "uuid", @@ -6893,6 +6958,23 @@ func init() { "$ref": "#/definitions/BulkAssignmentMoveID" } }, + "BulkAssignmentSavePayload": { + "type": "object", + "properties": { + "moveData": { + "type": "array", + "items": { + "$ref": "#/definitions/BulkAssignmentMoveData" + } + }, + "userData": { + "type": "array", + "items": { + "$ref": "#/definitions/BulkAssignmentForUser" + } + } + } + }, "ClientError": { "type": "object", "required": [ @@ -20796,6 +20878,64 @@ func init() { } } }, + "/queues/bulk-assignment/assign": { + "post": { + "description": "Supervisor office users are able to assign moves. This endpoint saves office user assignments to multiple moves.\n", + "tags": [ + "queues" + ], + "summary": "Assigns one or more moves to one or more office users", + "operationId": "postBulkAssignmentData", + "parameters": [ + { + "name": "bulkAssignmentSavePayload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/BulkAssignmentSavePayload" + } + }, + { + "enum": [ + "COUNSELING", + "CLOSEOUT", + "TASK_ORDER", + "PAYMENT_REQUEST" + ], + "type": "string", + "description": "A string corresponding to the queue type", + "name": "queueType", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Successfully returned bulk assignment data", + "schema": { + "$ref": "#/definitions/BulkAssignmentData" + } + }, + "401": { + "description": "The request was denied", + "schema": { + "$ref": "#/definitions/Error" + } + }, + "404": { + "description": "The requested resource wasn't found", + "schema": { + "$ref": "#/definitions/Error" + } + }, + "500": { + "description": "A server error occurred", + "schema": { + "$ref": "#/definitions/Error" + } + } + } + } + }, "/queues/counseling": { "get": { "description": "An office services counselor user will be assigned a transportation office that will determine which moves are displayed in their queue based on the origin duty location. GHC moves will show up here onced they have reached the NEEDS SERVICE COUNSELING status after submission from a customer or created on a customer's behalf.\n", @@ -23716,6 +23856,22 @@ func init() { } } }, + "BulkAssignmentForUser": { + "type": "object", + "properties": { + "moveAssignments": { + "type": "integer" + }, + "userId": { + "type": "string", + "format": "uuid" + } + } + }, + "BulkAssignmentMoveData": { + "type": "string", + "format": "uuid" + }, "BulkAssignmentMoveID": { "type": "string", "format": "uuid", @@ -23727,6 +23883,23 @@ func init() { "$ref": "#/definitions/BulkAssignmentMoveID" } }, + "BulkAssignmentSavePayload": { + "type": "object", + "properties": { + "moveData": { + "type": "array", + "items": { + "$ref": "#/definitions/BulkAssignmentMoveData" + } + }, + "userData": { + "type": "array", + "items": { + "$ref": "#/definitions/BulkAssignmentForUser" + } + } + } + }, "ClientError": { "type": "object", "required": [ diff --git a/pkg/gen/ghcapi/ghcoperations/mymove_api.go b/pkg/gen/ghcapi/ghcoperations/mymove_api.go index e64a6704256..c167fce48c4 100644 --- a/pkg/gen/ghcapi/ghcoperations/mymove_api.go +++ b/pkg/gen/ghcapi/ghcoperations/mymove_api.go @@ -275,6 +275,9 @@ func NewMymoveAPI(spec *loads.Document) *MymoveAPI { MoveMoveCancelerHandler: move.MoveCancelerHandlerFunc(func(params move.MoveCancelerParams) middleware.Responder { return middleware.NotImplemented("operation move.MoveCanceler has not yet been implemented") }), + QueuesPostBulkAssignmentDataHandler: queues.PostBulkAssignmentDataHandlerFunc(func(params queues.PostBulkAssignmentDataParams) middleware.Responder { + return middleware.NotImplemented("operation queues.PostBulkAssignmentData has not yet been implemented") + }), ShipmentRejectShipmentHandler: shipment.RejectShipmentHandlerFunc(func(params shipment.RejectShipmentParams) middleware.Responder { return middleware.NotImplemented("operation shipment.RejectShipment has not yet been implemented") }), @@ -574,6 +577,8 @@ type MymoveAPI struct { QueuesListPrimeMovesHandler queues.ListPrimeMovesHandler // MoveMoveCancelerHandler sets the operation handler for the move canceler operation MoveMoveCancelerHandler move.MoveCancelerHandler + // QueuesPostBulkAssignmentDataHandler sets the operation handler for the post bulk assignment data operation + QueuesPostBulkAssignmentDataHandler queues.PostBulkAssignmentDataHandler // ShipmentRejectShipmentHandler sets the operation handler for the reject shipment operation ShipmentRejectShipmentHandler shipment.RejectShipmentHandler // LinesOfAccountingRequestLineOfAccountingHandler sets the operation handler for the request line of accounting operation @@ -939,6 +944,9 @@ func (o *MymoveAPI) Validate() error { if o.MoveMoveCancelerHandler == nil { unregistered = append(unregistered, "move.MoveCancelerHandler") } + if o.QueuesPostBulkAssignmentDataHandler == nil { + unregistered = append(unregistered, "queues.PostBulkAssignmentDataHandler") + } if o.ShipmentRejectShipmentHandler == nil { unregistered = append(unregistered, "shipment.RejectShipmentHandler") } @@ -1423,6 +1431,10 @@ func (o *MymoveAPI) initHandlerCache() { if o.handlers["POST"] == nil { o.handlers["POST"] = make(map[string]http.Handler) } + o.handlers["POST"]["/queues/bulk-assignment/assign"] = queues.NewPostBulkAssignmentData(o.context, o.QueuesPostBulkAssignmentDataHandler) + if o.handlers["POST"] == nil { + o.handlers["POST"] = make(map[string]http.Handler) + } o.handlers["POST"]["/shipments/{shipmentID}/reject"] = shipment.NewRejectShipment(o.context, o.ShipmentRejectShipmentHandler) if o.handlers["POST"] == nil { o.handlers["POST"] = make(map[string]http.Handler) diff --git a/pkg/gen/ghcapi/ghcoperations/queues/post_bulk_assignment_data.go b/pkg/gen/ghcapi/ghcoperations/queues/post_bulk_assignment_data.go new file mode 100644 index 00000000000..4504e862f18 --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/queues/post_bulk_assignment_data.go @@ -0,0 +1,58 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package queues + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" +) + +// PostBulkAssignmentDataHandlerFunc turns a function with the right signature into a post bulk assignment data handler +type PostBulkAssignmentDataHandlerFunc func(PostBulkAssignmentDataParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn PostBulkAssignmentDataHandlerFunc) Handle(params PostBulkAssignmentDataParams) middleware.Responder { + return fn(params) +} + +// PostBulkAssignmentDataHandler interface for that can handle valid post bulk assignment data params +type PostBulkAssignmentDataHandler interface { + Handle(PostBulkAssignmentDataParams) middleware.Responder +} + +// NewPostBulkAssignmentData creates a new http.Handler for the post bulk assignment data operation +func NewPostBulkAssignmentData(ctx *middleware.Context, handler PostBulkAssignmentDataHandler) *PostBulkAssignmentData { + return &PostBulkAssignmentData{Context: ctx, Handler: handler} +} + +/* + PostBulkAssignmentData swagger:route POST /queues/bulk-assignment/assign queues postBulkAssignmentData + +# Assigns one or more moves to one or more office users + +Supervisor office users are able to assign moves. This endpoint saves office user assignments to multiple moves. +*/ +type PostBulkAssignmentData struct { + Context *middleware.Context + Handler PostBulkAssignmentDataHandler +} + +func (o *PostBulkAssignmentData) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewPostBulkAssignmentDataParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/pkg/gen/ghcapi/ghcoperations/queues/post_bulk_assignment_data_parameters.go b/pkg/gen/ghcapi/ghcoperations/queues/post_bulk_assignment_data_parameters.go new file mode 100644 index 00000000000..5e30050f10f --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/queues/post_bulk_assignment_data_parameters.go @@ -0,0 +1,128 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package queues + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "io" + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/validate" + + "github.com/transcom/mymove/pkg/gen/ghcmessages" +) + +// NewPostBulkAssignmentDataParams creates a new PostBulkAssignmentDataParams object +// +// There are no default values defined in the spec. +func NewPostBulkAssignmentDataParams() PostBulkAssignmentDataParams { + + return PostBulkAssignmentDataParams{} +} + +// PostBulkAssignmentDataParams contains all the bound params for the post bulk assignment data operation +// typically these are obtained from a http.Request +// +// swagger:parameters postBulkAssignmentData +type PostBulkAssignmentDataParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /* + Required: true + In: body + */ + BulkAssignmentSavePayload *ghcmessages.BulkAssignmentSavePayload + /*A string corresponding to the queue type + In: query + */ + QueueType *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 NewPostBulkAssignmentDataParams() beforehand. +func (o *PostBulkAssignmentDataParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + qs := runtime.Values(r.URL.Query()) + + if runtime.HasBody(r) { + defer r.Body.Close() + var body ghcmessages.BulkAssignmentSavePayload + if err := route.Consumer.Consume(r.Body, &body); err != nil { + if err == io.EOF { + res = append(res, errors.Required("bulkAssignmentSavePayload", "body", "")) + } else { + res = append(res, errors.NewParseError("bulkAssignmentSavePayload", "body", "", err)) + } + } else { + // validate body object + if err := body.Validate(route.Formats); err != nil { + res = append(res, err) + } + + ctx := validate.WithOperationRequest(r.Context()) + if err := body.ContextValidate(ctx, route.Formats); err != nil { + res = append(res, err) + } + + if len(res) == 0 { + o.BulkAssignmentSavePayload = &body + } + } + } else { + res = append(res, errors.Required("bulkAssignmentSavePayload", "body", "")) + } + + qQueueType, qhkQueueType, _ := qs.GetOK("queueType") + if err := o.bindQueueType(qQueueType, qhkQueueType, route.Formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindQueueType binds and validates parameter QueueType from query. +func (o *PostBulkAssignmentDataParams) bindQueueType(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.QueueType = &raw + + if err := o.validateQueueType(formats); err != nil { + return err + } + + return nil +} + +// validateQueueType carries on validations for parameter QueueType +func (o *PostBulkAssignmentDataParams) validateQueueType(formats strfmt.Registry) error { + + if err := validate.EnumCase("queueType", "query", *o.QueueType, []interface{}{"COUNSELING", "CLOSEOUT", "TASK_ORDER", "PAYMENT_REQUEST"}, true); err != nil { + return err + } + + return nil +} diff --git a/pkg/gen/ghcapi/ghcoperations/queues/post_bulk_assignment_data_responses.go b/pkg/gen/ghcapi/ghcoperations/queues/post_bulk_assignment_data_responses.go new file mode 100644 index 00000000000..f3a676ea8b7 --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/queues/post_bulk_assignment_data_responses.go @@ -0,0 +1,194 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package queues + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "github.com/transcom/mymove/pkg/gen/ghcmessages" +) + +// PostBulkAssignmentDataOKCode is the HTTP code returned for type PostBulkAssignmentDataOK +const PostBulkAssignmentDataOKCode int = 200 + +/* +PostBulkAssignmentDataOK Successfully returned bulk assignment data + +swagger:response postBulkAssignmentDataOK +*/ +type PostBulkAssignmentDataOK struct { + + /* + In: Body + */ + Payload *ghcmessages.BulkAssignmentData `json:"body,omitempty"` +} + +// NewPostBulkAssignmentDataOK creates PostBulkAssignmentDataOK with default headers values +func NewPostBulkAssignmentDataOK() *PostBulkAssignmentDataOK { + + return &PostBulkAssignmentDataOK{} +} + +// WithPayload adds the payload to the post bulk assignment data o k response +func (o *PostBulkAssignmentDataOK) WithPayload(payload *ghcmessages.BulkAssignmentData) *PostBulkAssignmentDataOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the post bulk assignment data o k response +func (o *PostBulkAssignmentDataOK) SetPayload(payload *ghcmessages.BulkAssignmentData) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *PostBulkAssignmentDataOK) 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 + } + } +} + +// PostBulkAssignmentDataUnauthorizedCode is the HTTP code returned for type PostBulkAssignmentDataUnauthorized +const PostBulkAssignmentDataUnauthorizedCode int = 401 + +/* +PostBulkAssignmentDataUnauthorized The request was denied + +swagger:response postBulkAssignmentDataUnauthorized +*/ +type PostBulkAssignmentDataUnauthorized struct { + + /* + In: Body + */ + Payload *ghcmessages.Error `json:"body,omitempty"` +} + +// NewPostBulkAssignmentDataUnauthorized creates PostBulkAssignmentDataUnauthorized with default headers values +func NewPostBulkAssignmentDataUnauthorized() *PostBulkAssignmentDataUnauthorized { + + return &PostBulkAssignmentDataUnauthorized{} +} + +// WithPayload adds the payload to the post bulk assignment data unauthorized response +func (o *PostBulkAssignmentDataUnauthorized) WithPayload(payload *ghcmessages.Error) *PostBulkAssignmentDataUnauthorized { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the post bulk assignment data unauthorized response +func (o *PostBulkAssignmentDataUnauthorized) SetPayload(payload *ghcmessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *PostBulkAssignmentDataUnauthorized) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(401) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// PostBulkAssignmentDataNotFoundCode is the HTTP code returned for type PostBulkAssignmentDataNotFound +const PostBulkAssignmentDataNotFoundCode int = 404 + +/* +PostBulkAssignmentDataNotFound The requested resource wasn't found + +swagger:response postBulkAssignmentDataNotFound +*/ +type PostBulkAssignmentDataNotFound struct { + + /* + In: Body + */ + Payload *ghcmessages.Error `json:"body,omitempty"` +} + +// NewPostBulkAssignmentDataNotFound creates PostBulkAssignmentDataNotFound with default headers values +func NewPostBulkAssignmentDataNotFound() *PostBulkAssignmentDataNotFound { + + return &PostBulkAssignmentDataNotFound{} +} + +// WithPayload adds the payload to the post bulk assignment data not found response +func (o *PostBulkAssignmentDataNotFound) WithPayload(payload *ghcmessages.Error) *PostBulkAssignmentDataNotFound { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the post bulk assignment data not found response +func (o *PostBulkAssignmentDataNotFound) SetPayload(payload *ghcmessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *PostBulkAssignmentDataNotFound) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(404) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// PostBulkAssignmentDataInternalServerErrorCode is the HTTP code returned for type PostBulkAssignmentDataInternalServerError +const PostBulkAssignmentDataInternalServerErrorCode int = 500 + +/* +PostBulkAssignmentDataInternalServerError A server error occurred + +swagger:response postBulkAssignmentDataInternalServerError +*/ +type PostBulkAssignmentDataInternalServerError struct { + + /* + In: Body + */ + Payload *ghcmessages.Error `json:"body,omitempty"` +} + +// NewPostBulkAssignmentDataInternalServerError creates PostBulkAssignmentDataInternalServerError with default headers values +func NewPostBulkAssignmentDataInternalServerError() *PostBulkAssignmentDataInternalServerError { + + return &PostBulkAssignmentDataInternalServerError{} +} + +// WithPayload adds the payload to the post bulk assignment data internal server error response +func (o *PostBulkAssignmentDataInternalServerError) WithPayload(payload *ghcmessages.Error) *PostBulkAssignmentDataInternalServerError { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the post bulk assignment data internal server error response +func (o *PostBulkAssignmentDataInternalServerError) SetPayload(payload *ghcmessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *PostBulkAssignmentDataInternalServerError) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(500) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/pkg/gen/ghcapi/ghcoperations/queues/post_bulk_assignment_data_urlbuilder.go b/pkg/gen/ghcapi/ghcoperations/queues/post_bulk_assignment_data_urlbuilder.go new file mode 100644 index 00000000000..67c41a60000 --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/queues/post_bulk_assignment_data_urlbuilder.go @@ -0,0 +1,103 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package queues + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" +) + +// PostBulkAssignmentDataURL generates an URL for the post bulk assignment data operation +type PostBulkAssignmentDataURL struct { + QueueType *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 *PostBulkAssignmentDataURL) WithBasePath(bp string) *PostBulkAssignmentDataURL { + 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 *PostBulkAssignmentDataURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *PostBulkAssignmentDataURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/queues/bulk-assignment/assign" + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/ghc/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + qs := make(url.Values) + + var queueTypeQ string + if o.QueueType != nil { + queueTypeQ = *o.QueueType + } + if queueTypeQ != "" { + qs.Set("queueType", queueTypeQ) + } + + _result.RawQuery = qs.Encode() + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *PostBulkAssignmentDataURL) 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 *PostBulkAssignmentDataURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *PostBulkAssignmentDataURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on PostBulkAssignmentDataURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on PostBulkAssignmentDataURL") + } + + 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 *PostBulkAssignmentDataURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/pkg/gen/ghcmessages/bulk_assignment_for_user.go b/pkg/gen/ghcmessages/bulk_assignment_for_user.go new file mode 100644 index 00000000000..daa85ae1f91 --- /dev/null +++ b/pkg/gen/ghcmessages/bulk_assignment_for_user.go @@ -0,0 +1,77 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package ghcmessages + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// BulkAssignmentForUser bulk assignment for user +// +// swagger:model BulkAssignmentForUser +type BulkAssignmentForUser struct { + + // move assignments + MoveAssignments int64 `json:"moveAssignments,omitempty"` + + // user Id + // Format: uuid + UserID strfmt.UUID `json:"userId,omitempty"` +} + +// Validate validates this bulk assignment for user +func (m *BulkAssignmentForUser) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateUserID(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *BulkAssignmentForUser) validateUserID(formats strfmt.Registry) error { + if swag.IsZero(m.UserID) { // not required + return nil + } + + if err := validate.FormatOf("userId", "body", "uuid", m.UserID.String(), formats); err != nil { + return err + } + + return nil +} + +// ContextValidate validates this bulk assignment for user based on context it is used +func (m *BulkAssignmentForUser) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *BulkAssignmentForUser) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *BulkAssignmentForUser) UnmarshalBinary(b []byte) error { + var res BulkAssignmentForUser + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/pkg/gen/ghcmessages/bulk_assignment_move_data.go b/pkg/gen/ghcmessages/bulk_assignment_move_data.go new file mode 100644 index 00000000000..c69c23c9ee7 --- /dev/null +++ b/pkg/gen/ghcmessages/bulk_assignment_move_data.go @@ -0,0 +1,38 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package ghcmessages + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/validate" +) + +// BulkAssignmentMoveData bulk assignment move data +// +// swagger:model BulkAssignmentMoveData +type BulkAssignmentMoveData strfmt.UUID + +// Validate validates this bulk assignment move data +func (m BulkAssignmentMoveData) Validate(formats strfmt.Registry) error { + var res []error + + if err := validate.FormatOf("", "body", "uuid", strfmt.UUID(m).String(), formats); err != nil { + return err + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// ContextValidate validates this bulk assignment move data based on context it is used +func (m BulkAssignmentMoveData) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} diff --git a/pkg/gen/ghcmessages/bulk_assignment_save_payload.go b/pkg/gen/ghcmessages/bulk_assignment_save_payload.go new file mode 100644 index 00000000000..83b5d4c8ba1 --- /dev/null +++ b/pkg/gen/ghcmessages/bulk_assignment_save_payload.go @@ -0,0 +1,175 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package ghcmessages + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + "strconv" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// BulkAssignmentSavePayload bulk assignment save payload +// +// swagger:model BulkAssignmentSavePayload +type BulkAssignmentSavePayload struct { + + // move data + MoveData []BulkAssignmentMoveData `json:"moveData"` + + // user data + UserData []*BulkAssignmentForUser `json:"userData"` +} + +// Validate validates this bulk assignment save payload +func (m *BulkAssignmentSavePayload) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateMoveData(formats); err != nil { + res = append(res, err) + } + + if err := m.validateUserData(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *BulkAssignmentSavePayload) validateMoveData(formats strfmt.Registry) error { + if swag.IsZero(m.MoveData) { // not required + return nil + } + + for i := 0; i < len(m.MoveData); i++ { + + if err := m.MoveData[i].Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("moveData" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("moveData" + "." + strconv.Itoa(i)) + } + return err + } + + } + + return nil +} + +func (m *BulkAssignmentSavePayload) validateUserData(formats strfmt.Registry) error { + if swag.IsZero(m.UserData) { // not required + return nil + } + + for i := 0; i < len(m.UserData); i++ { + if swag.IsZero(m.UserData[i]) { // not required + continue + } + + if m.UserData[i] != nil { + if err := m.UserData[i].Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("userData" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("userData" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + +// ContextValidate validate this bulk assignment save payload based on the context it is used +func (m *BulkAssignmentSavePayload) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateMoveData(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateUserData(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *BulkAssignmentSavePayload) contextValidateMoveData(ctx context.Context, formats strfmt.Registry) error { + + for i := 0; i < len(m.MoveData); i++ { + + if swag.IsZero(m.MoveData[i]) { // not required + return nil + } + + if err := m.MoveData[i].ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("moveData" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("moveData" + "." + strconv.Itoa(i)) + } + return err + } + + } + + return nil +} + +func (m *BulkAssignmentSavePayload) contextValidateUserData(ctx context.Context, formats strfmt.Registry) error { + + for i := 0; i < len(m.UserData); i++ { + + if m.UserData[i] != nil { + + if swag.IsZero(m.UserData[i]) { // not required + return nil + } + + if err := m.UserData[i].ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("userData" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("userData" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + +// MarshalBinary interface implementation +func (m *BulkAssignmentSavePayload) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *BulkAssignmentSavePayload) UnmarshalBinary(b []byte) error { + var res BulkAssignmentSavePayload + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/pkg/handlers/ghcapi/api.go b/pkg/handlers/ghcapi/api.go index 1b0a66d1956..69ee70ad75a 100644 --- a/pkg/handlers/ghcapi/api.go +++ b/pkg/handlers/ghcapi/api.go @@ -536,6 +536,13 @@ func NewGhcAPIHandler(handlerConfig handlers.HandlerConfig) *ghcops.MymoveAPI { move.NewMoveFetcherBulkAssignment(), } + ghcAPI.QueuesPostBulkAssignmentDataHandler = PostBulkAssignmentDataHandler{ + handlerConfig, + officeusercreator.NewOfficeUserFetcherPop(), + move.NewMoveFetcher(), + move.NewMoveAssignerBulkAssignment(), + } + ghcAPI.QueuesGetMovesQueueHandler = GetMovesQueueHandler{ handlerConfig, order.NewOrderFetcher(), diff --git a/pkg/handlers/ghcapi/queues.go b/pkg/handlers/ghcapi/queues.go index 16c80507c85..d25073c84c9 100644 --- a/pkg/handlers/ghcapi/queues.go +++ b/pkg/handlers/ghcapi/queues.go @@ -617,6 +617,73 @@ func (h GetBulkAssignmentDataHandler) Handle( }) } +// PostBulkAssignmentDataHandler saves the bulk assignment data +type PostBulkAssignmentDataHandler struct { + handlers.HandlerConfig + services.OfficeUserFetcherPop + services.MoveFetcher + services.MoveAssigner +} + +func (h PostBulkAssignmentDataHandler) Handle( + params queues.PostBulkAssignmentDataParams, +) middleware.Responder { + return h.AuditableAppContextFromRequestWithErrors(params.HTTPRequest, + func(appCtx appcontext.AppContext) (middleware.Responder, error) { + if !appCtx.Session().IsOfficeUser() { + err := apperror.NewForbiddenError("not an office user") + appCtx.Logger().Error("Must be an office user", zap.Error(err)) + return queues.NewGetBulkAssignmentDataUnauthorized(), err + } + + officeUser, err := h.OfficeUserFetcherPop.FetchOfficeUserByID(appCtx, appCtx.Session().OfficeUserID) + if err != nil { + appCtx.Logger().Error("Error retrieving office_user", zap.Error(err)) + return queues.NewGetBulkAssignmentDataNotFound(), err + } + + privileges, err := models.FetchPrivilegesForUser(appCtx.DB(), *officeUser.UserID) + if err != nil { + appCtx.Logger().Error("Error retreiving user privileges", zap.Error(err)) + return queues.NewGetBulkAssignmentDataNotFound(), err + } + + isSupervisor := privileges.HasPrivilege(models.PrivilegeTypeSupervisor) + if !isSupervisor { + appCtx.Logger().Error("Unauthorized", zap.Error(err)) + return queues.NewGetBulkAssignmentDataUnauthorized(), err + } + + queueType := params.QueueType + + // fetch the Services Counselors who work at their office + officeUsers, err := h.OfficeUserFetcherPop.FetchOfficeUsersWithWorkloadByRoleAndOffice( + appCtx, + roles.RoleTypeServicesCounselor, + officeUser.TransportationOfficeID, + ) + if err != nil { + appCtx.Logger().Error("Error retreiving office users", zap.Error(err)) + return queues.NewGetBulkAssignmentDataInternalServerError(), err + } + + // fetch the moves available to be assigned to their office users + movesForAssignment, err := h.MoveFetcher.FetchMovesByIdArray(appCtx, params.BulkAssignmentSavePayload.MoveData) + if err != nil { + appCtx.Logger().Error("Error retreiving moves for assignment", zap.Error(err)) + return queues.NewGetBulkAssignmentDataInternalServerError(), err + } + + _, err = h.MoveAssigner.BulkMoveAssignment(appCtx, *queueType, officeUsers, movesForAssignment) + if err != nil { + appCtx.Logger().Error("Error assigning moves", zap.Error(err)) + return queues.NewGetBulkAssignmentDataInternalServerError(), err + } + + return queues.NewGetBulkAssignmentDataOK().WithPayload(nil), nil + }) +} + // GetServicesCounselingOriginListHandler returns the origin list for the Service Counselor user via GET /queues/counselor/origin-list type GetServicesCounselingOriginListHandler struct { handlers.HandlerConfig diff --git a/pkg/services/move.go b/pkg/services/move.go index 237e360fe3f..d42ffe8cd99 100644 --- a/pkg/services/move.go +++ b/pkg/services/move.go @@ -8,6 +8,7 @@ import ( "github.com/gofrs/uuid" "github.com/transcom/mymove/pkg/appcontext" + "github.com/transcom/mymove/pkg/gen/ghcmessages" "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/models/roles" "github.com/transcom/mymove/pkg/storage" @@ -27,6 +28,7 @@ type MoveListFetcher interface { type MoveFetcher interface { FetchMove(appCtx appcontext.AppContext, locator string, searchParams *MoveFetcherParams) (*models.Move, error) FetchMovesForPPTASReports(appCtx appcontext.AppContext, params *MoveTaskOrderFetcherParams) (models.Moves, error) + FetchMovesByIdArray(appCtx appcontext.AppContext, moveIds []ghcmessages.BulkAssignmentMoveData) (models.Moves, error) } type MoveFetcherBulkAssignment interface { @@ -133,3 +135,10 @@ type MoveAssignedOfficeUserUpdater interface { type CheckForLockedMovesAndUnlockHandler interface { CheckForLockedMovesAndUnlock(appCtx appcontext.AppContext, officeUserID uuid.UUID) error } + +// MoveAssigner is the exported interface for bulk assigning moves to office users +// +//go:generate mockery --name MoveAssigner +type MoveAssigner interface { + BulkMoveAssignment(appCtx appcontext.AppContext, queueType string, officeUsers []models.OfficeUserWithWorkload, movesToAssign models.Moves) (*models.Moves, error) +} diff --git a/pkg/services/move/move_assignment.go b/pkg/services/move/move_assignment.go new file mode 100644 index 00000000000..6ebff8a8833 --- /dev/null +++ b/pkg/services/move/move_assignment.go @@ -0,0 +1,68 @@ +package move + +import ( + "github.com/transcom/mymove/pkg/appcontext" + "github.com/transcom/mymove/pkg/apperror" + "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services" +) + +type moveAssigner struct { +} + +func NewMoveAssignerBulkAssignment() services.MoveAssigner { + return &moveAssigner{} +} + +func (a moveAssigner) BulkMoveAssignment(appCtx appcontext.AppContext, queueType string, officeUsers []models.OfficeUserWithWorkload, movesToAssign models.Moves) (*models.Moves, error) { + if len(movesToAssign) == 0 { + return nil, apperror.NewBadDataError("No moves to assign") + } + + transactionErr := appCtx.NewTransaction(func(txnAppCtx appcontext.AppContext) error { + for _, move := range movesToAssign { + for _, officeUser := range officeUsers { + if officeUser.Workload > 0 { + switch queueType { + case string(models.QueueTypeCounseling): + move.SCAssignedID = &officeUser.ID + case string(models.QueueTypeCloseout): + move.SCAssignedID = &officeUser.ID + case string(models.QueueTypeTaskOrder): + move.TOOAssignedID = &officeUser.ID + } + + officeUser.Workload -= 1 + + verrs, err := appCtx.DB().ValidateAndUpdate(&move) + if err != nil || verrs.HasAny() { + return apperror.NewInvalidInputError(move.ID, err, verrs, "") + } + + break + } + } + } + + return nil + }) + + if transactionErr != nil { + return nil, transactionErr + } + + return nil, nil +} + +// { +// officeUsers: [ +// '1': 2, +// '2': 1, +// '3': 3, +// '4': 4, +// '5': 5, +// '6': 6, +// ] +// moveIds: [] +// queueType +// } diff --git a/pkg/services/move/move_fetcher.go b/pkg/services/move/move_fetcher.go index f4fc42dea57..518912e6c2e 100644 --- a/pkg/services/move/move_fetcher.go +++ b/pkg/services/move/move_fetcher.go @@ -9,6 +9,7 @@ import ( "github.com/transcom/mymove/pkg/appcontext" "github.com/transcom/mymove/pkg/apperror" "github.com/transcom/mymove/pkg/db/utilities" + "github.com/transcom/mymove/pkg/gen/ghcmessages" "github.com/transcom/mymove/pkg/gen/internalmessages" "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/services" @@ -62,6 +63,21 @@ func (f moveFetcher) FetchMove(appCtx appcontext.AppContext, locator string, sea return move, nil } +func (f moveFetcher) FetchMovesByIdArray(appCtx appcontext.AppContext, moveIds []ghcmessages.BulkAssignmentMoveData) (models.Moves, error) { + moves := models.Moves{} + + err := appCtx.DB().Q(). + Where("id in (?)", moveIds). + Where("show = TRUE"). + All(&moves) + + if err != nil { + return nil, err + } + + return moves, nil +} + // Fetches moves for Navy servicemembers with approved shipments. Ignores gbloc rules func (f moveFetcher) FetchMovesForPPTASReports(appCtx appcontext.AppContext, params *services.MoveTaskOrderFetcherParams) (models.Moves, error) { var moves models.Moves @@ -165,3 +181,104 @@ func (f moveFetcherBulkAssignment) FetchMovesForBulkAssignmentCounseling(appCtx return moves, nil } + +func (f moveFetcherBulkAssignment) FetchMovesForBulkAssignmentCloseout(appCtx appcontext.AppContext, gbloc string, officeId uuid.UUID) ([]models.MoveWithEarliestDate, error) { + var moves []models.MoveWithEarliestDate + + query := `SELECT + moves.id, + COALESCE(ppm_shipments.expected_departure_date, '9999-12-31') AS earliest_date + FROM moves + INNER JOIN orders ON orders.id = moves.orders_id + INNER JOIN service_members ON service_members.id = orders.service_member_id + INNER JOIN mto_shipments ON mto_shipments.move_id = moves.id + INNER JOIN ppm_shipments ON ppm_shipments.shipment_id = mto_shipments.id + WHERE + (moves.status IN ('APPROVED', 'SERVICE COUNSELING COMPLETED')) + AND moves.show = $1 + AND moves.sc_assigned_id IS NULL` + + switch gbloc { + case "NAVY": + query += ` AND (service_members.affiliation in ('` + string(models.AffiliationNAVY) + `'))` + case "TVCB": + query += ` AND (service_members.affiliation in ('` + string(models.AffiliationMARINES) + `'))` + case "USCG": + query += ` AND (service_members.affiliation in ('` + string(models.AffiliationCOASTGUARD) + `'))` + default: + query += ` AND moves.closeout_office_id = '` + officeId.String() + `' + AND (service_members.affiliation NOT IN ('` + + string(models.AffiliationNAVY) + `', '` + + string(models.AffiliationMARINES) + `', '` + + string(models.AffiliationCOASTGUARD) + `'))` + } + + query += ` AND (ppm_shipments.status IN ($2)) + AND (orders.orders_type NOT IN ($3, $4, $5)) + GROUP BY moves.id, ppm_shipments.expected_departure_date + ORDER BY earliest_date ASC` + + err := appCtx.DB().RawQuery(query, + models.BoolPointer(true), + models.PPMShipmentStatusNeedsCloseout, + internalmessages.OrdersTypeBLUEBARK, + internalmessages.OrdersTypeWOUNDEDWARRIOR, + internalmessages.OrdersTypeSAFETY).All(&moves) + + if err != nil { + return nil, fmt.Errorf("error fetching moves for office: %s with error %w", officeId, err) + } + + if len(moves) < 1 { + return nil, nil + } + + return moves, nil +} + +func (f moveFetcherBulkAssignment) FetchMovesForBulkAssignmentTaskOrder(appCtx appcontext.AppContext, gbloc string, officeId uuid.UUID) ([]models.MoveWithEarliestDate, error) { + var moves []models.MoveWithEarliestDate + + err := appCtx.DB(). + RawQuery(`SELECT + moves.id, + MIN(LEAST( + COALESCE(mto_shipments.requested_pickup_date, '9999-12-31'), + COALESCE(mto_shipments.requested_delivery_date, '9999-12-31'), + COALESCE(ppm_shipments.expected_departure_date, '9999-12-31') + )) AS earliest_date + FROM moves + INNER JOIN orders ON orders.id = moves.orders_id + INNER JOIN service_members ON orders.service_member_id = service_members.id + INNER JOIN mto_shipments ON mto_shipments.move_id = moves.id + LEFT JOIN ppm_shipments ON ppm_shipments.shipment_id = mto_shipments.id + LEFT JOIN move_to_gbloc ON move_to_gbloc.move_id = moves.id + WHERE + (moves.status IN ('APPROVALS REQUESTED', 'SUBMITTED', 'SERVICE COUNSELING COMPLETED')) + AND moves.show = $1 + AND moves.too_assigned_id IS NULL + AND (orders.orders_type NOT IN ($2, $3, $4)) + AND service_members.affiliation != 'MARINES' + AND ((mto_shipments.shipment_type != $5 AND move_to_gbloc.gbloc = $6) OR (mto_shipments.shipment_type = $7 AND orders.gbloc = $8)) + GROUP BY moves.id + ORDER BY earliest_date ASC`, + models.BoolPointer(true), + internalmessages.OrdersTypeBLUEBARK, + internalmessages.OrdersTypeWOUNDEDWARRIOR, + internalmessages.OrdersTypeSAFETY, + models.MTOShipmentTypeHHGOutOfNTSDom, + gbloc, + models.MTOShipmentTypeHHGOutOfNTSDom, + gbloc). + All(&moves) + + if err != nil { + return nil, fmt.Errorf("error fetching moves for office: %s with error %w", officeId, err) + } + + if len(moves) < 1 { + return nil, nil + } + + return moves, nil +} diff --git a/src/components/Table/TableQueue.jsx b/src/components/Table/TableQueue.jsx index d657df5007f..230b40e96d1 100644 --- a/src/components/Table/TableQueue.jsx +++ b/src/components/Table/TableQueue.jsx @@ -27,6 +27,7 @@ import { getSelectionOptionLabel, } from 'components/Table/utils'; import { roleTypes } from 'constants/userRoles'; +import { SetBulkAssignmentSaveQueries } from 'hooks/queries'; const defaultPageSize = 20; const defaultPage = 1; @@ -320,7 +321,11 @@ const TableQueue = ({
{isBulkAssignModalVisible && ( - + )}
diff --git a/src/hooks/queries.js b/src/hooks/queries.js index ce469702359..16e9e3ea8cc 100644 --- a/src/hooks/queries.js +++ b/src/hooks/queries.js @@ -35,6 +35,7 @@ import { searchCustomers, getGBLOCs, getBulkAssignmentData, + postBulkAssignmentData, } from 'services/ghcApi'; import { getLoggedInUserQueries } from 'services/internalApi'; import { getPrimeSimulatorMove } from 'services/primeApi'; @@ -234,6 +235,21 @@ export const useBulkAssignmentQueries = (queueType) => { }; }; +export const SetBulkAssignmentSaveQueries = ({ queueType, bulkSaveData }) => { + const { data = {}, ...bulkAssignmentSaveDataQuery } = useQuery([{ queueType, bulkSaveData }], ({ queryKey }) => + postBulkAssignmentData(...queryKey), + ); + + const { isLoading, isError, isSuccess } = getQueriesStatus([bulkAssignmentSaveDataQuery]); + + return { + data, + isLoading, + isError, + isSuccess, + }; +}; + export const useEditShipmentQueries = (moveCode) => { // Get the orders info const { data: move = {}, ...moveQuery } = useQuery([MOVES, moveCode], ({ queryKey }) => getMove(...queryKey)); diff --git a/src/services/ghcApi.js b/src/services/ghcApi.js index aab855315c7..a9075de31fc 100644 --- a/src/services/ghcApi.js +++ b/src/services/ghcApi.js @@ -146,6 +146,10 @@ export async function getBulkAssignmentData(queueType) { return makeGHCRequest('queues.getBulkAssignmentData', { queueType }, { normalize: false }); } +export async function postBulkAssignmentData(queueType, data) { + return makeGHCRequest('queues.postBulkAssignmentData', { queueType, data }, { normalize: false }); +} + export async function createCustomerSupportRemarkForMove({ body, locator }) { return makeGHCRequest('customerSupportRemarks.createCustomerSupportRemarkForMove', { body, diff --git a/swagger-def/ghc.yaml b/swagger-def/ghc.yaml index 140db63b440..9bc10701432 100644 --- a/swagger-def/ghc.yaml +++ b/swagger-def/ghc.yaml @@ -3502,6 +3502,40 @@ paths: $ref: '#/responses/NotFound' '500': $ref: '#/responses/ServerError' + /queues/bulk-assignment/assign: + post: + summary: Assigns one or more moves to one or more office users + description: > + Supervisor office users are able to assign moves. This endpoint saves office user assignments to multiple moves. + operationId: postBulkAssignmentData + tags: + - queues + parameters: + - in: body + name: bulkAssignmentSavePayload + required: true + schema: + $ref: '#/definitions/BulkAssignmentSavePayload' + - in: query + name: queueType + type: string + description: A string corresponding to the queue type + enum: + - COUNSELING + - CLOSEOUT + - TASK_ORDER + - PAYMENT_REQUEST + responses: + '200': + description: Successfully returned bulk assignment data + schema: + $ref: '#/definitions/BulkAssignmentData' + '401': + $ref: '#/responses/PermissionDenied' + '404': + $ref: '#/responses/NotFound' + '500': + $ref: '#/responses/ServerError' /queues/counseling/origin-list: get: produces: @@ -7042,6 +7076,28 @@ definitions: $ref: '#/definitions/AvailableOfficeUsers' bulkAssignmentMoveIDs: $ref: '#/definitions/BulkAssignmentMoveIDs' + BulkAssignmentSavePayload: + type: object + properties: + userData: + type: array + items: + $ref: '#/definitions/BulkAssignmentForUser' + moveData: + type: array + items: + $ref: '#/definitions/BulkAssignmentMoveData' + BulkAssignmentForUser: + type: object + properties: + userId: + type: string + format: uuid + moveAssignments: + type: integer + BulkAssignmentMoveData: + format: uuid + type: string QueueMoves: type: array items: diff --git a/swagger/ghc.yaml b/swagger/ghc.yaml index 6b1aaa959f2..d55f4e4ec99 100644 --- a/swagger/ghc.yaml +++ b/swagger/ghc.yaml @@ -3634,6 +3634,41 @@ paths: $ref: '#/responses/NotFound' '500': $ref: '#/responses/ServerError' + /queues/bulk-assignment/assign: + post: + summary: Assigns one or more moves to one or more office users + description: > + Supervisor office users are able to assign moves. This endpoint saves + office user assignments to multiple moves. + operationId: postBulkAssignmentData + tags: + - queues + parameters: + - in: body + name: bulkAssignmentSavePayload + required: true + schema: + $ref: '#/definitions/BulkAssignmentSavePayload' + - in: query + name: queueType + type: string + description: A string corresponding to the queue type + enum: + - COUNSELING + - CLOSEOUT + - TASK_ORDER + - PAYMENT_REQUEST + responses: + '200': + description: Successfully returned bulk assignment data + schema: + $ref: '#/definitions/BulkAssignmentData' + '401': + $ref: '#/responses/PermissionDenied' + '404': + $ref: '#/responses/NotFound' + '500': + $ref: '#/responses/ServerError' /queues/counseling/origin-list: get: produces: @@ -7382,6 +7417,28 @@ definitions: $ref: '#/definitions/AvailableOfficeUsers' bulkAssignmentMoveIDs: $ref: '#/definitions/BulkAssignmentMoveIDs' + BulkAssignmentSavePayload: + type: object + properties: + userData: + type: array + items: + $ref: '#/definitions/BulkAssignmentForUser' + moveData: + type: array + items: + $ref: '#/definitions/BulkAssignmentMoveData' + BulkAssignmentForUser: + type: object + properties: + userId: + type: string + format: uuid + moveAssignments: + type: integer + BulkAssignmentMoveData: + format: uuid + type: string QueueMoves: type: array items: From 9192b9ec508f2ebcd4d4eb2e5044e79c1ee946c6 Mon Sep 17 00:00:00 2001 From: loganwc Date: Thu, 16 Jan 2025 16:15:51 +0000 Subject: [PATCH 02/35] most backend ok, frontend not ok --- src/components/Table/TableQueue.jsx | 5 +++-- src/hooks/queries.js | 16 -------------- .../ServicesCounselingQueue.jsx | 22 +++++++++++++++++++ src/services/ghcApi.js | 2 +- 4 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/components/Table/TableQueue.jsx b/src/components/Table/TableQueue.jsx index 230b40e96d1..8a1ca15ad3d 100644 --- a/src/components/Table/TableQueue.jsx +++ b/src/components/Table/TableQueue.jsx @@ -3,6 +3,7 @@ import { connect } from 'react-redux'; import { GridContainer, Button } from '@trussworks/react-uswds'; import { useTable, useFilters, usePagination, useSortBy } from 'react-table'; import PropTypes from 'prop-types'; +import { useMutation } from 'react-query'; import styles from './TableQueue.module.scss'; import TableCSVExportButton from './TableCSVExportButton'; @@ -27,7 +28,6 @@ import { getSelectionOptionLabel, } from 'components/Table/utils'; import { roleTypes } from 'constants/userRoles'; -import { SetBulkAssignmentSaveQueries } from 'hooks/queries'; const defaultPageSize = 20; const defaultPage = 1; @@ -57,6 +57,7 @@ const TableQueue = ({ isBulkAssignmentFFEnabled, officeUser, activeRole, + handleBulkAssignmentSave, }) => { const [isPageReload, setIsPageReload] = useState(true); useEffect(() => { @@ -323,7 +324,7 @@ const TableQueue = ({ {isBulkAssignModalVisible && ( )} diff --git a/src/hooks/queries.js b/src/hooks/queries.js index 16e9e3ea8cc..ce469702359 100644 --- a/src/hooks/queries.js +++ b/src/hooks/queries.js @@ -35,7 +35,6 @@ import { searchCustomers, getGBLOCs, getBulkAssignmentData, - postBulkAssignmentData, } from 'services/ghcApi'; import { getLoggedInUserQueries } from 'services/internalApi'; import { getPrimeSimulatorMove } from 'services/primeApi'; @@ -235,21 +234,6 @@ export const useBulkAssignmentQueries = (queueType) => { }; }; -export const SetBulkAssignmentSaveQueries = ({ queueType, bulkSaveData }) => { - const { data = {}, ...bulkAssignmentSaveDataQuery } = useQuery([{ queueType, bulkSaveData }], ({ queryKey }) => - postBulkAssignmentData(...queryKey), - ); - - const { isLoading, isError, isSuccess } = getQueriesStatus([bulkAssignmentSaveDataQuery]); - - return { - data, - isLoading, - isError, - isSuccess, - }; -}; - export const useEditShipmentQueries = (moveCode) => { // Get the orders info const { data: move = {}, ...moveQuery } = useQuery([MOVES, moveCode], ({ queryKey }) => getMove(...queryKey)); diff --git a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx index 977a9ebd9ba..755599d6878 100644 --- a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx +++ b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx @@ -3,6 +3,7 @@ import { generatePath, useNavigate, Navigate, useParams, NavLink } from 'react-r import { connect } from 'react-redux'; import { Button, Dropdown } from '@trussworks/react-uswds'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { useMutation, useQueryClient } from 'react-query'; import styles from './ServicesCounselingQueue.module.scss'; @@ -26,11 +27,13 @@ import { useUserQueries, useMoveSearchQueries, useCustomerSearchQueries, + useBulkAssignmentQueries, } from 'hooks/queries'; import { getServicesCounselingOriginLocations, getServicesCounselingQueue, getServicesCounselingPPMQueue, + saveBulkAssignmentData, } from 'services/ghcApi'; import { DATE_FORMAT_STRING, DEFAULT_EMPTY_VALUE, MOVE_STATUSES } from 'shared/constants'; import { formatDateFromIso, serviceMemberAgencyLabel } from 'utils/formatters'; @@ -513,6 +516,24 @@ const ServicesCounselingQueue = ({ navigate(generatePath(servicesCounselingRoutes.CREATE_CUSTOMER_PATH)); }; + const { bulkAssignmentData } = useBulkAssignmentQueries('COUNSELING'); + console.log(bulkAssignmentData); + + const queryClient = useQueryClient(); + const { mutate: mutateBulkAssignment } = useMutation(saveBulkAssignmentData, { + onSuccess: () => { + queryClient.setQueryData(['COUNSELING', {}], data); + console.log('success'); + // setAlertMessage('Bulk Assignment Successful'); + // setAlertType('success'); + }, + onError: () => { + console.log('error'); + // setAlertMessage('There was a problem cancelling the move. Please try again later.'); + // setAlertType('error'); + }, + }); + const [search, setSearch] = useState({ moveCode: null, dodID: null, customerName: null }); const [searchHappened, setSearchHappened] = useState(false); const counselorMoveCreateFeatureFlag = isBooleanFlagEnabled('counselor_move_create'); @@ -693,6 +714,7 @@ const ServicesCounselingQueue = ({ key={queueType} isSupervisor={supervisor} isBulkAssignmentFFEnabled={isBulkAssignmentFFEnabled} + handleBulkAssignmentSave={mutateBulkAssignment} />
); diff --git a/src/services/ghcApi.js b/src/services/ghcApi.js index a9075de31fc..3367a08565f 100644 --- a/src/services/ghcApi.js +++ b/src/services/ghcApi.js @@ -146,7 +146,7 @@ export async function getBulkAssignmentData(queueType) { return makeGHCRequest('queues.getBulkAssignmentData', { queueType }, { normalize: false }); } -export async function postBulkAssignmentData(queueType, data) { +export async function saveBulkAssignmentData(queueType, data) { return makeGHCRequest('queues.postBulkAssignmentData', { queueType, data }, { normalize: false }); } From 9da8897a48c4e4995f10cde05fbadf13c195e236 Mon Sep 17 00:00:00 2001 From: loganwc Date: Thu, 16 Jan 2025 19:01:51 +0000 Subject: [PATCH 03/35] fixed queryClient issue --- src/components/Table/TableQueue.jsx | 2 +- .../ServicesCounselingQueue/ServicesCounselingQueue.jsx | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/components/Table/TableQueue.jsx b/src/components/Table/TableQueue.jsx index 8a1ca15ad3d..2c2b3fea776 100644 --- a/src/components/Table/TableQueue.jsx +++ b/src/components/Table/TableQueue.jsx @@ -324,7 +324,7 @@ const TableQueue = ({ {isBulkAssignModalVisible && ( )} diff --git a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx index 755599d6878..e5b66389695 100644 --- a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx +++ b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx @@ -3,7 +3,7 @@ import { generatePath, useNavigate, Navigate, useParams, NavLink } from 'react-r import { connect } from 'react-redux'; import { Button, Dropdown } from '@trussworks/react-uswds'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { useMutation, useQueryClient } from 'react-query'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; import styles from './ServicesCounselingQueue.module.scss'; @@ -524,11 +524,9 @@ const ServicesCounselingQueue = ({ onSuccess: () => { queryClient.setQueryData(['COUNSELING', {}], data); console.log('success'); - // setAlertMessage('Bulk Assignment Successful'); - // setAlertType('success'); }, - onError: () => { - console.log('error'); + onError: (e) => { + console.log('error', e); // setAlertMessage('There was a problem cancelling the move. Please try again later.'); // setAlertType('error'); }, From e758dac06c8eb6f56990673b1662f9d2a6d265eb Mon Sep 17 00:00:00 2001 From: loganwc Date: Fri, 17 Jan 2025 17:33:55 +0000 Subject: [PATCH 04/35] backend is now correctly assigning moves to multiple users --- pkg/gen/ghcapi/configure_mymove.go | 10 +- pkg/gen/ghcapi/embedded_spec.go | 4 +- pkg/gen/ghcapi/ghcoperations/mymove_api.go | 24 +-- .../queues/post_bulk_assignment_data.go | 58 ------ .../post_bulk_assignment_data_responses.go | 194 ------------------ .../queues/save_bulk_assignment_data.go | 58 ++++++ ...> save_bulk_assignment_data_parameters.go} | 20 +- .../save_bulk_assignment_data_responses.go | 194 ++++++++++++++++++ ...> save_bulk_assignment_data_urlbuilder.go} | 22 +- pkg/handlers/ghcapi/api.go | 2 +- pkg/handlers/ghcapi/queues.go | 21 +- pkg/services/move.go | 2 +- pkg/services/move/move_assignment.go | 31 +-- .../BulkAssignment/BulkAssignmentModal.jsx | 13 ++ src/hooks/queries.js | 10 + .../ServicesCounselingQueue.jsx | 41 ++-- src/services/ghcApi.js | 8 +- swagger-def/ghc.yaml | 2 +- swagger/ghc.yaml | 2 +- 19 files changed, 369 insertions(+), 347 deletions(-) delete mode 100644 pkg/gen/ghcapi/ghcoperations/queues/post_bulk_assignment_data.go delete mode 100644 pkg/gen/ghcapi/ghcoperations/queues/post_bulk_assignment_data_responses.go create mode 100644 pkg/gen/ghcapi/ghcoperations/queues/save_bulk_assignment_data.go rename pkg/gen/ghcapi/ghcoperations/queues/{post_bulk_assignment_data_parameters.go => save_bulk_assignment_data_parameters.go} (83%) create mode 100644 pkg/gen/ghcapi/ghcoperations/queues/save_bulk_assignment_data_responses.go rename pkg/gen/ghcapi/ghcoperations/queues/{post_bulk_assignment_data_urlbuilder.go => save_bulk_assignment_data_urlbuilder.go} (72%) diff --git a/pkg/gen/ghcapi/configure_mymove.go b/pkg/gen/ghcapi/configure_mymove.go index 204cd592560..121d4c18ce7 100644 --- a/pkg/gen/ghcapi/configure_mymove.go +++ b/pkg/gen/ghcapi/configure_mymove.go @@ -412,11 +412,6 @@ func configureAPI(api *ghcoperations.MymoveAPI) http.Handler { return middleware.NotImplemented("operation move.MoveCanceler has not yet been implemented") }) } - if api.QueuesPostBulkAssignmentDataHandler == nil { - api.QueuesPostBulkAssignmentDataHandler = queues.PostBulkAssignmentDataHandlerFunc(func(params queues.PostBulkAssignmentDataParams) middleware.Responder { - return middleware.NotImplemented("operation queues.PostBulkAssignmentData has not yet been implemented") - }) - } if api.ShipmentRejectShipmentHandler == nil { api.ShipmentRejectShipmentHandler = shipment.RejectShipmentHandlerFunc(func(params shipment.RejectShipmentParams) middleware.Responder { return middleware.NotImplemented("operation shipment.RejectShipment has not yet been implemented") @@ -447,6 +442,11 @@ func configureAPI(api *ghcoperations.MymoveAPI) http.Handler { return middleware.NotImplemented("operation shipment.ReviewShipmentAddressUpdate has not yet been implemented") }) } + if api.QueuesSaveBulkAssignmentDataHandler == nil { + api.QueuesSaveBulkAssignmentDataHandler = queues.SaveBulkAssignmentDataHandlerFunc(func(params queues.SaveBulkAssignmentDataParams) middleware.Responder { + return middleware.NotImplemented("operation queues.SaveBulkAssignmentData has not yet been implemented") + }) + } if api.EvaluationReportsSaveEvaluationReportHandler == nil { api.EvaluationReportsSaveEvaluationReportHandler = evaluation_reports.SaveEvaluationReportHandlerFunc(func(params evaluation_reports.SaveEvaluationReportParams) middleware.Responder { return middleware.NotImplemented("operation evaluation_reports.SaveEvaluationReport has not yet been implemented") diff --git a/pkg/gen/ghcapi/embedded_spec.go b/pkg/gen/ghcapi/embedded_spec.go index 09d0577eb7d..9ced63a8129 100644 --- a/pkg/gen/ghcapi/embedded_spec.go +++ b/pkg/gen/ghcapi/embedded_spec.go @@ -4363,7 +4363,7 @@ func init() { "queues" ], "summary": "Assigns one or more moves to one or more office users", - "operationId": "postBulkAssignmentData", + "operationId": "saveBulkAssignmentData", "parameters": [ { "name": "bulkAssignmentSavePayload", @@ -20885,7 +20885,7 @@ func init() { "queues" ], "summary": "Assigns one or more moves to one or more office users", - "operationId": "postBulkAssignmentData", + "operationId": "saveBulkAssignmentData", "parameters": [ { "name": "bulkAssignmentSavePayload", diff --git a/pkg/gen/ghcapi/ghcoperations/mymove_api.go b/pkg/gen/ghcapi/ghcoperations/mymove_api.go index c167fce48c4..2278112e2aa 100644 --- a/pkg/gen/ghcapi/ghcoperations/mymove_api.go +++ b/pkg/gen/ghcapi/ghcoperations/mymove_api.go @@ -275,9 +275,6 @@ func NewMymoveAPI(spec *loads.Document) *MymoveAPI { MoveMoveCancelerHandler: move.MoveCancelerHandlerFunc(func(params move.MoveCancelerParams) middleware.Responder { return middleware.NotImplemented("operation move.MoveCanceler has not yet been implemented") }), - QueuesPostBulkAssignmentDataHandler: queues.PostBulkAssignmentDataHandlerFunc(func(params queues.PostBulkAssignmentDataParams) middleware.Responder { - return middleware.NotImplemented("operation queues.PostBulkAssignmentData has not yet been implemented") - }), ShipmentRejectShipmentHandler: shipment.RejectShipmentHandlerFunc(func(params shipment.RejectShipmentParams) middleware.Responder { return middleware.NotImplemented("operation shipment.RejectShipment has not yet been implemented") }), @@ -296,6 +293,9 @@ func NewMymoveAPI(spec *loads.Document) *MymoveAPI { ShipmentReviewShipmentAddressUpdateHandler: shipment.ReviewShipmentAddressUpdateHandlerFunc(func(params shipment.ReviewShipmentAddressUpdateParams) middleware.Responder { return middleware.NotImplemented("operation shipment.ReviewShipmentAddressUpdate has not yet been implemented") }), + QueuesSaveBulkAssignmentDataHandler: queues.SaveBulkAssignmentDataHandlerFunc(func(params queues.SaveBulkAssignmentDataParams) middleware.Responder { + return middleware.NotImplemented("operation queues.SaveBulkAssignmentData has not yet been implemented") + }), EvaluationReportsSaveEvaluationReportHandler: evaluation_reports.SaveEvaluationReportHandlerFunc(func(params evaluation_reports.SaveEvaluationReportParams) middleware.Responder { return middleware.NotImplemented("operation evaluation_reports.SaveEvaluationReport has not yet been implemented") }), @@ -577,8 +577,6 @@ type MymoveAPI struct { QueuesListPrimeMovesHandler queues.ListPrimeMovesHandler // MoveMoveCancelerHandler sets the operation handler for the move canceler operation MoveMoveCancelerHandler move.MoveCancelerHandler - // QueuesPostBulkAssignmentDataHandler sets the operation handler for the post bulk assignment data operation - QueuesPostBulkAssignmentDataHandler queues.PostBulkAssignmentDataHandler // ShipmentRejectShipmentHandler sets the operation handler for the reject shipment operation ShipmentRejectShipmentHandler shipment.RejectShipmentHandler // LinesOfAccountingRequestLineOfAccountingHandler sets the operation handler for the request line of accounting operation @@ -591,6 +589,8 @@ type MymoveAPI struct { ShipmentRequestShipmentReweighHandler shipment.RequestShipmentReweighHandler // ShipmentReviewShipmentAddressUpdateHandler sets the operation handler for the review shipment address update operation ShipmentReviewShipmentAddressUpdateHandler shipment.ReviewShipmentAddressUpdateHandler + // QueuesSaveBulkAssignmentDataHandler sets the operation handler for the save bulk assignment data operation + QueuesSaveBulkAssignmentDataHandler queues.SaveBulkAssignmentDataHandler // EvaluationReportsSaveEvaluationReportHandler sets the operation handler for the save evaluation report operation EvaluationReportsSaveEvaluationReportHandler evaluation_reports.SaveEvaluationReportHandler // CustomerSearchCustomersHandler sets the operation handler for the search customers operation @@ -944,9 +944,6 @@ func (o *MymoveAPI) Validate() error { if o.MoveMoveCancelerHandler == nil { unregistered = append(unregistered, "move.MoveCancelerHandler") } - if o.QueuesPostBulkAssignmentDataHandler == nil { - unregistered = append(unregistered, "queues.PostBulkAssignmentDataHandler") - } if o.ShipmentRejectShipmentHandler == nil { unregistered = append(unregistered, "shipment.RejectShipmentHandler") } @@ -965,6 +962,9 @@ func (o *MymoveAPI) Validate() error { if o.ShipmentReviewShipmentAddressUpdateHandler == nil { unregistered = append(unregistered, "shipment.ReviewShipmentAddressUpdateHandler") } + if o.QueuesSaveBulkAssignmentDataHandler == nil { + unregistered = append(unregistered, "queues.SaveBulkAssignmentDataHandler") + } if o.EvaluationReportsSaveEvaluationReportHandler == nil { unregistered = append(unregistered, "evaluation_reports.SaveEvaluationReportHandler") } @@ -1431,10 +1431,6 @@ func (o *MymoveAPI) initHandlerCache() { if o.handlers["POST"] == nil { o.handlers["POST"] = make(map[string]http.Handler) } - o.handlers["POST"]["/queues/bulk-assignment/assign"] = queues.NewPostBulkAssignmentData(o.context, o.QueuesPostBulkAssignmentDataHandler) - if o.handlers["POST"] == nil { - o.handlers["POST"] = make(map[string]http.Handler) - } o.handlers["POST"]["/shipments/{shipmentID}/reject"] = shipment.NewRejectShipment(o.context, o.ShipmentRejectShipmentHandler) if o.handlers["POST"] == nil { o.handlers["POST"] = make(map[string]http.Handler) @@ -1456,6 +1452,10 @@ func (o *MymoveAPI) initHandlerCache() { o.handlers["PATCH"] = make(map[string]http.Handler) } o.handlers["PATCH"]["/shipments/{shipmentID}/review-shipment-address-update"] = shipment.NewReviewShipmentAddressUpdate(o.context, o.ShipmentReviewShipmentAddressUpdateHandler) + if o.handlers["POST"] == nil { + o.handlers["POST"] = make(map[string]http.Handler) + } + o.handlers["POST"]["/queues/bulk-assignment/assign"] = queues.NewSaveBulkAssignmentData(o.context, o.QueuesSaveBulkAssignmentDataHandler) if o.handlers["PUT"] == nil { o.handlers["PUT"] = make(map[string]http.Handler) } diff --git a/pkg/gen/ghcapi/ghcoperations/queues/post_bulk_assignment_data.go b/pkg/gen/ghcapi/ghcoperations/queues/post_bulk_assignment_data.go deleted file mode 100644 index 4504e862f18..00000000000 --- a/pkg/gen/ghcapi/ghcoperations/queues/post_bulk_assignment_data.go +++ /dev/null @@ -1,58 +0,0 @@ -// Code generated by go-swagger; DO NOT EDIT. - -package queues - -// This file was generated by the swagger tool. -// Editing this file might prove futile when you re-run the generate command - -import ( - "net/http" - - "github.com/go-openapi/runtime/middleware" -) - -// PostBulkAssignmentDataHandlerFunc turns a function with the right signature into a post bulk assignment data handler -type PostBulkAssignmentDataHandlerFunc func(PostBulkAssignmentDataParams) middleware.Responder - -// Handle executing the request and returning a response -func (fn PostBulkAssignmentDataHandlerFunc) Handle(params PostBulkAssignmentDataParams) middleware.Responder { - return fn(params) -} - -// PostBulkAssignmentDataHandler interface for that can handle valid post bulk assignment data params -type PostBulkAssignmentDataHandler interface { - Handle(PostBulkAssignmentDataParams) middleware.Responder -} - -// NewPostBulkAssignmentData creates a new http.Handler for the post bulk assignment data operation -func NewPostBulkAssignmentData(ctx *middleware.Context, handler PostBulkAssignmentDataHandler) *PostBulkAssignmentData { - return &PostBulkAssignmentData{Context: ctx, Handler: handler} -} - -/* - PostBulkAssignmentData swagger:route POST /queues/bulk-assignment/assign queues postBulkAssignmentData - -# Assigns one or more moves to one or more office users - -Supervisor office users are able to assign moves. This endpoint saves office user assignments to multiple moves. -*/ -type PostBulkAssignmentData struct { - Context *middleware.Context - Handler PostBulkAssignmentDataHandler -} - -func (o *PostBulkAssignmentData) ServeHTTP(rw http.ResponseWriter, r *http.Request) { - route, rCtx, _ := o.Context.RouteInfo(r) - if rCtx != nil { - *r = *rCtx - } - var Params = NewPostBulkAssignmentDataParams() - if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params - o.Context.Respond(rw, r, route.Produces, route, err) - return - } - - res := o.Handler.Handle(Params) // actually handle the request - o.Context.Respond(rw, r, route.Produces, route, res) - -} diff --git a/pkg/gen/ghcapi/ghcoperations/queues/post_bulk_assignment_data_responses.go b/pkg/gen/ghcapi/ghcoperations/queues/post_bulk_assignment_data_responses.go deleted file mode 100644 index f3a676ea8b7..00000000000 --- a/pkg/gen/ghcapi/ghcoperations/queues/post_bulk_assignment_data_responses.go +++ /dev/null @@ -1,194 +0,0 @@ -// Code generated by go-swagger; DO NOT EDIT. - -package queues - -// This file was generated by the swagger tool. -// Editing this file might prove futile when you re-run the swagger generate command - -import ( - "net/http" - - "github.com/go-openapi/runtime" - - "github.com/transcom/mymove/pkg/gen/ghcmessages" -) - -// PostBulkAssignmentDataOKCode is the HTTP code returned for type PostBulkAssignmentDataOK -const PostBulkAssignmentDataOKCode int = 200 - -/* -PostBulkAssignmentDataOK Successfully returned bulk assignment data - -swagger:response postBulkAssignmentDataOK -*/ -type PostBulkAssignmentDataOK struct { - - /* - In: Body - */ - Payload *ghcmessages.BulkAssignmentData `json:"body,omitempty"` -} - -// NewPostBulkAssignmentDataOK creates PostBulkAssignmentDataOK with default headers values -func NewPostBulkAssignmentDataOK() *PostBulkAssignmentDataOK { - - return &PostBulkAssignmentDataOK{} -} - -// WithPayload adds the payload to the post bulk assignment data o k response -func (o *PostBulkAssignmentDataOK) WithPayload(payload *ghcmessages.BulkAssignmentData) *PostBulkAssignmentDataOK { - o.Payload = payload - return o -} - -// SetPayload sets the payload to the post bulk assignment data o k response -func (o *PostBulkAssignmentDataOK) SetPayload(payload *ghcmessages.BulkAssignmentData) { - o.Payload = payload -} - -// WriteResponse to the client -func (o *PostBulkAssignmentDataOK) 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 - } - } -} - -// PostBulkAssignmentDataUnauthorizedCode is the HTTP code returned for type PostBulkAssignmentDataUnauthorized -const PostBulkAssignmentDataUnauthorizedCode int = 401 - -/* -PostBulkAssignmentDataUnauthorized The request was denied - -swagger:response postBulkAssignmentDataUnauthorized -*/ -type PostBulkAssignmentDataUnauthorized struct { - - /* - In: Body - */ - Payload *ghcmessages.Error `json:"body,omitempty"` -} - -// NewPostBulkAssignmentDataUnauthorized creates PostBulkAssignmentDataUnauthorized with default headers values -func NewPostBulkAssignmentDataUnauthorized() *PostBulkAssignmentDataUnauthorized { - - return &PostBulkAssignmentDataUnauthorized{} -} - -// WithPayload adds the payload to the post bulk assignment data unauthorized response -func (o *PostBulkAssignmentDataUnauthorized) WithPayload(payload *ghcmessages.Error) *PostBulkAssignmentDataUnauthorized { - o.Payload = payload - return o -} - -// SetPayload sets the payload to the post bulk assignment data unauthorized response -func (o *PostBulkAssignmentDataUnauthorized) SetPayload(payload *ghcmessages.Error) { - o.Payload = payload -} - -// WriteResponse to the client -func (o *PostBulkAssignmentDataUnauthorized) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { - - rw.WriteHeader(401) - if o.Payload != nil { - payload := o.Payload - if err := producer.Produce(rw, payload); err != nil { - panic(err) // let the recovery middleware deal with this - } - } -} - -// PostBulkAssignmentDataNotFoundCode is the HTTP code returned for type PostBulkAssignmentDataNotFound -const PostBulkAssignmentDataNotFoundCode int = 404 - -/* -PostBulkAssignmentDataNotFound The requested resource wasn't found - -swagger:response postBulkAssignmentDataNotFound -*/ -type PostBulkAssignmentDataNotFound struct { - - /* - In: Body - */ - Payload *ghcmessages.Error `json:"body,omitempty"` -} - -// NewPostBulkAssignmentDataNotFound creates PostBulkAssignmentDataNotFound with default headers values -func NewPostBulkAssignmentDataNotFound() *PostBulkAssignmentDataNotFound { - - return &PostBulkAssignmentDataNotFound{} -} - -// WithPayload adds the payload to the post bulk assignment data not found response -func (o *PostBulkAssignmentDataNotFound) WithPayload(payload *ghcmessages.Error) *PostBulkAssignmentDataNotFound { - o.Payload = payload - return o -} - -// SetPayload sets the payload to the post bulk assignment data not found response -func (o *PostBulkAssignmentDataNotFound) SetPayload(payload *ghcmessages.Error) { - o.Payload = payload -} - -// WriteResponse to the client -func (o *PostBulkAssignmentDataNotFound) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { - - rw.WriteHeader(404) - if o.Payload != nil { - payload := o.Payload - if err := producer.Produce(rw, payload); err != nil { - panic(err) // let the recovery middleware deal with this - } - } -} - -// PostBulkAssignmentDataInternalServerErrorCode is the HTTP code returned for type PostBulkAssignmentDataInternalServerError -const PostBulkAssignmentDataInternalServerErrorCode int = 500 - -/* -PostBulkAssignmentDataInternalServerError A server error occurred - -swagger:response postBulkAssignmentDataInternalServerError -*/ -type PostBulkAssignmentDataInternalServerError struct { - - /* - In: Body - */ - Payload *ghcmessages.Error `json:"body,omitempty"` -} - -// NewPostBulkAssignmentDataInternalServerError creates PostBulkAssignmentDataInternalServerError with default headers values -func NewPostBulkAssignmentDataInternalServerError() *PostBulkAssignmentDataInternalServerError { - - return &PostBulkAssignmentDataInternalServerError{} -} - -// WithPayload adds the payload to the post bulk assignment data internal server error response -func (o *PostBulkAssignmentDataInternalServerError) WithPayload(payload *ghcmessages.Error) *PostBulkAssignmentDataInternalServerError { - o.Payload = payload - return o -} - -// SetPayload sets the payload to the post bulk assignment data internal server error response -func (o *PostBulkAssignmentDataInternalServerError) SetPayload(payload *ghcmessages.Error) { - o.Payload = payload -} - -// WriteResponse to the client -func (o *PostBulkAssignmentDataInternalServerError) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { - - rw.WriteHeader(500) - if o.Payload != nil { - payload := o.Payload - if err := producer.Produce(rw, payload); err != nil { - panic(err) // let the recovery middleware deal with this - } - } -} diff --git a/pkg/gen/ghcapi/ghcoperations/queues/save_bulk_assignment_data.go b/pkg/gen/ghcapi/ghcoperations/queues/save_bulk_assignment_data.go new file mode 100644 index 00000000000..32537ec144c --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/queues/save_bulk_assignment_data.go @@ -0,0 +1,58 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package queues + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" +) + +// SaveBulkAssignmentDataHandlerFunc turns a function with the right signature into a save bulk assignment data handler +type SaveBulkAssignmentDataHandlerFunc func(SaveBulkAssignmentDataParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn SaveBulkAssignmentDataHandlerFunc) Handle(params SaveBulkAssignmentDataParams) middleware.Responder { + return fn(params) +} + +// SaveBulkAssignmentDataHandler interface for that can handle valid save bulk assignment data params +type SaveBulkAssignmentDataHandler interface { + Handle(SaveBulkAssignmentDataParams) middleware.Responder +} + +// NewSaveBulkAssignmentData creates a new http.Handler for the save bulk assignment data operation +func NewSaveBulkAssignmentData(ctx *middleware.Context, handler SaveBulkAssignmentDataHandler) *SaveBulkAssignmentData { + return &SaveBulkAssignmentData{Context: ctx, Handler: handler} +} + +/* + SaveBulkAssignmentData swagger:route POST /queues/bulk-assignment/assign queues saveBulkAssignmentData + +# Assigns one or more moves to one or more office users + +Supervisor office users are able to assign moves. This endpoint saves office user assignments to multiple moves. +*/ +type SaveBulkAssignmentData struct { + Context *middleware.Context + Handler SaveBulkAssignmentDataHandler +} + +func (o *SaveBulkAssignmentData) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewSaveBulkAssignmentDataParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/pkg/gen/ghcapi/ghcoperations/queues/post_bulk_assignment_data_parameters.go b/pkg/gen/ghcapi/ghcoperations/queues/save_bulk_assignment_data_parameters.go similarity index 83% rename from pkg/gen/ghcapi/ghcoperations/queues/post_bulk_assignment_data_parameters.go rename to pkg/gen/ghcapi/ghcoperations/queues/save_bulk_assignment_data_parameters.go index 5e30050f10f..2135e56dde5 100644 --- a/pkg/gen/ghcapi/ghcoperations/queues/post_bulk_assignment_data_parameters.go +++ b/pkg/gen/ghcapi/ghcoperations/queues/save_bulk_assignment_data_parameters.go @@ -18,19 +18,19 @@ import ( "github.com/transcom/mymove/pkg/gen/ghcmessages" ) -// NewPostBulkAssignmentDataParams creates a new PostBulkAssignmentDataParams object +// NewSaveBulkAssignmentDataParams creates a new SaveBulkAssignmentDataParams object // // There are no default values defined in the spec. -func NewPostBulkAssignmentDataParams() PostBulkAssignmentDataParams { +func NewSaveBulkAssignmentDataParams() SaveBulkAssignmentDataParams { - return PostBulkAssignmentDataParams{} + return SaveBulkAssignmentDataParams{} } -// PostBulkAssignmentDataParams contains all the bound params for the post bulk assignment data operation +// SaveBulkAssignmentDataParams contains all the bound params for the save bulk assignment data operation // typically these are obtained from a http.Request // -// swagger:parameters postBulkAssignmentData -type PostBulkAssignmentDataParams struct { +// swagger:parameters saveBulkAssignmentData +type SaveBulkAssignmentDataParams struct { // HTTP Request Object HTTPRequest *http.Request `json:"-"` @@ -49,8 +49,8 @@ type PostBulkAssignmentDataParams struct { // 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 NewPostBulkAssignmentDataParams() beforehand. -func (o *PostBulkAssignmentDataParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { +// To ensure default values, the struct must have been initialized with NewSaveBulkAssignmentDataParams() beforehand. +func (o *SaveBulkAssignmentDataParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { var res []error o.HTTPRequest = r @@ -96,7 +96,7 @@ func (o *PostBulkAssignmentDataParams) BindRequest(r *http.Request, route *middl } // bindQueueType binds and validates parameter QueueType from query. -func (o *PostBulkAssignmentDataParams) bindQueueType(rawData []string, hasKey bool, formats strfmt.Registry) error { +func (o *SaveBulkAssignmentDataParams) bindQueueType(rawData []string, hasKey bool, formats strfmt.Registry) error { var raw string if len(rawData) > 0 { raw = rawData[len(rawData)-1] @@ -118,7 +118,7 @@ func (o *PostBulkAssignmentDataParams) bindQueueType(rawData []string, hasKey bo } // validateQueueType carries on validations for parameter QueueType -func (o *PostBulkAssignmentDataParams) validateQueueType(formats strfmt.Registry) error { +func (o *SaveBulkAssignmentDataParams) validateQueueType(formats strfmt.Registry) error { if err := validate.EnumCase("queueType", "query", *o.QueueType, []interface{}{"COUNSELING", "CLOSEOUT", "TASK_ORDER", "PAYMENT_REQUEST"}, true); err != nil { return err diff --git a/pkg/gen/ghcapi/ghcoperations/queues/save_bulk_assignment_data_responses.go b/pkg/gen/ghcapi/ghcoperations/queues/save_bulk_assignment_data_responses.go new file mode 100644 index 00000000000..5470e3abc10 --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/queues/save_bulk_assignment_data_responses.go @@ -0,0 +1,194 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package queues + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "github.com/transcom/mymove/pkg/gen/ghcmessages" +) + +// SaveBulkAssignmentDataOKCode is the HTTP code returned for type SaveBulkAssignmentDataOK +const SaveBulkAssignmentDataOKCode int = 200 + +/* +SaveBulkAssignmentDataOK Successfully returned bulk assignment data + +swagger:response saveBulkAssignmentDataOK +*/ +type SaveBulkAssignmentDataOK struct { + + /* + In: Body + */ + Payload *ghcmessages.BulkAssignmentData `json:"body,omitempty"` +} + +// NewSaveBulkAssignmentDataOK creates SaveBulkAssignmentDataOK with default headers values +func NewSaveBulkAssignmentDataOK() *SaveBulkAssignmentDataOK { + + return &SaveBulkAssignmentDataOK{} +} + +// WithPayload adds the payload to the save bulk assignment data o k response +func (o *SaveBulkAssignmentDataOK) WithPayload(payload *ghcmessages.BulkAssignmentData) *SaveBulkAssignmentDataOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the save bulk assignment data o k response +func (o *SaveBulkAssignmentDataOK) SetPayload(payload *ghcmessages.BulkAssignmentData) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *SaveBulkAssignmentDataOK) 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 + } + } +} + +// SaveBulkAssignmentDataUnauthorizedCode is the HTTP code returned for type SaveBulkAssignmentDataUnauthorized +const SaveBulkAssignmentDataUnauthorizedCode int = 401 + +/* +SaveBulkAssignmentDataUnauthorized The request was denied + +swagger:response saveBulkAssignmentDataUnauthorized +*/ +type SaveBulkAssignmentDataUnauthorized struct { + + /* + In: Body + */ + Payload *ghcmessages.Error `json:"body,omitempty"` +} + +// NewSaveBulkAssignmentDataUnauthorized creates SaveBulkAssignmentDataUnauthorized with default headers values +func NewSaveBulkAssignmentDataUnauthorized() *SaveBulkAssignmentDataUnauthorized { + + return &SaveBulkAssignmentDataUnauthorized{} +} + +// WithPayload adds the payload to the save bulk assignment data unauthorized response +func (o *SaveBulkAssignmentDataUnauthorized) WithPayload(payload *ghcmessages.Error) *SaveBulkAssignmentDataUnauthorized { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the save bulk assignment data unauthorized response +func (o *SaveBulkAssignmentDataUnauthorized) SetPayload(payload *ghcmessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *SaveBulkAssignmentDataUnauthorized) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(401) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// SaveBulkAssignmentDataNotFoundCode is the HTTP code returned for type SaveBulkAssignmentDataNotFound +const SaveBulkAssignmentDataNotFoundCode int = 404 + +/* +SaveBulkAssignmentDataNotFound The requested resource wasn't found + +swagger:response saveBulkAssignmentDataNotFound +*/ +type SaveBulkAssignmentDataNotFound struct { + + /* + In: Body + */ + Payload *ghcmessages.Error `json:"body,omitempty"` +} + +// NewSaveBulkAssignmentDataNotFound creates SaveBulkAssignmentDataNotFound with default headers values +func NewSaveBulkAssignmentDataNotFound() *SaveBulkAssignmentDataNotFound { + + return &SaveBulkAssignmentDataNotFound{} +} + +// WithPayload adds the payload to the save bulk assignment data not found response +func (o *SaveBulkAssignmentDataNotFound) WithPayload(payload *ghcmessages.Error) *SaveBulkAssignmentDataNotFound { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the save bulk assignment data not found response +func (o *SaveBulkAssignmentDataNotFound) SetPayload(payload *ghcmessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *SaveBulkAssignmentDataNotFound) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(404) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// SaveBulkAssignmentDataInternalServerErrorCode is the HTTP code returned for type SaveBulkAssignmentDataInternalServerError +const SaveBulkAssignmentDataInternalServerErrorCode int = 500 + +/* +SaveBulkAssignmentDataInternalServerError A server error occurred + +swagger:response saveBulkAssignmentDataInternalServerError +*/ +type SaveBulkAssignmentDataInternalServerError struct { + + /* + In: Body + */ + Payload *ghcmessages.Error `json:"body,omitempty"` +} + +// NewSaveBulkAssignmentDataInternalServerError creates SaveBulkAssignmentDataInternalServerError with default headers values +func NewSaveBulkAssignmentDataInternalServerError() *SaveBulkAssignmentDataInternalServerError { + + return &SaveBulkAssignmentDataInternalServerError{} +} + +// WithPayload adds the payload to the save bulk assignment data internal server error response +func (o *SaveBulkAssignmentDataInternalServerError) WithPayload(payload *ghcmessages.Error) *SaveBulkAssignmentDataInternalServerError { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the save bulk assignment data internal server error response +func (o *SaveBulkAssignmentDataInternalServerError) SetPayload(payload *ghcmessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *SaveBulkAssignmentDataInternalServerError) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(500) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/pkg/gen/ghcapi/ghcoperations/queues/post_bulk_assignment_data_urlbuilder.go b/pkg/gen/ghcapi/ghcoperations/queues/save_bulk_assignment_data_urlbuilder.go similarity index 72% rename from pkg/gen/ghcapi/ghcoperations/queues/post_bulk_assignment_data_urlbuilder.go rename to pkg/gen/ghcapi/ghcoperations/queues/save_bulk_assignment_data_urlbuilder.go index 67c41a60000..2d670454d24 100644 --- a/pkg/gen/ghcapi/ghcoperations/queues/post_bulk_assignment_data_urlbuilder.go +++ b/pkg/gen/ghcapi/ghcoperations/queues/save_bulk_assignment_data_urlbuilder.go @@ -11,8 +11,8 @@ import ( golangswaggerpaths "path" ) -// PostBulkAssignmentDataURL generates an URL for the post bulk assignment data operation -type PostBulkAssignmentDataURL struct { +// SaveBulkAssignmentDataURL generates an URL for the save bulk assignment data operation +type SaveBulkAssignmentDataURL struct { QueueType *string _basePath string @@ -23,7 +23,7 @@ type PostBulkAssignmentDataURL 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 *PostBulkAssignmentDataURL) WithBasePath(bp string) *PostBulkAssignmentDataURL { +func (o *SaveBulkAssignmentDataURL) WithBasePath(bp string) *SaveBulkAssignmentDataURL { o.SetBasePath(bp) return o } @@ -31,12 +31,12 @@ func (o *PostBulkAssignmentDataURL) WithBasePath(bp string) *PostBulkAssignmentD // 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 *PostBulkAssignmentDataURL) SetBasePath(bp string) { +func (o *SaveBulkAssignmentDataURL) SetBasePath(bp string) { o._basePath = bp } // Build a url path and query string -func (o *PostBulkAssignmentDataURL) Build() (*url.URL, error) { +func (o *SaveBulkAssignmentDataURL) Build() (*url.URL, error) { var _result url.URL var _path = "/queues/bulk-assignment/assign" @@ -63,7 +63,7 @@ func (o *PostBulkAssignmentDataURL) Build() (*url.URL, error) { } // Must is a helper function to panic when the url builder returns an error -func (o *PostBulkAssignmentDataURL) Must(u *url.URL, err error) *url.URL { +func (o *SaveBulkAssignmentDataURL) Must(u *url.URL, err error) *url.URL { if err != nil { panic(err) } @@ -74,17 +74,17 @@ func (o *PostBulkAssignmentDataURL) Must(u *url.URL, err error) *url.URL { } // String returns the string representation of the path with query string -func (o *PostBulkAssignmentDataURL) String() string { +func (o *SaveBulkAssignmentDataURL) String() string { return o.Must(o.Build()).String() } // BuildFull builds a full url with scheme, host, path and query string -func (o *PostBulkAssignmentDataURL) BuildFull(scheme, host string) (*url.URL, error) { +func (o *SaveBulkAssignmentDataURL) BuildFull(scheme, host string) (*url.URL, error) { if scheme == "" { - return nil, errors.New("scheme is required for a full url on PostBulkAssignmentDataURL") + return nil, errors.New("scheme is required for a full url on SaveBulkAssignmentDataURL") } if host == "" { - return nil, errors.New("host is required for a full url on PostBulkAssignmentDataURL") + return nil, errors.New("host is required for a full url on SaveBulkAssignmentDataURL") } base, err := o.Build() @@ -98,6 +98,6 @@ func (o *PostBulkAssignmentDataURL) BuildFull(scheme, host string) (*url.URL, er } // StringFull returns the string representation of a complete url -func (o *PostBulkAssignmentDataURL) StringFull(scheme, host string) string { +func (o *SaveBulkAssignmentDataURL) StringFull(scheme, host string) string { return o.Must(o.BuildFull(scheme, host)).String() } diff --git a/pkg/handlers/ghcapi/api.go b/pkg/handlers/ghcapi/api.go index 69ee70ad75a..2e8845934f7 100644 --- a/pkg/handlers/ghcapi/api.go +++ b/pkg/handlers/ghcapi/api.go @@ -536,7 +536,7 @@ func NewGhcAPIHandler(handlerConfig handlers.HandlerConfig) *ghcops.MymoveAPI { move.NewMoveFetcherBulkAssignment(), } - ghcAPI.QueuesPostBulkAssignmentDataHandler = PostBulkAssignmentDataHandler{ + ghcAPI.QueuesSaveBulkAssignmentDataHandler = SaveBulkAssignmentDataHandler{ handlerConfig, officeusercreator.NewOfficeUserFetcherPop(), move.NewMoveFetcher(), diff --git a/pkg/handlers/ghcapi/queues.go b/pkg/handlers/ghcapi/queues.go index d25073c84c9..0a8f890af56 100644 --- a/pkg/handlers/ghcapi/queues.go +++ b/pkg/handlers/ghcapi/queues.go @@ -617,16 +617,16 @@ func (h GetBulkAssignmentDataHandler) Handle( }) } -// PostBulkAssignmentDataHandler saves the bulk assignment data -type PostBulkAssignmentDataHandler struct { +// SaveBulkAssignmentDataHandler saves the bulk assignment data +type SaveBulkAssignmentDataHandler struct { handlers.HandlerConfig services.OfficeUserFetcherPop services.MoveFetcher services.MoveAssigner } -func (h PostBulkAssignmentDataHandler) Handle( - params queues.PostBulkAssignmentDataParams, +func (h SaveBulkAssignmentDataHandler) Handle( + params queues.SaveBulkAssignmentDataParams, ) middleware.Responder { return h.AuditableAppContextFromRequestWithErrors(params.HTTPRequest, func(appCtx appcontext.AppContext) (middleware.Responder, error) { @@ -656,17 +656,6 @@ func (h PostBulkAssignmentDataHandler) Handle( queueType := params.QueueType - // fetch the Services Counselors who work at their office - officeUsers, err := h.OfficeUserFetcherPop.FetchOfficeUsersWithWorkloadByRoleAndOffice( - appCtx, - roles.RoleTypeServicesCounselor, - officeUser.TransportationOfficeID, - ) - if err != nil { - appCtx.Logger().Error("Error retreiving office users", zap.Error(err)) - return queues.NewGetBulkAssignmentDataInternalServerError(), err - } - // fetch the moves available to be assigned to their office users movesForAssignment, err := h.MoveFetcher.FetchMovesByIdArray(appCtx, params.BulkAssignmentSavePayload.MoveData) if err != nil { @@ -674,7 +663,7 @@ func (h PostBulkAssignmentDataHandler) Handle( return queues.NewGetBulkAssignmentDataInternalServerError(), err } - _, err = h.MoveAssigner.BulkMoveAssignment(appCtx, *queueType, officeUsers, movesForAssignment) + _, err = h.MoveAssigner.BulkMoveAssignment(appCtx, *queueType, params.BulkAssignmentSavePayload.UserData, movesForAssignment) if err != nil { appCtx.Logger().Error("Error assigning moves", zap.Error(err)) return queues.NewGetBulkAssignmentDataInternalServerError(), err diff --git a/pkg/services/move.go b/pkg/services/move.go index d42ffe8cd99..a39656692e2 100644 --- a/pkg/services/move.go +++ b/pkg/services/move.go @@ -140,5 +140,5 @@ type CheckForLockedMovesAndUnlockHandler interface { // //go:generate mockery --name MoveAssigner type MoveAssigner interface { - BulkMoveAssignment(appCtx appcontext.AppContext, queueType string, officeUsers []models.OfficeUserWithWorkload, movesToAssign models.Moves) (*models.Moves, error) + BulkMoveAssignment(appCtx appcontext.AppContext, queueType string, officeUserData []*ghcmessages.BulkAssignmentForUser, movesToAssign models.Moves) (*models.Moves, error) } diff --git a/pkg/services/move/move_assignment.go b/pkg/services/move/move_assignment.go index 6ebff8a8833..da5a679f9b7 100644 --- a/pkg/services/move/move_assignment.go +++ b/pkg/services/move/move_assignment.go @@ -1,8 +1,10 @@ package move import ( + "github.com/gofrs/uuid" "github.com/transcom/mymove/pkg/appcontext" "github.com/transcom/mymove/pkg/apperror" + "github.com/transcom/mymove/pkg/gen/ghcmessages" "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/services" ) @@ -14,25 +16,27 @@ func NewMoveAssignerBulkAssignment() services.MoveAssigner { return &moveAssigner{} } -func (a moveAssigner) BulkMoveAssignment(appCtx appcontext.AppContext, queueType string, officeUsers []models.OfficeUserWithWorkload, movesToAssign models.Moves) (*models.Moves, error) { +func (a moveAssigner) BulkMoveAssignment(appCtx appcontext.AppContext, queueType string, officeUserData []*ghcmessages.BulkAssignmentForUser, movesToAssign models.Moves) (*models.Moves, error) { if len(movesToAssign) == 0 { return nil, apperror.NewBadDataError("No moves to assign") } transactionErr := appCtx.NewTransaction(func(txnAppCtx appcontext.AppContext) error { for _, move := range movesToAssign { - for _, officeUser := range officeUsers { - if officeUser.Workload > 0 { + for _, officeUser := range officeUserData { + if officeUser.MoveAssignments > 0 { + userId := uuid.FromStringOrNil(officeUser.UserID.String()) + switch queueType { case string(models.QueueTypeCounseling): - move.SCAssignedID = &officeUser.ID + move.SCAssignedID = &userId case string(models.QueueTypeCloseout): - move.SCAssignedID = &officeUser.ID + move.SCAssignedID = &userId case string(models.QueueTypeTaskOrder): - move.TOOAssignedID = &officeUser.ID + move.TOOAssignedID = &userId } - officeUser.Workload -= 1 + officeUser.MoveAssignments -= 1 verrs, err := appCtx.DB().ValidateAndUpdate(&move) if err != nil || verrs.HasAny() { @@ -53,16 +57,3 @@ func (a moveAssigner) BulkMoveAssignment(appCtx appcontext.AppContext, queueType return nil, nil } - -// { -// officeUsers: [ -// '1': 2, -// '2': 1, -// '3': 3, -// '4': 4, -// '5': 5, -// '6': 6, -// ] -// moveIds: [] -// queueType -// } diff --git a/src/components/BulkAssignment/BulkAssignmentModal.jsx b/src/components/BulkAssignment/BulkAssignmentModal.jsx index ca58c6814a2..2bcd90ebda1 100644 --- a/src/components/BulkAssignment/BulkAssignmentModal.jsx +++ b/src/components/BulkAssignment/BulkAssignmentModal.jsx @@ -18,6 +18,19 @@ export const BulkAssignmentModal = ({ onClose, onSubmit, title, content, submitT type="submit" data-testid="modalSubmitButton" onClick={() => onSubmit()} + // onSubmit({ + // queueType: 'COUNSELING', + // saveBulkAssignmentData: { + // userData: [ + // { + // userId: '1cc7ce3a-c4ef-4582-a58a-e70665b412b0', + // moveAssignments: 2, + // }, + // ], + // moveData: ['b3baf6ce-f43b-437c-85be-e1145c0ddb96', 'fee7f916-35a6-4c0b-9ea6-a1d8094b3ed3'], + // }, + // }) + // } > {submitText} diff --git a/src/hooks/queries.js b/src/hooks/queries.js index ce469702359..ebe07931a68 100644 --- a/src/hooks/queries.js +++ b/src/hooks/queries.js @@ -35,6 +35,7 @@ import { searchCustomers, getGBLOCs, getBulkAssignmentData, + saveBulkAssignmentData, } from 'services/ghcApi'; import { getLoggedInUserQueries } from 'services/internalApi'; import { getPrimeSimulatorMove } from 'services/primeApi'; @@ -586,6 +587,15 @@ export const useMovesQueueQueries = ({ }; }; +export const useBulkAssignmentSaveQueries = ({ queueType, bulkAssignmentSavePayload }) => { + const { data = {}, ...bulkAssignmentSaveQuery } = useQuery( + [{ queueType, bulkAssignmentSavePayload }], + ({ queryKey }) => saveBulkAssignmentData(...queryKey), + ); + const { isLoading, isError, isSuccess } = bulkAssignmentSaveQuery; + return { data, isLoading, isError, isSuccess }; +}; + export const useServicesCounselingQueuePPMQueries = ({ sort, order, diff --git a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx index e5b66389695..f382bdf1e57 100644 --- a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx +++ b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx @@ -28,6 +28,7 @@ import { useMoveSearchQueries, useCustomerSearchQueries, useBulkAssignmentQueries, + useBulkAssignmentSaveQueries, } from 'hooks/queries'; import { getServicesCounselingOriginLocations, @@ -519,18 +520,32 @@ const ServicesCounselingQueue = ({ const { bulkAssignmentData } = useBulkAssignmentQueries('COUNSELING'); console.log(bulkAssignmentData); - const queryClient = useQueryClient(); - const { mutate: mutateBulkAssignment } = useMutation(saveBulkAssignmentData, { - onSuccess: () => { - queryClient.setQueryData(['COUNSELING', {}], data); - console.log('success'); - }, - onError: (e) => { - console.log('error', e); - // setAlertMessage('There was a problem cancelling the move. Please try again later.'); - // setAlertType('error'); - }, - }); + // const queryClient = useQueryClient(); + // const { mutate: mutateBulkAssignment } = useMutation(saveBulkAssignmentData, { + // onSuccess: () => { + // queryClient.setQueryData( + // [ + // 'COUNSELING', + // { + // userData: [ + // { + // userId: '1cc7ce3a-c4ef-4582-a58a-e70665b412b0', + // moveAssignments: 2, + // }, + // ], + // moveData: ['b3baf6ce-f43b-437c-85be-e1145c0ddb96', 'fee7f916-35a6-4c0b-9ea6-a1d8094b3ed3'], + // }, + // ], + // data, + // ); + // console.log('success'); + // }, + // onError: (e) => { + // console.log('error', e); + // // setAlertMessage('There was a problem cancelling the move. Please try again later.'); + // // setAlertType('error'); + // }, + // }); const [search, setSearch] = useState({ moveCode: null, dodID: null, customerName: null }); const [searchHappened, setSearchHappened] = useState(false); @@ -712,7 +727,7 @@ const ServicesCounselingQueue = ({ key={queueType} isSupervisor={supervisor} isBulkAssignmentFFEnabled={isBulkAssignmentFFEnabled} - handleBulkAssignmentSave={mutateBulkAssignment} + handleBulkAssignmentSave={saveBulkAssignmentData} />
); diff --git a/src/services/ghcApi.js b/src/services/ghcApi.js index 3367a08565f..6fc2f9ec970 100644 --- a/src/services/ghcApi.js +++ b/src/services/ghcApi.js @@ -146,8 +146,12 @@ export async function getBulkAssignmentData(queueType) { return makeGHCRequest('queues.getBulkAssignmentData', { queueType }, { normalize: false }); } -export async function saveBulkAssignmentData(queueType, data) { - return makeGHCRequest('queues.postBulkAssignmentData', { queueType, data }, { normalize: false }); +export async function saveBulkAssignmentData(queueType, bulkAssignmentSavePayload) { + return makeGHCRequest( + 'queues.saveBulkAssignmentData', + { queueType, bulkAssignmentSavePayload }, + { normalize: false }, + ); } export async function createCustomerSupportRemarkForMove({ body, locator }) { diff --git a/swagger-def/ghc.yaml b/swagger-def/ghc.yaml index 9bc10701432..0050caf3de4 100644 --- a/swagger-def/ghc.yaml +++ b/swagger-def/ghc.yaml @@ -3507,7 +3507,7 @@ paths: summary: Assigns one or more moves to one or more office users description: > Supervisor office users are able to assign moves. This endpoint saves office user assignments to multiple moves. - operationId: postBulkAssignmentData + operationId: saveBulkAssignmentData tags: - queues parameters: diff --git a/swagger/ghc.yaml b/swagger/ghc.yaml index d55f4e4ec99..b6effb29435 100644 --- a/swagger/ghc.yaml +++ b/swagger/ghc.yaml @@ -3640,7 +3640,7 @@ paths: description: > Supervisor office users are able to assign moves. This endpoint saves office user assignments to multiple moves. - operationId: postBulkAssignmentData + operationId: saveBulkAssignmentData tags: - queues parameters: From 80fa2889a2d3f558ef3f5e4fdf6d7f7a5248011b Mon Sep 17 00:00:00 2001 From: loganwc Date: Mon, 20 Jan 2025 19:24:35 +0000 Subject: [PATCH 05/35] endpoint accepts payload now --- pkg/gen/ghcapi/embedded_spec.go | 12 ++ .../BulkAssignment/BulkAssignmentModal.jsx | 131 +++++++++++++----- .../BulkAssignmentModal.module.scss | 24 ++++ src/components/Table/TableQueue.jsx | 2 + src/hooks/queries.js | 9 -- .../ServicesCounselingQueue.jsx | 48 +++---- src/services/ghcApi.js | 2 +- swagger-def/ghc.yaml | 4 + swagger/ghc.yaml | 4 + 9 files changed, 166 insertions(+), 70 deletions(-) create mode 100644 src/components/BulkAssignment/BulkAssignmentModal.module.scss diff --git a/pkg/gen/ghcapi/embedded_spec.go b/pkg/gen/ghcapi/embedded_spec.go index 9ced63a8129..0110bcc62fc 100644 --- a/pkg/gen/ghcapi/embedded_spec.go +++ b/pkg/gen/ghcapi/embedded_spec.go @@ -4359,6 +4359,12 @@ func init() { "/queues/bulk-assignment/assign": { "post": { "description": "Supervisor office users are able to assign moves. This endpoint saves office user assignments to multiple moves.\n", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], "tags": [ "queues" ], @@ -20881,6 +20887,12 @@ func init() { "/queues/bulk-assignment/assign": { "post": { "description": "Supervisor office users are able to assign moves. This endpoint saves office user assignments to multiple moves.\n", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], "tags": [ "queues" ], diff --git a/src/components/BulkAssignment/BulkAssignmentModal.jsx b/src/components/BulkAssignment/BulkAssignmentModal.jsx index 2bcd90ebda1..582e5f35514 100644 --- a/src/components/BulkAssignment/BulkAssignmentModal.jsx +++ b/src/components/BulkAssignment/BulkAssignmentModal.jsx @@ -1,43 +1,114 @@ import React from 'react'; import PropTypes from 'prop-types'; import { Button } from '@trussworks/react-uswds'; +import { Formik } from 'formik'; + +import styles from './BulkAssignmentModal.module.scss'; import Modal, { ModalTitle, ModalClose, ModalActions, connectModal } from 'components/Modal/Modal'; +import { Form } from 'components/form'; + +const initialValues = { + userData: [ + // { + // userId: '045c3048-df9a-4d44-88ed-8cd6e2100e08', + // moveAssignments: 2, + // }, + // { + // userId: '4b1f2722-b0bf-4b16-b8c4-49b4e49ba42a', + // moveAssignments: 1, + // }, + ], + moveData: [], +}; -export const BulkAssignmentModal = ({ onClose, onSubmit, title, content, submitText, closeText }) => ( +export const BulkAssignmentModal = ({ onClose, onSubmit, title, submitText, closeText, bulkAssignmentData }) => ( onClose()} /> -

{title}

+

+ {title} ( + {bulkAssignmentData.bulkAssignmentMoveIDs == null ? 0 : bulkAssignmentData.bulkAssignmentMoveIDs.length}) +

-

{content}

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

+ {user.lastName}, {user.firstName} +

+
+

{user.workload || 0}

+
+ +
+ + + + +
+ +
); @@ -46,14 +117,12 @@ BulkAssignmentModal.propTypes = { onSubmit: PropTypes.func.isRequired, title: PropTypes.string, - content: PropTypes.string, submitText: PropTypes.string, closeText: PropTypes.string, }; BulkAssignmentModal.defaultProps = { title: 'Bulk Assignment', - content: 'Here we will display moves to be assigned in bulk.', submitText: 'Save', closeText: 'Cancel', }; diff --git a/src/components/BulkAssignment/BulkAssignmentModal.module.scss b/src/components/BulkAssignment/BulkAssignmentModal.module.scss new file mode 100644 index 00000000000..0d2b035c811 --- /dev/null +++ b/src/components/BulkAssignment/BulkAssignmentModal.module.scss @@ -0,0 +1,24 @@ +.BulkModal { + min-width: 650px !important; + overflow-y: auto; + max-height: 90vh; +} + +.BulkAssignmentTable { + table { + th { + max-width: 10px; + text-align: center; + } + .BulkAssignmentDataCenter { + text-align: center; + } + .BulkAssignmentAssignment { + width: 60px; + text-align: center; + } + tr { + width: 99%; + } + } +} diff --git a/src/components/Table/TableQueue.jsx b/src/components/Table/TableQueue.jsx index 2c2b3fea776..17e28e6879c 100644 --- a/src/components/Table/TableQueue.jsx +++ b/src/components/Table/TableQueue.jsx @@ -58,6 +58,7 @@ const TableQueue = ({ officeUser, activeRole, handleBulkAssignmentSave, + bulkAssignmentData, }) => { const [isPageReload, setIsPageReload] = useState(true); useEffect(() => { @@ -326,6 +327,7 @@ const TableQueue = ({ isOpen={isBulkAssignModalVisible} onSubmit={handleBulkAssignmentSave} onClose={handleCloseBulkAssignModal} + bulkAssignmentData={bulkAssignmentData || {}} /> )} diff --git a/src/hooks/queries.js b/src/hooks/queries.js index ebe07931a68..fffd50664b4 100644 --- a/src/hooks/queries.js +++ b/src/hooks/queries.js @@ -587,15 +587,6 @@ export const useMovesQueueQueries = ({ }; }; -export const useBulkAssignmentSaveQueries = ({ queueType, bulkAssignmentSavePayload }) => { - const { data = {}, ...bulkAssignmentSaveQuery } = useQuery( - [{ queueType, bulkAssignmentSavePayload }], - ({ queryKey }) => saveBulkAssignmentData(...queryKey), - ); - const { isLoading, isError, isSuccess } = bulkAssignmentSaveQuery; - return { data, isLoading, isError, isSuccess }; -}; - export const useServicesCounselingQueuePPMQueries = ({ sort, order, diff --git a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx index f382bdf1e57..9377683f1b3 100644 --- a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx +++ b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx @@ -28,7 +28,6 @@ import { useMoveSearchQueries, useCustomerSearchQueries, useBulkAssignmentQueries, - useBulkAssignmentSaveQueries, } from 'hooks/queries'; import { getServicesCounselingOriginLocations, @@ -517,35 +516,24 @@ const ServicesCounselingQueue = ({ navigate(generatePath(servicesCounselingRoutes.CREATE_CUSTOMER_PATH)); }; - const { bulkAssignmentData } = useBulkAssignmentQueries('COUNSELING'); - console.log(bulkAssignmentData); + const closeoutBulkAssignmentData = useBulkAssignmentQueries('CLOSEOUT'); + const counselingBulkAssignmentData = useBulkAssignmentQueries('COUNSELING'); // const queryClient = useQueryClient(); - // const { mutate: mutateBulkAssignment } = useMutation(saveBulkAssignmentData, { - // onSuccess: () => { - // queryClient.setQueryData( - // [ - // 'COUNSELING', - // { - // userData: [ - // { - // userId: '1cc7ce3a-c4ef-4582-a58a-e70665b412b0', - // moveAssignments: 2, - // }, - // ], - // moveData: ['b3baf6ce-f43b-437c-85be-e1145c0ddb96', 'fee7f916-35a6-4c0b-9ea6-a1d8094b3ed3'], - // }, - // ], - // data, - // ); - // console.log('success'); - // }, - // onError: (e) => { - // console.log('error', e); - // // setAlertMessage('There was a problem cancelling the move. Please try again later.'); - // // setAlertType('error'); - // }, - // }); + const { mutate: mutateBulkAssignment } = useMutation(saveBulkAssignmentData, { + onSuccess: () => { + // refetch queue + }, + onError: (e) => { + console.log('error', e); + // setAlertMessage('There was a problem cancelling the move. Please try again later.'); + // setAlertType('error'); + }, + }); + + const onSubmitBulk = (qType, bulkAssignmentSavePayload) => { + mutateBulkAssignment({ queueType: 'COUNSELING', bulkAssignmentSavePayload }); + }; const [search, setSearch] = useState({ moveCode: null, dodID: null, customerName: null }); const [searchHappened, setSearchHappened] = useState(false); @@ -698,6 +686,7 @@ const ServicesCounselingQueue = ({ key={queueType} isSupervisor={supervisor} isBulkAssignmentFFEnabled={isBulkAssignmentFFEnabled} + bulkAssignmentData={closeoutBulkAssignmentData.bulkAssignmentData || {}} /> ); @@ -727,7 +716,8 @@ const ServicesCounselingQueue = ({ key={queueType} isSupervisor={supervisor} isBulkAssignmentFFEnabled={isBulkAssignmentFFEnabled} - handleBulkAssignmentSave={saveBulkAssignmentData} + handleBulkAssignmentSave={onSubmitBulk} + bulkAssignmentData={counselingBulkAssignmentData.bulkAssignmentData || {}} /> ); diff --git a/src/services/ghcApi.js b/src/services/ghcApi.js index 6fc2f9ec970..6e1f8659e94 100644 --- a/src/services/ghcApi.js +++ b/src/services/ghcApi.js @@ -146,7 +146,7 @@ export async function getBulkAssignmentData(queueType) { return makeGHCRequest('queues.getBulkAssignmentData', { queueType }, { normalize: false }); } -export async function saveBulkAssignmentData(queueType, bulkAssignmentSavePayload) { +export async function saveBulkAssignmentData({ queueType, bulkAssignmentSavePayload }) { return makeGHCRequest( 'queues.saveBulkAssignmentData', { queueType, bulkAssignmentSavePayload }, diff --git a/swagger-def/ghc.yaml b/swagger-def/ghc.yaml index 0050caf3de4..ad1491e54c7 100644 --- a/swagger-def/ghc.yaml +++ b/swagger-def/ghc.yaml @@ -3504,6 +3504,10 @@ paths: $ref: '#/responses/ServerError' /queues/bulk-assignment/assign: post: + produces: + - application/json + consumes: + - application/json summary: Assigns one or more moves to one or more office users description: > Supervisor office users are able to assign moves. This endpoint saves office user assignments to multiple moves. diff --git a/swagger/ghc.yaml b/swagger/ghc.yaml index b6effb29435..b115341d4d1 100644 --- a/swagger/ghc.yaml +++ b/swagger/ghc.yaml @@ -3636,6 +3636,10 @@ paths: $ref: '#/responses/ServerError' /queues/bulk-assignment/assign: post: + produces: + - application/json + consumes: + - application/json summary: Assigns one or more moves to one or more office users description: > Supervisor office users are able to assign moves. This endpoint saves From c796ed4c54098ac853cd22fa6f402a6ba46bcc27 Mon Sep 17 00:00:00 2001 From: loganwc Date: Tue, 21 Jan 2025 15:51:13 +0000 Subject: [PATCH 06/35] broke the frontend --- pkg/gen/ghcapi/embedded_spec.go | 16 +- .../ghcmessages/bulk_assignment_for_user.go | 16 +- pkg/services/move/move_assignment.go | 8 +- .../BulkAssignment/BulkAssignmentModal.jsx | 196 ++++++++++-------- .../ServicesCounselingQueue.jsx | 3 +- src/services/ghcApi.js | 1 + swagger-def/ghc.yaml | 2 +- swagger/ghc.yaml | 2 +- 8 files changed, 131 insertions(+), 113 deletions(-) diff --git a/pkg/gen/ghcapi/embedded_spec.go b/pkg/gen/ghcapi/embedded_spec.go index 0110bcc62fc..d6bcf77ef13 100644 --- a/pkg/gen/ghcapi/embedded_spec.go +++ b/pkg/gen/ghcapi/embedded_spec.go @@ -6940,12 +6940,12 @@ func init() { "BulkAssignmentForUser": { "type": "object", "properties": { - "moveAssignments": { - "type": "integer" - }, - "userId": { + "id": { "type": "string", "format": "uuid" + }, + "moveAssignments": { + "type": "integer" } } }, @@ -23871,12 +23871,12 @@ func init() { "BulkAssignmentForUser": { "type": "object", "properties": { - "moveAssignments": { - "type": "integer" - }, - "userId": { + "id": { "type": "string", "format": "uuid" + }, + "moveAssignments": { + "type": "integer" } } }, diff --git a/pkg/gen/ghcmessages/bulk_assignment_for_user.go b/pkg/gen/ghcmessages/bulk_assignment_for_user.go index daa85ae1f91..57c74f2b034 100644 --- a/pkg/gen/ghcmessages/bulk_assignment_for_user.go +++ b/pkg/gen/ghcmessages/bulk_assignment_for_user.go @@ -19,19 +19,19 @@ import ( // swagger:model BulkAssignmentForUser type BulkAssignmentForUser struct { + // id + // Format: uuid + ID strfmt.UUID `json:"id,omitempty"` + // move assignments MoveAssignments int64 `json:"moveAssignments,omitempty"` - - // user Id - // Format: uuid - UserID strfmt.UUID `json:"userId,omitempty"` } // Validate validates this bulk assignment for user func (m *BulkAssignmentForUser) Validate(formats strfmt.Registry) error { var res []error - if err := m.validateUserID(formats); err != nil { + if err := m.validateID(formats); err != nil { res = append(res, err) } @@ -41,12 +41,12 @@ func (m *BulkAssignmentForUser) Validate(formats strfmt.Registry) error { return nil } -func (m *BulkAssignmentForUser) validateUserID(formats strfmt.Registry) error { - if swag.IsZero(m.UserID) { // not required +func (m *BulkAssignmentForUser) validateID(formats strfmt.Registry) error { + if swag.IsZero(m.ID) { // not required return nil } - if err := validate.FormatOf("userId", "body", "uuid", m.UserID.String(), formats); err != nil { + if err := validate.FormatOf("id", "body", "uuid", m.ID.String(), formats); err != nil { return err } diff --git a/pkg/services/move/move_assignment.go b/pkg/services/move/move_assignment.go index da5a679f9b7..35d66242141 100644 --- a/pkg/services/move/move_assignment.go +++ b/pkg/services/move/move_assignment.go @@ -25,15 +25,15 @@ func (a moveAssigner) BulkMoveAssignment(appCtx appcontext.AppContext, queueType for _, move := range movesToAssign { for _, officeUser := range officeUserData { if officeUser.MoveAssignments > 0 { - userId := uuid.FromStringOrNil(officeUser.UserID.String()) + officeUserId := uuid.FromStringOrNil(officeUser.ID.String()) switch queueType { case string(models.QueueTypeCounseling): - move.SCAssignedID = &userId + move.SCAssignedID = &officeUserId case string(models.QueueTypeCloseout): - move.SCAssignedID = &userId + move.SCAssignedID = &officeUserId case string(models.QueueTypeTaskOrder): - move.TOOAssignedID = &userId + move.TOOAssignedID = &officeUserId } officeUser.MoveAssignments -= 1 diff --git a/src/components/BulkAssignment/BulkAssignmentModal.jsx b/src/components/BulkAssignment/BulkAssignmentModal.jsx index 582e5f35514..7d073946c4a 100644 --- a/src/components/BulkAssignment/BulkAssignmentModal.jsx +++ b/src/components/BulkAssignment/BulkAssignmentModal.jsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import PropTypes from 'prop-types'; import { Button } from '@trussworks/react-uswds'; import { Formik } from 'formik'; @@ -22,95 +22,111 @@ const initialValues = { moveData: [], }; -export const BulkAssignmentModal = ({ onClose, onSubmit, title, submitText, closeText, bulkAssignmentData }) => ( - - onClose()} /> - -

- {title} ( - {bulkAssignmentData.bulkAssignmentMoveIDs == null ? 0 : bulkAssignmentData.bulkAssignmentMoveIDs.length}) -

-
-
- { - console.log(e); - return onSubmit(e); - }} - initialValues={initialValues} - > -
- - - - - - - {bulkAssignmentData?.availableOfficeUsers?.map((user) => { - return ( - - - - - - ); - })} -
UserWorkloadAssignment
-

- {user.lastName}, {user.firstName} -

-
-

{user.workload || 0}

-
- -
- - - - -
-
-
-
-); +export const BulkAssignmentModal = ({ onClose, onSubmit, title, submitText, closeText, bulkAssignmentData }) => { + // adds move data to the initialValues obj + useEffect(() => { + console.log(initialValues); + initialValues.moveData = bulkAssignmentData.bulkAssignmentMoveIDs; + console.log(initialValues); + }); + + return ( + + onClose()} /> + +

+ {title} ( + {bulkAssignmentData.bulkAssignmentMoveIDs == null ? 0 : bulkAssignmentData.bulkAssignmentMoveIDs.length}) +

+
+
+ { + onSubmit({ + bulkAssignmentSavePayload: values, + }); + }} + initialValues={initialValues} + > + {({ setValues, values }) => { + const addUserAssignment = (user) => { + let newUserAssignment; + if (user.target.value !== '') { + newUserAssignment = { + userId: user.target.id, + moveAssignments: user.target.value, + }; + } else { + newUserAssignment = { + userId: user.target.id, + moveAssignments: 0, + }; + } + + setValues({ + ...values, + userData: [newUserAssignment], + }); + }; + + return ( +
+ + + + + + + {bulkAssignmentData?.availableOfficeUsers?.map((user) => { + return ( + + + + + + ); + })} +
UserWorkloadAssignment
+

+ {user.lastName}, {user.firstName} +

+
+

{user.workload || 0}

+
+ +
+ + + + +
+ ); + }} +
+
+
+ ); +}; BulkAssignmentModal.propTypes = { onClose: PropTypes.func.isRequired, diff --git a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx index 9377683f1b3..f7446f16976 100644 --- a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx +++ b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx @@ -531,7 +531,8 @@ const ServicesCounselingQueue = ({ }, }); - const onSubmitBulk = (qType, bulkAssignmentSavePayload) => { + const onSubmitBulk = (bulkAssignmentSavePayload) => { + console.log(bulkAssignmentSavePayload); mutateBulkAssignment({ queueType: 'COUNSELING', bulkAssignmentSavePayload }); }; diff --git a/src/services/ghcApi.js b/src/services/ghcApi.js index 6e1f8659e94..93ec29ab5d3 100644 --- a/src/services/ghcApi.js +++ b/src/services/ghcApi.js @@ -147,6 +147,7 @@ export async function getBulkAssignmentData(queueType) { } export async function saveBulkAssignmentData({ queueType, bulkAssignmentSavePayload }) { + console.log(queueType, bulkAssignmentSavePayload); return makeGHCRequest( 'queues.saveBulkAssignmentData', { queueType, bulkAssignmentSavePayload }, diff --git a/swagger-def/ghc.yaml b/swagger-def/ghc.yaml index ad1491e54c7..83d9bb8cfa0 100644 --- a/swagger-def/ghc.yaml +++ b/swagger-def/ghc.yaml @@ -7094,7 +7094,7 @@ definitions: BulkAssignmentForUser: type: object properties: - userId: + id: type: string format: uuid moveAssignments: diff --git a/swagger/ghc.yaml b/swagger/ghc.yaml index b115341d4d1..6ef302b4c6c 100644 --- a/swagger/ghc.yaml +++ b/swagger/ghc.yaml @@ -7435,7 +7435,7 @@ definitions: BulkAssignmentForUser: type: object properties: - userId: + id: type: string format: uuid moveAssignments: From 1d4dffad932695488c098fc8be5011d1d84413d0 Mon Sep 17 00:00:00 2001 From: loganwc Date: Tue, 21 Jan 2025 19:59:58 +0000 Subject: [PATCH 07/35] mocks and backend tests --- pkg/handlers/ghcapi/queues.go | 18 +- pkg/handlers/ghcapi/queues_test.go | 132 +++++++++++ pkg/services/mocks/MoveAssigner.go | 61 ++++++ pkg/services/mocks/MoveFetcher.go | 34 ++- pkg/services/move/move_assignment.go | 1 + pkg/services/move/move_assignment_test.go | 205 ++++++++++++++++++ .../BulkAssignment/BulkAssignmentModal.jsx | 25 +-- .../BulkAssignmentModal.module.scss | 11 +- src/hooks/queries.js | 1 - 9 files changed, 455 insertions(+), 33 deletions(-) create mode 100644 pkg/services/mocks/MoveAssigner.go create mode 100644 pkg/services/move/move_assignment_test.go diff --git a/pkg/handlers/ghcapi/queues.go b/pkg/handlers/ghcapi/queues.go index 0a8f890af56..2abacadfffd 100644 --- a/pkg/handlers/ghcapi/queues.go +++ b/pkg/handlers/ghcapi/queues.go @@ -633,25 +633,25 @@ func (h SaveBulkAssignmentDataHandler) Handle( if !appCtx.Session().IsOfficeUser() { err := apperror.NewForbiddenError("not an office user") appCtx.Logger().Error("Must be an office user", zap.Error(err)) - return queues.NewGetBulkAssignmentDataUnauthorized(), err + return queues.NewSaveBulkAssignmentDataUnauthorized(), err } officeUser, err := h.OfficeUserFetcherPop.FetchOfficeUserByID(appCtx, appCtx.Session().OfficeUserID) if err != nil { appCtx.Logger().Error("Error retrieving office_user", zap.Error(err)) - return queues.NewGetBulkAssignmentDataNotFound(), err + return queues.NewSaveBulkAssignmentDataNotFound(), err } privileges, err := models.FetchPrivilegesForUser(appCtx.DB(), *officeUser.UserID) if err != nil { appCtx.Logger().Error("Error retreiving user privileges", zap.Error(err)) - return queues.NewGetBulkAssignmentDataNotFound(), err + return queues.NewSaveBulkAssignmentDataNotFound(), err } isSupervisor := privileges.HasPrivilege(models.PrivilegeTypeSupervisor) if !isSupervisor { appCtx.Logger().Error("Unauthorized", zap.Error(err)) - return queues.NewGetBulkAssignmentDataUnauthorized(), err + return queues.NewSaveBulkAssignmentDataUnauthorized(), err } queueType := params.QueueType @@ -660,7 +660,7 @@ func (h SaveBulkAssignmentDataHandler) Handle( movesForAssignment, err := h.MoveFetcher.FetchMovesByIdArray(appCtx, params.BulkAssignmentSavePayload.MoveData) if err != nil { appCtx.Logger().Error("Error retreiving moves for assignment", zap.Error(err)) - return queues.NewGetBulkAssignmentDataInternalServerError(), err + return queues.NewSaveBulkAssignmentDataInternalServerError(), err } _, err = h.MoveAssigner.BulkMoveAssignment(appCtx, *queueType, params.BulkAssignmentSavePayload.UserData, movesForAssignment) @@ -669,7 +669,13 @@ func (h SaveBulkAssignmentDataHandler) Handle( return queues.NewGetBulkAssignmentDataInternalServerError(), err } - return queues.NewGetBulkAssignmentDataOK().WithPayload(nil), nil + if err != nil { + appCtx.Logger(). + Error("error fetching list of moves for office user", zap.Error(err)) + return queues.NewGetServicesCounselingQueueInternalServerError(), err + } + + return queues.NewSaveBulkAssignmentDataOK().WithPayload(nil), nil }) } diff --git a/pkg/handlers/ghcapi/queues_test.go b/pkg/handlers/ghcapi/queues_test.go index c01f7628f14..e946edd7ac5 100644 --- a/pkg/handlers/ghcapi/queues_test.go +++ b/pkg/handlers/ghcapi/queues_test.go @@ -1743,3 +1743,135 @@ func (suite *HandlerSuite) TestGetBulkAssignmentDataHandler() { suite.Len(payload.BulkAssignmentMoveIDs, 1) }) } + +func (suite *HandlerSuite) TestSaveBulkAssignmentDataHandler() { + suite.Run("returns an unauthorized error when an attempt is made by a non supervisor", func() { + officeUser := factory.BuildOfficeUserWithPrivileges(suite.DB(), []factory.Customization{ + { + Model: models.OfficeUser{ + Email: "officeuser1@example.com", + }, + }, + { + Model: models.User{ + Roles: []roles.Role{ + { + RoleType: roles.RoleTypeServicesCounselor, + }, + }, + }, + }, + }, nil) + + transportationOffice := factory.BuildTransportationOffice(suite.DB(), nil, nil) + move := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusNeedsServiceCounseling, + }, + }, + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + }, nil) + + userData := []*ghcmessages.BulkAssignmentForUser{ + {ID: strfmt.UUID(officeUser.ID.String()), MoveAssignments: 1}, + } + moveData := []ghcmessages.BulkAssignmentMoveData{ghcmessages.BulkAssignmentMoveData(move.ID.String())} + + request := httptest.NewRequest("POST", "/queues/bulk-assignment/assign", nil) + request = suite.AuthenticateOfficeRequest(request, officeUser) + params := queues.SaveBulkAssignmentDataParams{ + HTTPRequest: request, + QueueType: models.StringPointer("COUNSELING"), + BulkAssignmentSavePayload: &ghcmessages.BulkAssignmentSavePayload{ + MoveData: moveData, + UserData: userData, + }, + } + handlerConfig := suite.HandlerConfig() + handler := SaveBulkAssignmentDataHandler{ + handlerConfig, + officeusercreator.NewOfficeUserFetcherPop(), + movefetcher.NewMoveFetcher(), + movefetcher.NewMoveAssignerBulkAssignment(), + } + response := handler.Handle(params) + suite.IsNotErrResponse(response) + suite.IsType(&queues.SaveBulkAssignmentDataUnauthorized{}, response) + }) + + suite.Run("successfully assigns bulk assignments", func() { + transportationOffice := factory.BuildTransportationOffice(suite.DB(), nil, nil) + + officeUser := factory.BuildOfficeUserWithPrivileges(suite.DB(), []factory.Customization{ + { + Model: models.OfficeUser{ + Email: "officeuser1@example.com", + Active: true, + }, + }, + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + { + Model: models.User{ + Privileges: []models.Privilege{ + { + PrivilegeType: models.PrivilegeTypeSupervisor, + }, + }, + Roles: []roles.Role{ + { + RoleType: roles.RoleTypeServicesCounselor, + }, + }, + }, + }, + }, nil) + + move := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusNeedsServiceCounseling, + }, + }, + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + }, nil) + + userData := []*ghcmessages.BulkAssignmentForUser{ + {ID: strfmt.UUID(officeUser.ID.String()), MoveAssignments: 1}, + } + moveData := []ghcmessages.BulkAssignmentMoveData{ghcmessages.BulkAssignmentMoveData(move.ID.String())} + + request := httptest.NewRequest("POST", "/queues/bulk-assignment/assign", nil) + request = suite.AuthenticateOfficeRequest(request, officeUser) + params := queues.SaveBulkAssignmentDataParams{ + HTTPRequest: request, + QueueType: models.StringPointer("COUNSELING"), + BulkAssignmentSavePayload: &ghcmessages.BulkAssignmentSavePayload{ + MoveData: moveData, + UserData: userData, + }, + } + handlerConfig := suite.HandlerConfig() + handler := SaveBulkAssignmentDataHandler{ + handlerConfig, + officeusercreator.NewOfficeUserFetcherPop(), + movefetcher.NewMoveFetcher(), + movefetcher.NewMoveAssignerBulkAssignment(), + } + response := handler.Handle(params) + suite.IsNotErrResponse(response) + suite.IsType(&queues.SaveBulkAssignmentDataOK{}, response) + }) +} diff --git a/pkg/services/mocks/MoveAssigner.go b/pkg/services/mocks/MoveAssigner.go new file mode 100644 index 00000000000..5aa23ae5697 --- /dev/null +++ b/pkg/services/mocks/MoveAssigner.go @@ -0,0 +1,61 @@ +// Code generated by mockery. DO NOT EDIT. + +package mocks + +import ( + appcontext "github.com/transcom/mymove/pkg/appcontext" + ghcmessages "github.com/transcom/mymove/pkg/gen/ghcmessages" + + mock "github.com/stretchr/testify/mock" + + models "github.com/transcom/mymove/pkg/models" +) + +// MoveAssigner is an autogenerated mock type for the MoveAssigner type +type MoveAssigner struct { + mock.Mock +} + +// BulkMoveAssignment provides a mock function with given fields: appCtx, queueType, officeUserData, movesToAssign +func (_m *MoveAssigner) BulkMoveAssignment(appCtx appcontext.AppContext, queueType string, officeUserData []*ghcmessages.BulkAssignmentForUser, movesToAssign models.Moves) (*models.Moves, error) { + ret := _m.Called(appCtx, queueType, officeUserData, movesToAssign) + + if len(ret) == 0 { + panic("no return value specified for BulkMoveAssignment") + } + + var r0 *models.Moves + var r1 error + if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, []*ghcmessages.BulkAssignmentForUser, models.Moves) (*models.Moves, error)); ok { + return rf(appCtx, queueType, officeUserData, movesToAssign) + } + if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, []*ghcmessages.BulkAssignmentForUser, models.Moves) *models.Moves); ok { + r0 = rf(appCtx, queueType, officeUserData, movesToAssign) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.Moves) + } + } + + if rf, ok := ret.Get(1).(func(appcontext.AppContext, string, []*ghcmessages.BulkAssignmentForUser, models.Moves) error); ok { + r1 = rf(appCtx, queueType, officeUserData, movesToAssign) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewMoveAssigner creates a new instance of MoveAssigner. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMoveAssigner(t interface { + mock.TestingT + Cleanup(func()) +}) *MoveAssigner { + mock := &MoveAssigner{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/services/mocks/MoveFetcher.go b/pkg/services/mocks/MoveFetcher.go index 22a26abbd68..454d022156b 100644 --- a/pkg/services/mocks/MoveFetcher.go +++ b/pkg/services/mocks/MoveFetcher.go @@ -3,8 +3,10 @@ package mocks import ( - mock "github.com/stretchr/testify/mock" appcontext "github.com/transcom/mymove/pkg/appcontext" + ghcmessages "github.com/transcom/mymove/pkg/gen/ghcmessages" + + mock "github.com/stretchr/testify/mock" models "github.com/transcom/mymove/pkg/models" @@ -46,6 +48,36 @@ func (_m *MoveFetcher) FetchMove(appCtx appcontext.AppContext, locator string, s return r0, r1 } +// FetchMovesByIdArray provides a mock function with given fields: appCtx, moveIds +func (_m *MoveFetcher) FetchMovesByIdArray(appCtx appcontext.AppContext, moveIds []ghcmessages.BulkAssignmentMoveData) (models.Moves, error) { + ret := _m.Called(appCtx, moveIds) + + if len(ret) == 0 { + panic("no return value specified for FetchMovesByIdArray") + } + + var r0 models.Moves + var r1 error + if rf, ok := ret.Get(0).(func(appcontext.AppContext, []ghcmessages.BulkAssignmentMoveData) (models.Moves, error)); ok { + return rf(appCtx, moveIds) + } + if rf, ok := ret.Get(0).(func(appcontext.AppContext, []ghcmessages.BulkAssignmentMoveData) models.Moves); ok { + r0 = rf(appCtx, moveIds) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(models.Moves) + } + } + + if rf, ok := ret.Get(1).(func(appcontext.AppContext, []ghcmessages.BulkAssignmentMoveData) error); ok { + r1 = rf(appCtx, moveIds) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // FetchMovesForPPTASReports provides a mock function with given fields: appCtx, params func (_m *MoveFetcher) FetchMovesForPPTASReports(appCtx appcontext.AppContext, params *services.MoveTaskOrderFetcherParams) (models.Moves, error) { ret := _m.Called(appCtx, params) diff --git a/pkg/services/move/move_assignment.go b/pkg/services/move/move_assignment.go index 35d66242141..a7b0c5290b0 100644 --- a/pkg/services/move/move_assignment.go +++ b/pkg/services/move/move_assignment.go @@ -2,6 +2,7 @@ package move import ( "github.com/gofrs/uuid" + "github.com/transcom/mymove/pkg/appcontext" "github.com/transcom/mymove/pkg/apperror" "github.com/transcom/mymove/pkg/gen/ghcmessages" diff --git a/pkg/services/move/move_assignment_test.go b/pkg/services/move/move_assignment_test.go new file mode 100644 index 00000000000..d9f612b9166 --- /dev/null +++ b/pkg/services/move/move_assignment_test.go @@ -0,0 +1,205 @@ +package move + +import ( + "github.com/go-openapi/strfmt" + + "github.com/transcom/mymove/pkg/factory" + "github.com/transcom/mymove/pkg/gen/ghcmessages" + "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/models/roles" +) + +func (suite *MoveServiceSuite) TestBulkMoveAssignment() { + moveAssigner := NewMoveAssignerBulkAssignment() + + setupTestData := func() (models.TransportationOffice, models.Move, models.Move, models.Move) { + transportationOffice := factory.BuildTransportationOffice(suite.DB(), nil, nil) + move1 := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusNeedsServiceCounseling, + }, + }, + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + }, nil) + + move2 := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusNeedsServiceCounseling, + }, + }, + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + }, nil) + + move3 := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusNeedsServiceCounseling, + }, + }, + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + }, nil) + + return transportationOffice, move1, move2, move3 + } + + suite.Run("successfully assigns multiple counseling moves to a SC user", func() { + transportationOffice, move1, move2, move3 := setupTestData() + + officeUser := factory.BuildOfficeUserWithPrivileges(suite.DB(), []factory.Customization{ + { + Model: models.OfficeUser{ + Email: "officeuser1@example.com", + Active: true, + }, + }, + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + { + Model: models.User{ + Privileges: []models.Privilege{ + { + PrivilegeType: models.PrivilegeTypeSupervisor, + }, + }, + Roles: []roles.Role{ + { + RoleType: roles.RoleTypeServicesCounselor, + }, + }, + }, + }, + }, nil) + + moves := []models.Move{move1, move2, move3} + userData := []*ghcmessages.BulkAssignmentForUser{ + {ID: strfmt.UUID(officeUser.ID.String()), MoveAssignments: 2}, + } + + _, err := moveAssigner.BulkMoveAssignment(suite.AppContextForTest(), string(models.QueueTypeCounseling), userData, moves) + suite.NoError(err) + + // reload move data to check assigned + suite.NoError(suite.DB().Reload(&move1)) + suite.NoError(suite.DB().Reload(&move2)) + suite.NoError(suite.DB().Reload(&move3)) + + suite.Equal(officeUser.ID, *move1.SCAssignedID) + suite.Equal(officeUser.ID, *move2.SCAssignedID) + suite.Nil(move3.SCAssignedID) + }) + + suite.Run("successfully assigns multiple closeout moves to a SC user", func() { + transportationOffice, move1, move2, move3 := setupTestData() + + officeUser := factory.BuildOfficeUserWithPrivileges(suite.DB(), []factory.Customization{ + { + Model: models.OfficeUser{ + Email: "officeuser1@example.com", + Active: true, + }, + }, + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + { + Model: models.User{ + Privileges: []models.Privilege{ + { + PrivilegeType: models.PrivilegeTypeSupervisor, + }, + }, + Roles: []roles.Role{ + { + RoleType: roles.RoleTypeServicesCounselor, + }, + }, + }, + }, + }, nil) + + moves := []models.Move{move1, move2, move3} + userData := []*ghcmessages.BulkAssignmentForUser{ + {ID: strfmt.UUID(officeUser.ID.String()), MoveAssignments: 2}, + } + + _, err := moveAssigner.BulkMoveAssignment(suite.AppContextForTest(), string(models.QueueTypeCloseout), userData, moves) + suite.NoError(err) + + // reload move data to check assigned + suite.NoError(suite.DB().Reload(&move1)) + suite.NoError(suite.DB().Reload(&move2)) + suite.NoError(suite.DB().Reload(&move3)) + + suite.Equal(officeUser.ID, *move1.SCAssignedID) + suite.Equal(officeUser.ID, *move2.SCAssignedID) + suite.Nil(move3.SCAssignedID) + }) + + suite.Run("successfully assigns multiple task order moves to a TOO user", func() { + transportationOffice, move1, move2, move3 := setupTestData() + + officeUser := factory.BuildOfficeUserWithPrivileges(suite.DB(), []factory.Customization{ + { + Model: models.OfficeUser{ + Email: "officeuser1@example.com", + Active: true, + }, + }, + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + { + Model: models.User{ + Privileges: []models.Privilege{ + { + PrivilegeType: models.PrivilegeTypeSupervisor, + }, + }, + Roles: []roles.Role{ + { + RoleType: roles.RoleTypeTOO, + }, + }, + }, + }, + }, nil) + + moves := []models.Move{move1, move2, move3} + userData := []*ghcmessages.BulkAssignmentForUser{ + {ID: strfmt.UUID(officeUser.ID.String()), MoveAssignments: 2}, + } + + _, err := moveAssigner.BulkMoveAssignment(suite.AppContextForTest(), string(models.QueueTypeTaskOrder), userData, moves) + suite.NoError(err) + + // reload move data to check assigned + suite.NoError(suite.DB().Reload(&move1)) + suite.NoError(suite.DB().Reload(&move2)) + suite.NoError(suite.DB().Reload(&move3)) + + suite.Equal(officeUser.ID, *move1.TOOAssignedID) + suite.Equal(officeUser.ID, *move2.TOOAssignedID) + suite.Nil(move3.SCAssignedID) + }) +} diff --git a/src/components/BulkAssignment/BulkAssignmentModal.jsx b/src/components/BulkAssignment/BulkAssignmentModal.jsx index 7d073946c4a..3842e10663a 100644 --- a/src/components/BulkAssignment/BulkAssignmentModal.jsx +++ b/src/components/BulkAssignment/BulkAssignmentModal.jsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; import { Button } from '@trussworks/react-uswds'; import { Formik } from 'formik'; @@ -9,26 +9,13 @@ import Modal, { ModalTitle, ModalClose, ModalActions, connectModal } from 'compo import { Form } from 'components/form'; const initialValues = { - userData: [ - // { - // userId: '045c3048-df9a-4d44-88ed-8cd6e2100e08', - // moveAssignments: 2, - // }, - // { - // userId: '4b1f2722-b0bf-4b16-b8c4-49b4e49ba42a', - // moveAssignments: 1, - // }, - ], + userData: [], moveData: [], }; export const BulkAssignmentModal = ({ onClose, onSubmit, title, submitText, closeText, bulkAssignmentData }) => { // adds move data to the initialValues obj - useEffect(() => { - console.log(initialValues); - initialValues.moveData = bulkAssignmentData.bulkAssignmentMoveIDs; - console.log(initialValues); - }); + initialValues.moveData = bulkAssignmentData.bulkAssignmentMoveIDs; return ( @@ -42,9 +29,8 @@ export const BulkAssignmentModal = ({ onClose, onSubmit, title, submitText, clos
{ - onSubmit({ - bulkAssignmentSavePayload: values, - }); + const bulkAssignmentSavePayload = values; + onSubmit({ bulkAssignmentSavePayload }); }} initialValues={initialValues} > @@ -106,7 +92,6 @@ export const BulkAssignmentModal = ({ onClose, onSubmit, title, submitText, clos className="usa-button--destructive" type="submit" data-testid="modalSubmitButton" - onClick={(bulkAssignmentSavePayload) => onSubmit(bulkAssignmentSavePayload)} > {submitText} diff --git a/src/components/BulkAssignment/BulkAssignmentModal.module.scss b/src/components/BulkAssignment/BulkAssignmentModal.module.scss index 0d2b035c811..28af55e0c74 100644 --- a/src/components/BulkAssignment/BulkAssignmentModal.module.scss +++ b/src/components/BulkAssignment/BulkAssignmentModal.module.scss @@ -14,11 +14,12 @@ text-align: center; } .BulkAssignmentAssignment { - width: 60px; - text-align: center; - } - tr { - width: 99%; + width: 60px; + text-align: center; } } + + form { + display: inherit; + } } diff --git a/src/hooks/queries.js b/src/hooks/queries.js index fffd50664b4..ce469702359 100644 --- a/src/hooks/queries.js +++ b/src/hooks/queries.js @@ -35,7 +35,6 @@ import { searchCustomers, getGBLOCs, getBulkAssignmentData, - saveBulkAssignmentData, } from 'services/ghcApi'; import { getLoggedInUserQueries } from 'services/internalApi'; import { getPrimeSimulatorMove } from 'services/primeApi'; From 65721b9adfa5ee0dc60c4ef5156f69488faebd94 Mon Sep 17 00:00:00 2001 From: Paul Stonebraker Date: Tue, 21 Jan 2025 22:31:37 +0000 Subject: [PATCH 08/35] fixed issues with save bulk assignment endpoint --- pkg/gen/ghcapi/embedded_spec.go | 44 +++++++------- .../save_bulk_assignment_data_parameters.go | 44 -------------- .../save_bulk_assignment_data_urlbuilder.go | 16 ----- .../bulk_assignment_save_payload.go | 58 +++++++++++++++++++ pkg/handlers/ghcapi/queues.go | 8 ++- .../BulkAssignment/BulkAssignmentModal.jsx | 4 +- .../ServicesCounselingQueue.jsx | 2 +- src/services/ghcApi.js | 8 +-- swagger-def/ghc.yaml | 17 +++--- swagger/ghc.yaml | 17 +++--- 10 files changed, 104 insertions(+), 114 deletions(-) diff --git a/pkg/gen/ghcapi/embedded_spec.go b/pkg/gen/ghcapi/embedded_spec.go index d6bcf77ef13..d0abe6ace1a 100644 --- a/pkg/gen/ghcapi/embedded_spec.go +++ b/pkg/gen/ghcapi/embedded_spec.go @@ -4378,18 +4378,6 @@ func init() { "schema": { "$ref": "#/definitions/BulkAssignmentSavePayload" } - }, - { - "enum": [ - "COUNSELING", - "CLOSEOUT", - "TASK_ORDER", - "PAYMENT_REQUEST" - ], - "type": "string", - "description": "A string corresponding to the queue type", - "name": "queueType", - "in": "query" } ], "responses": { @@ -6973,6 +6961,16 @@ func init() { "$ref": "#/definitions/BulkAssignmentMoveData" } }, + "queueType": { + "description": "A string corresponding to the queue type", + "type": "string", + "enum": [ + "COUNSELING", + "CLOSEOUT", + "TASK_ORDER", + "PAYMENT_REQUEST" + ] + }, "userData": { "type": "array", "items": { @@ -20906,18 +20904,6 @@ func init() { "schema": { "$ref": "#/definitions/BulkAssignmentSavePayload" } - }, - { - "enum": [ - "COUNSELING", - "CLOSEOUT", - "TASK_ORDER", - "PAYMENT_REQUEST" - ], - "type": "string", - "description": "A string corresponding to the queue type", - "name": "queueType", - "in": "query" } ], "responses": { @@ -23904,6 +23890,16 @@ func init() { "$ref": "#/definitions/BulkAssignmentMoveData" } }, + "queueType": { + "description": "A string corresponding to the queue type", + "type": "string", + "enum": [ + "COUNSELING", + "CLOSEOUT", + "TASK_ORDER", + "PAYMENT_REQUEST" + ] + }, "userData": { "type": "array", "items": { diff --git a/pkg/gen/ghcapi/ghcoperations/queues/save_bulk_assignment_data_parameters.go b/pkg/gen/ghcapi/ghcoperations/queues/save_bulk_assignment_data_parameters.go index 2135e56dde5..87d3a9ca0f8 100644 --- a/pkg/gen/ghcapi/ghcoperations/queues/save_bulk_assignment_data_parameters.go +++ b/pkg/gen/ghcapi/ghcoperations/queues/save_bulk_assignment_data_parameters.go @@ -12,7 +12,6 @@ import ( "github.com/go-openapi/errors" "github.com/go-openapi/runtime" "github.com/go-openapi/runtime/middleware" - "github.com/go-openapi/strfmt" "github.com/go-openapi/validate" "github.com/transcom/mymove/pkg/gen/ghcmessages" @@ -40,10 +39,6 @@ type SaveBulkAssignmentDataParams struct { In: body */ BulkAssignmentSavePayload *ghcmessages.BulkAssignmentSavePayload - /*A string corresponding to the queue type - In: query - */ - QueueType *string } // BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface @@ -55,8 +50,6 @@ func (o *SaveBulkAssignmentDataParams) BindRequest(r *http.Request, route *middl o.HTTPRequest = r - qs := runtime.Values(r.URL.Query()) - if runtime.HasBody(r) { defer r.Body.Close() var body ghcmessages.BulkAssignmentSavePayload @@ -84,45 +77,8 @@ func (o *SaveBulkAssignmentDataParams) BindRequest(r *http.Request, route *middl } else { res = append(res, errors.Required("bulkAssignmentSavePayload", "body", "")) } - - qQueueType, qhkQueueType, _ := qs.GetOK("queueType") - if err := o.bindQueueType(qQueueType, qhkQueueType, route.Formats); err != nil { - res = append(res, err) - } if len(res) > 0 { return errors.CompositeValidationError(res...) } return nil } - -// bindQueueType binds and validates parameter QueueType from query. -func (o *SaveBulkAssignmentDataParams) bindQueueType(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.QueueType = &raw - - if err := o.validateQueueType(formats); err != nil { - return err - } - - return nil -} - -// validateQueueType carries on validations for parameter QueueType -func (o *SaveBulkAssignmentDataParams) validateQueueType(formats strfmt.Registry) error { - - if err := validate.EnumCase("queueType", "query", *o.QueueType, []interface{}{"COUNSELING", "CLOSEOUT", "TASK_ORDER", "PAYMENT_REQUEST"}, true); err != nil { - return err - } - - return nil -} diff --git a/pkg/gen/ghcapi/ghcoperations/queues/save_bulk_assignment_data_urlbuilder.go b/pkg/gen/ghcapi/ghcoperations/queues/save_bulk_assignment_data_urlbuilder.go index 2d670454d24..d021ae6b61c 100644 --- a/pkg/gen/ghcapi/ghcoperations/queues/save_bulk_assignment_data_urlbuilder.go +++ b/pkg/gen/ghcapi/ghcoperations/queues/save_bulk_assignment_data_urlbuilder.go @@ -13,11 +13,7 @@ import ( // SaveBulkAssignmentDataURL generates an URL for the save bulk assignment data operation type SaveBulkAssignmentDataURL struct { - QueueType *string - _basePath string - // avoid unkeyed usage - _ struct{} } // WithBasePath sets the base path for this url builder, only required when it's different from the @@ -47,18 +43,6 @@ func (o *SaveBulkAssignmentDataURL) Build() (*url.URL, error) { } _result.Path = golangswaggerpaths.Join(_basePath, _path) - qs := make(url.Values) - - var queueTypeQ string - if o.QueueType != nil { - queueTypeQ = *o.QueueType - } - if queueTypeQ != "" { - qs.Set("queueType", queueTypeQ) - } - - _result.RawQuery = qs.Encode() - return &_result, nil } diff --git a/pkg/gen/ghcmessages/bulk_assignment_save_payload.go b/pkg/gen/ghcmessages/bulk_assignment_save_payload.go index 83b5d4c8ba1..376206bdf7c 100644 --- a/pkg/gen/ghcmessages/bulk_assignment_save_payload.go +++ b/pkg/gen/ghcmessages/bulk_assignment_save_payload.go @@ -7,11 +7,13 @@ package ghcmessages import ( "context" + "encoding/json" "strconv" "github.com/go-openapi/errors" "github.com/go-openapi/strfmt" "github.com/go-openapi/swag" + "github.com/go-openapi/validate" ) // BulkAssignmentSavePayload bulk assignment save payload @@ -22,6 +24,10 @@ type BulkAssignmentSavePayload struct { // move data MoveData []BulkAssignmentMoveData `json:"moveData"` + // A string corresponding to the queue type + // Enum: [COUNSELING CLOSEOUT TASK_ORDER PAYMENT_REQUEST] + QueueType string `json:"queueType,omitempty"` + // user data UserData []*BulkAssignmentForUser `json:"userData"` } @@ -34,6 +40,10 @@ func (m *BulkAssignmentSavePayload) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validateQueueType(formats); err != nil { + res = append(res, err) + } + if err := m.validateUserData(formats); err != nil { res = append(res, err) } @@ -65,6 +75,54 @@ func (m *BulkAssignmentSavePayload) validateMoveData(formats strfmt.Registry) er return nil } +var bulkAssignmentSavePayloadTypeQueueTypePropEnum []interface{} + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["COUNSELING","CLOSEOUT","TASK_ORDER","PAYMENT_REQUEST"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + bulkAssignmentSavePayloadTypeQueueTypePropEnum = append(bulkAssignmentSavePayloadTypeQueueTypePropEnum, v) + } +} + +const ( + + // BulkAssignmentSavePayloadQueueTypeCOUNSELING captures enum value "COUNSELING" + BulkAssignmentSavePayloadQueueTypeCOUNSELING string = "COUNSELING" + + // BulkAssignmentSavePayloadQueueTypeCLOSEOUT captures enum value "CLOSEOUT" + BulkAssignmentSavePayloadQueueTypeCLOSEOUT string = "CLOSEOUT" + + // BulkAssignmentSavePayloadQueueTypeTASKORDER captures enum value "TASK_ORDER" + BulkAssignmentSavePayloadQueueTypeTASKORDER string = "TASK_ORDER" + + // BulkAssignmentSavePayloadQueueTypePAYMENTREQUEST captures enum value "PAYMENT_REQUEST" + BulkAssignmentSavePayloadQueueTypePAYMENTREQUEST string = "PAYMENT_REQUEST" +) + +// prop value enum +func (m *BulkAssignmentSavePayload) validateQueueTypeEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, bulkAssignmentSavePayloadTypeQueueTypePropEnum, true); err != nil { + return err + } + return nil +} + +func (m *BulkAssignmentSavePayload) validateQueueType(formats strfmt.Registry) error { + if swag.IsZero(m.QueueType) { // not required + return nil + } + + // value enum + if err := m.validateQueueTypeEnum("queueType", "body", m.QueueType); err != nil { + return err + } + + return nil +} + func (m *BulkAssignmentSavePayload) validateUserData(formats strfmt.Registry) error { if swag.IsZero(m.UserData) { // not required return nil diff --git a/pkg/handlers/ghcapi/queues.go b/pkg/handlers/ghcapi/queues.go index 2abacadfffd..1071835086b 100644 --- a/pkg/handlers/ghcapi/queues.go +++ b/pkg/handlers/ghcapi/queues.go @@ -654,16 +654,18 @@ func (h SaveBulkAssignmentDataHandler) Handle( return queues.NewSaveBulkAssignmentDataUnauthorized(), err } - queueType := params.QueueType + queueType := params.BulkAssignmentSavePayload.QueueType + moveData := params.BulkAssignmentSavePayload.MoveData + userData := params.BulkAssignmentSavePayload.UserData // fetch the moves available to be assigned to their office users - movesForAssignment, err := h.MoveFetcher.FetchMovesByIdArray(appCtx, params.BulkAssignmentSavePayload.MoveData) + movesForAssignment, err := h.MoveFetcher.FetchMovesByIdArray(appCtx, moveData) if err != nil { appCtx.Logger().Error("Error retreiving moves for assignment", zap.Error(err)) return queues.NewSaveBulkAssignmentDataInternalServerError(), err } - _, err = h.MoveAssigner.BulkMoveAssignment(appCtx, *queueType, params.BulkAssignmentSavePayload.UserData, movesForAssignment) + _, err = h.MoveAssigner.BulkMoveAssignment(appCtx, queueType, userData, movesForAssignment) if err != nil { appCtx.Logger().Error("Error assigning moves", zap.Error(err)) return queues.NewGetBulkAssignmentDataInternalServerError(), err diff --git a/src/components/BulkAssignment/BulkAssignmentModal.jsx b/src/components/BulkAssignment/BulkAssignmentModal.jsx index 3842e10663a..1a90474de0c 100644 --- a/src/components/BulkAssignment/BulkAssignmentModal.jsx +++ b/src/components/BulkAssignment/BulkAssignmentModal.jsx @@ -39,8 +39,8 @@ export const BulkAssignmentModal = ({ onClose, onSubmit, title, submitText, clos let newUserAssignment; if (user.target.value !== '') { newUserAssignment = { - userId: user.target.id, - moveAssignments: user.target.value, + ID: user.target.id, + moveAssignments: +user.target.value, }; } else { newUserAssignment = { diff --git a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx index f7446f16976..f163e18a7e3 100644 --- a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx +++ b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx @@ -533,7 +533,7 @@ const ServicesCounselingQueue = ({ const onSubmitBulk = (bulkAssignmentSavePayload) => { console.log(bulkAssignmentSavePayload); - mutateBulkAssignment({ queueType: 'COUNSELING', bulkAssignmentSavePayload }); + mutateBulkAssignment({ queueType: 'COUNSELING', ...bulkAssignmentSavePayload }); }; const [search, setSearch] = useState({ moveCode: null, dodID: null, customerName: null }); diff --git a/src/services/ghcApi.js b/src/services/ghcApi.js index 93ec29ab5d3..b7c695cda35 100644 --- a/src/services/ghcApi.js +++ b/src/services/ghcApi.js @@ -147,12 +147,8 @@ export async function getBulkAssignmentData(queueType) { } export async function saveBulkAssignmentData({ queueType, bulkAssignmentSavePayload }) { - console.log(queueType, bulkAssignmentSavePayload); - return makeGHCRequest( - 'queues.saveBulkAssignmentData', - { queueType, bulkAssignmentSavePayload }, - { normalize: false }, - ); + const body = { queueType, ...bulkAssignmentSavePayload }; + return makeGHCRequest('queues.saveBulkAssignmentData', { bulkAssignmentSavePayload: body }, { normalize: false }); } export async function createCustomerSupportRemarkForMove({ body, locator }) { diff --git a/swagger-def/ghc.yaml b/swagger-def/ghc.yaml index 83d9bb8cfa0..9446ef6651c 100644 --- a/swagger-def/ghc.yaml +++ b/swagger-def/ghc.yaml @@ -3520,15 +3520,6 @@ paths: required: true schema: $ref: '#/definitions/BulkAssignmentSavePayload' - - in: query - name: queueType - type: string - description: A string corresponding to the queue type - enum: - - COUNSELING - - CLOSEOUT - - TASK_ORDER - - PAYMENT_REQUEST responses: '200': description: Successfully returned bulk assignment data @@ -7091,6 +7082,14 @@ definitions: type: array items: $ref: '#/definitions/BulkAssignmentMoveData' + queueType: + type: string + description: A string corresponding to the queue type + enum: + - COUNSELING + - CLOSEOUT + - TASK_ORDER + - PAYMENT_REQUEST BulkAssignmentForUser: type: object properties: diff --git a/swagger/ghc.yaml b/swagger/ghc.yaml index 6ef302b4c6c..122a7498694 100644 --- a/swagger/ghc.yaml +++ b/swagger/ghc.yaml @@ -3653,15 +3653,6 @@ paths: required: true schema: $ref: '#/definitions/BulkAssignmentSavePayload' - - in: query - name: queueType - type: string - description: A string corresponding to the queue type - enum: - - COUNSELING - - CLOSEOUT - - TASK_ORDER - - PAYMENT_REQUEST responses: '200': description: Successfully returned bulk assignment data @@ -7432,6 +7423,14 @@ definitions: type: array items: $ref: '#/definitions/BulkAssignmentMoveData' + queueType: + type: string + description: A string corresponding to the queue type + enum: + - COUNSELING + - CLOSEOUT + - TASK_ORDER + - PAYMENT_REQUEST BulkAssignmentForUser: type: object properties: From 2b512a54c3bd49b6ab083add34e1f6c9ba98e21f Mon Sep 17 00:00:00 2001 From: loganwc Date: Wed, 22 Jan 2025 20:36:37 +0000 Subject: [PATCH 09/35] assign now works and reloads page to fetch fresh data --- .../BulkAssignment/BulkAssignmentModal.jsx | 50 ++++++++++--------- .../ServicesCounselingQueue.jsx | 33 ++++++++---- 2 files changed, 51 insertions(+), 32 deletions(-) diff --git a/src/components/BulkAssignment/BulkAssignmentModal.jsx b/src/components/BulkAssignment/BulkAssignmentModal.jsx index 1a90474de0c..af5f501e332 100644 --- a/src/components/BulkAssignment/BulkAssignmentModal.jsx +++ b/src/components/BulkAssignment/BulkAssignmentModal.jsx @@ -31,30 +31,11 @@ export const BulkAssignmentModal = ({ onClose, onSubmit, title, submitText, clos onSubmit={(values) => { const bulkAssignmentSavePayload = values; onSubmit({ bulkAssignmentSavePayload }); + onClose(); }} initialValues={initialValues} > - {({ setValues, values }) => { - const addUserAssignment = (user) => { - let newUserAssignment; - if (user.target.value !== '') { - newUserAssignment = { - ID: user.target.id, - moveAssignments: +user.target.value, - }; - } else { - newUserAssignment = { - userId: user.target.id, - moveAssignments: 0, - }; - } - - setValues({ - ...values, - userData: [newUserAssignment], - }); - }; - + {({ handleChange, setValues, values }) => { return (
@@ -63,7 +44,7 @@ export const BulkAssignmentModal = ({ onClose, onSubmit, title, submitText, clos - {bulkAssignmentData?.availableOfficeUsers?.map((user) => { + {bulkAssignmentData?.availableOfficeUsers?.map((user, i) => { return ( diff --git a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx index f163e18a7e3..2b87996ed41 100644 --- a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx +++ b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx @@ -1,9 +1,9 @@ import React, { useCallback, useEffect, useState, useContext } from 'react'; import { generatePath, useNavigate, Navigate, useParams, NavLink } from 'react-router-dom'; import { connect } from 'react-redux'; -import { Button, Dropdown } from '@trussworks/react-uswds'; +import { Button, Dropdown, Grid } from '@trussworks/react-uswds'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { useMutation } from '@tanstack/react-query'; import styles from './ServicesCounselingQueue.module.scss'; @@ -55,6 +55,7 @@ import MultiSelectTypeAheadCheckBoxFilter from 'components/Table/Filters/MutliSe import handleQueueAssignment from 'utils/queues'; import { selectLoggedInUser } from 'store/entities/selectors'; import SelectedGblocContext from 'components/Office/GblocSwitcher/SelectedGblocContext'; +import Alert from 'types/alert'; export const counselingColumns = (moveLockFlag, originLocationList, supervisor, isQueueManagementEnabled) => { const cols = [ @@ -516,23 +517,28 @@ const ServicesCounselingQueue = ({ navigate(generatePath(servicesCounselingRoutes.CREATE_CUSTOMER_PATH)); }; + const [errorMessage, setErrorMessage] = useState(null); + const [alertMessage, setAlertMessage] = useState(null); + const [alertType, setAlertType] = useState('success'); + const closeoutBulkAssignmentData = useBulkAssignmentQueries('CLOSEOUT'); const counselingBulkAssignmentData = useBulkAssignmentQueries('COUNSELING'); - // const queryClient = useQueryClient(); const { mutate: mutateBulkAssignment } = useMutation(saveBulkAssignmentData, { onSuccess: () => { - // refetch queue + setAlertMessage('Bulk moves successfully assigned.'); + setAlertType('success'); + + // reload page to refetch queue + window.location.reload(); }, - onError: (e) => { - console.log('error', e); - // setAlertMessage('There was a problem cancelling the move. Please try again later.'); - // setAlertType('error'); + onError: () => { + setAlertMessage('There was a problem assigning the move. Please try again later.'); + setAlertType('error'); }, }); const onSubmitBulk = (bulkAssignmentSavePayload) => { - console.log(bulkAssignmentSavePayload); mutateBulkAssignment({ queueType: 'COUNSELING', ...bulkAssignmentSavePayload }); }; @@ -631,6 +637,15 @@ const ServicesCounselingQueue = ({
{renderNavBar()} + + {alertMessage && ( + + + {alertMessage} + + + )} +

Search for a move

From 995d010a7f968b5e87810ba9aaca7ce78b343eed Mon Sep 17 00:00:00 2001 From: loganwc Date: Fri, 24 Jan 2025 17:33:43 +0000 Subject: [PATCH 10/35] now fetches data when modal is opened --- .../BulkAssignment/BulkAssignmentModal.jsx | 27 +++++++++++-- src/components/Table/TableQueue.jsx | 5 +-- src/pages/Office/MoveQueue/MoveQueue.jsx | 16 +++++++- .../ServicesCounselingQueue.jsx | 38 +++++-------------- 4 files changed, 49 insertions(+), 37 deletions(-) diff --git a/src/components/BulkAssignment/BulkAssignmentModal.jsx b/src/components/BulkAssignment/BulkAssignmentModal.jsx index af5f501e332..9556d2e2ddc 100644 --- a/src/components/BulkAssignment/BulkAssignmentModal.jsx +++ b/src/components/BulkAssignment/BulkAssignmentModal.jsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import PropTypes from 'prop-types'; import { Button } from '@trussworks/react-uswds'; import { Formik } from 'formik'; @@ -7,15 +7,34 @@ import styles from './BulkAssignmentModal.module.scss'; import Modal, { ModalTitle, ModalClose, ModalActions, connectModal } from 'components/Modal/Modal'; import { Form } from 'components/form'; +import { getBulkAssignmentData } from 'services/ghcApi'; +import { milmoveLogger } from 'utils/milmoveLog'; const initialValues = { userData: [], moveData: [], }; -export const BulkAssignmentModal = ({ onClose, onSubmit, title, submitText, closeText, bulkAssignmentData }) => { +export const BulkAssignmentModal = ({ onClose, onSubmit, title, submitText, closeText, queueType }) => { + // fetch bulk assignment data + const [bulkAssignmentData, setBulkAssignmentData] = useState(null); + + useEffect(() => { + const fetchData = async () => { + try { + getBulkAssignmentData(queueType).then((data) => { + setBulkAssignmentData(data); + }); + } catch (err) { + milmoveLogger.error('Error fetching bulk assignment data:', err); + } + }; + + fetchData(); + }, [queueType]); + // adds move data to the initialValues obj - initialValues.moveData = bulkAssignmentData.bulkAssignmentMoveIDs; + initialValues.moveData = bulkAssignmentData?.bulkAssignmentMoveIDs; return ( @@ -23,7 +42,7 @@ export const BulkAssignmentModal = ({ onClose, onSubmit, title, submitText, clos

{title} ( - {bulkAssignmentData.bulkAssignmentMoveIDs == null ? 0 : bulkAssignmentData.bulkAssignmentMoveIDs.length}) + {bulkAssignmentData?.bulkAssignmentMoveIDs == null ? 0 : bulkAssignmentData?.bulkAssignmentMoveIDs?.length})

diff --git a/src/components/Table/TableQueue.jsx b/src/components/Table/TableQueue.jsx index 17e28e6879c..37289e0273a 100644 --- a/src/components/Table/TableQueue.jsx +++ b/src/components/Table/TableQueue.jsx @@ -3,7 +3,6 @@ import { connect } from 'react-redux'; import { GridContainer, Button } from '@trussworks/react-uswds'; import { useTable, useFilters, usePagination, useSortBy } from 'react-table'; import PropTypes from 'prop-types'; -import { useMutation } from 'react-query'; import styles from './TableQueue.module.scss'; import TableCSVExportButton from './TableCSVExportButton'; @@ -58,7 +57,7 @@ const TableQueue = ({ officeUser, activeRole, handleBulkAssignmentSave, - bulkAssignmentData, + queueType, }) => { const [isPageReload, setIsPageReload] = useState(true); useEffect(() => { @@ -327,7 +326,7 @@ const TableQueue = ({ isOpen={isBulkAssignModalVisible} onSubmit={handleBulkAssignmentSave} onClose={handleCloseBulkAssignModal} - bulkAssignmentData={bulkAssignmentData || {}} + queueType={queueType} /> )} diff --git a/src/pages/Office/MoveQueue/MoveQueue.jsx b/src/pages/Office/MoveQueue/MoveQueue.jsx index bc156ffb44c..0ef76074f71 100644 --- a/src/pages/Office/MoveQueue/MoveQueue.jsx +++ b/src/pages/Office/MoveQueue/MoveQueue.jsx @@ -2,12 +2,13 @@ import React, { useCallback, useEffect, useState } from 'react'; import { useNavigate, NavLink, useParams, Navigate, generatePath } from 'react-router-dom'; import { Dropdown } from '@trussworks/react-uswds'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { useMutation } from '@tanstack/react-query'; import styles from './MoveQueue.module.scss'; import { createHeader } from 'components/Table/utils'; import { useMovesQueueQueries, useUserQueries, useMoveSearchQueries } from 'hooks/queries'; -import { getMovesQueue } from 'services/ghcApi'; +import { getMovesQueue, saveBulkAssignmentData } from 'services/ghcApi'; import { formatDateFromIso, serviceMemberAgencyLabel } from 'utils/formatters'; import MultiSelectCheckBoxFilter from 'components/Table/Filters/MultiSelectCheckBoxFilter'; import SelectFilter from 'components/Table/Filters/SelectFilter'; @@ -203,6 +204,17 @@ const MoveQueue = ({ isQueueManagementFFEnabled, userPrivileges, isBulkAssignmen fetchData(); }, []); + const { mutate: mutateBulkAssignment } = useMutation(saveBulkAssignmentData, { + onSuccess: () => { + // reload page to refetch queue + window.location.reload(); + }, + }); + + const onSubmitBulk = (bulkAssignmentSavePayload) => { + mutateBulkAssignment({ queueType: 'TASKORDER', ...bulkAssignmentSavePayload }); + }; + const onSubmit = useCallback((values) => { const payload = { moveCode: null, @@ -331,6 +343,8 @@ const MoveQueue = ({ isQueueManagementFFEnabled, userPrivileges, isBulkAssignmen key={queueType} isSupervisor={supervisor} isBulkAssignmentFFEnabled={isBulkAssignmentFFEnabled} + handleBulkAssignmentSave={onSubmitBulk} + queueType="TASK_ORDER" />
); diff --git a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx index 2b87996ed41..3c63f1feced 100644 --- a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx +++ b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx @@ -1,7 +1,7 @@ import React, { useCallback, useEffect, useState, useContext } from 'react'; import { generatePath, useNavigate, Navigate, useParams, NavLink } from 'react-router-dom'; import { connect } from 'react-redux'; -import { Button, Dropdown, Grid } from '@trussworks/react-uswds'; +import { Button, Dropdown } from '@trussworks/react-uswds'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { useMutation } from '@tanstack/react-query'; @@ -27,12 +27,11 @@ import { useUserQueries, useMoveSearchQueries, useCustomerSearchQueries, - useBulkAssignmentQueries, } from 'hooks/queries'; import { getServicesCounselingOriginLocations, - getServicesCounselingQueue, getServicesCounselingPPMQueue, + getServicesCounselingQueue, saveBulkAssignmentData, } from 'services/ghcApi'; import { DATE_FORMAT_STRING, DEFAULT_EMPTY_VALUE, MOVE_STATUSES } from 'shared/constants'; @@ -55,7 +54,6 @@ import MultiSelectTypeAheadCheckBoxFilter from 'components/Table/Filters/MutliSe import handleQueueAssignment from 'utils/queues'; import { selectLoggedInUser } from 'store/entities/selectors'; import SelectedGblocContext from 'components/Office/GblocSwitcher/SelectedGblocContext'; -import Alert from 'types/alert'; export const counselingColumns = (moveLockFlag, originLocationList, supervisor, isQueueManagementEnabled) => { const cols = [ @@ -517,31 +515,21 @@ const ServicesCounselingQueue = ({ navigate(generatePath(servicesCounselingRoutes.CREATE_CUSTOMER_PATH)); }; - const [errorMessage, setErrorMessage] = useState(null); - const [alertMessage, setAlertMessage] = useState(null); - const [alertType, setAlertType] = useState('success'); - - const closeoutBulkAssignmentData = useBulkAssignmentQueries('CLOSEOUT'); - const counselingBulkAssignmentData = useBulkAssignmentQueries('COUNSELING'); - const { mutate: mutateBulkAssignment } = useMutation(saveBulkAssignmentData, { onSuccess: () => { - setAlertMessage('Bulk moves successfully assigned.'); - setAlertType('success'); - // reload page to refetch queue window.location.reload(); }, - onError: () => { - setAlertMessage('There was a problem assigning the move. Please try again later.'); - setAlertType('error'); - }, }); const onSubmitBulk = (bulkAssignmentSavePayload) => { mutateBulkAssignment({ queueType: 'COUNSELING', ...bulkAssignmentSavePayload }); }; + const onSubmitBulkCloseout = (bulkAssignmentSavePayload) => { + mutateBulkAssignment({ queueType: 'CLOSEOUT', ...bulkAssignmentSavePayload }); + }; + const [search, setSearch] = useState({ moveCode: null, dodID: null, customerName: null }); const [searchHappened, setSearchHappened] = useState(false); const counselorMoveCreateFeatureFlag = isBooleanFlagEnabled('counselor_move_create'); @@ -637,15 +625,6 @@ const ServicesCounselingQueue = ({
{renderNavBar()} - - {alertMessage && ( - - - {alertMessage} - - - )} -

Search for a move

@@ -702,7 +681,8 @@ const ServicesCounselingQueue = ({ key={queueType} isSupervisor={supervisor} isBulkAssignmentFFEnabled={isBulkAssignmentFFEnabled} - bulkAssignmentData={closeoutBulkAssignmentData.bulkAssignmentData || {}} + handleBulkAssignmentSave={onSubmitBulkCloseout} + queueType="CLOSEOUT" />
); @@ -733,7 +713,7 @@ const ServicesCounselingQueue = ({ isSupervisor={supervisor} isBulkAssignmentFFEnabled={isBulkAssignmentFFEnabled} handleBulkAssignmentSave={onSubmitBulk} - bulkAssignmentData={counselingBulkAssignmentData.bulkAssignmentData || {}} + queueType="COUNSELING" />
); From 4f206aad02b7412d0051f39cfd60eeb8d3143c2a Mon Sep 17 00:00:00 2001 From: loganwc Date: Fri, 24 Jan 2025 20:40:05 +0000 Subject: [PATCH 11/35] fixes form bleeding out of container --- .../BulkAssignment/BulkAssignmentModal.module.scss | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/components/BulkAssignment/BulkAssignmentModal.module.scss b/src/components/BulkAssignment/BulkAssignmentModal.module.scss index 28af55e0c74..652618f9825 100644 --- a/src/components/BulkAssignment/BulkAssignmentModal.module.scss +++ b/src/components/BulkAssignment/BulkAssignmentModal.module.scss @@ -6,6 +6,9 @@ .BulkAssignmentTable { table { + table-layout: fixed; + width: 100%; + th { max-width: 10px; text-align: center; @@ -14,12 +17,8 @@ text-align: center; } .BulkAssignmentAssignment { - width: 60px; - text-align: center; + width: 60px; + text-align: center; } } - - form { - display: inherit; - } } From a3e8cdf5de13f2be0b6123f384b36a8afa34d371 Mon Sep 17 00:00:00 2001 From: loganwc Date: Tue, 28 Jan 2025 18:04:27 +0000 Subject: [PATCH 12/35] trying to reload table after save bulk --- .../BulkAssignment/BulkAssignmentModal.jsx | 9 +-- .../BulkAssignmentModal.module.scss | 9 +++ .../BulkAssignmentModal.test.jsx | 58 +++++++++++++++++-- src/components/Table/TableQueue.jsx | 41 ++++++++++--- src/hooks/queries.js | 9 ++- src/pages/Office/MoveQueue/MoveQueue.jsx | 16 +---- .../PaymentRequestQueue.jsx | 3 +- .../ServicesCounselingQueue.jsx | 25 ++------ 8 files changed, 117 insertions(+), 53 deletions(-) diff --git a/src/components/BulkAssignment/BulkAssignmentModal.jsx b/src/components/BulkAssignment/BulkAssignmentModal.jsx index 9556d2e2ddc..80619376742 100644 --- a/src/components/BulkAssignment/BulkAssignmentModal.jsx +++ b/src/components/BulkAssignment/BulkAssignmentModal.jsx @@ -26,6 +26,7 @@ export const BulkAssignmentModal = ({ onClose, onSubmit, title, submitText, clos setBulkAssignmentData(data); }); } catch (err) { + setBulkAssignmentData({}); milmoveLogger.error('Error fetching bulk assignment data:', err); } }; @@ -112,20 +113,20 @@ export const BulkAssignmentModal = ({ onClose, onSubmit, title, submitText, clos - + ); diff --git a/src/components/BulkAssignment/BulkAssignmentModal.module.scss b/src/components/BulkAssignment/BulkAssignmentModal.module.scss index 652618f9825..3b358d2db76 100644 --- a/src/components/BulkAssignment/BulkAssignmentModal.module.scss +++ b/src/components/BulkAssignment/BulkAssignmentModal.module.scss @@ -22,3 +22,12 @@ } } } + +button.backbutton { + color: #005ea2; + background-color: white; + border: 2px solid #005ea2; + border-radius: 0.25rem; + font-weight: 700; + padding: 0.75rem 1.25rem; +} diff --git a/src/components/BulkAssignment/BulkAssignmentModal.test.jsx b/src/components/BulkAssignment/BulkAssignmentModal.test.jsx index 4ffc69e7bd3..354744d7a2a 100644 --- a/src/components/BulkAssignment/BulkAssignmentModal.test.jsx +++ b/src/components/BulkAssignment/BulkAssignmentModal.test.jsx @@ -1,8 +1,10 @@ import React from 'react'; -import { render, screen } from '@testing-library/react'; +import { act, render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { BulkAssignmentModal } from 'components/BulkAssignment/BulkAssignmentModal'; +import { QUEUE_TYPES } from 'constants/queues'; +import { MockProviders } from 'testUtils'; let onClose; let onSubmit; @@ -11,15 +13,48 @@ beforeEach(() => { onSubmit = jest.fn(); }); +const bulkAssignmentData = { + availableOfficeUsers: [ + { + firstName: 'sc', + lastName: 'user', + officeUserId: '045c3048-df9a-4d44-88ed-8cd6e2100e08', + workload: 1, + }, + { + firstName: 'test1', + lastName: 'person', + officeUserId: '4b1f2722-b0bf-4b16-b8c4-49b4e49ba42a', + }, + ], + bulkAssignmentMoveIDs: [ + 'b3baf6ce-f43b-437c-85be-e1145c0ddb96', + '962ce8d2-03a2-435c-94ca-6b9ef6c226c1', + 'fee7f916-35a6-4c0b-9ea6-a1d8094b3ed3', + ], +}; + +jest.mock('services/ghcApi', () => ({ + getBulkAssignmentData: jest.fn().mockImplementation(() => Promise.resolve(bulkAssignmentData)), +})); + describe('BulkAssignmentModal', () => { it('renders the component', async () => { - render(); + render( + + + , + ); - expect(await screen.findByRole('heading', { level: 3, name: 'Bulk Assignment' })).toBeInTheDocument(); + expect(await screen.findByRole('heading', { level: 3, name: 'Bulk Assignment (3)' })).toBeInTheDocument(); }); it('closes the modal when close icon is clicked', async () => { - render(); + render( + + + , + ); const closeButton = await screen.findByTestId('modalCloseButton'); @@ -29,7 +64,7 @@ describe('BulkAssignmentModal', () => { }); it('closes the modal when the Cancel button is clicked', async () => { - render(); + render(); const cancelButton = await screen.findByRole('button', { name: 'Cancel' }); @@ -47,4 +82,17 @@ describe('BulkAssignmentModal', () => { expect(onSubmit).toHaveBeenCalledTimes(1); }); + + it('renders the user data', async () => { + render(); + const userTable = await screen.findByRole('table'); + expect(userTable).toBeInTheDocument(); + expect(screen.getByText('User')).toBeInTheDocument(); + expect(screen.getByText('Workload')).toBeInTheDocument(); + expect(screen.getByText('Assignment')).toBeInTheDocument(); + await act(async () => { + expect(await screen.getByText('user, sc')).toBeInTheDocument(); + }); + expect(screen.getAllByTestId('bulkAssignmentUserWorkload')[0]).toHaveTextContent('1'); + }); }); diff --git a/src/components/Table/TableQueue.jsx b/src/components/Table/TableQueue.jsx index 37289e0273a..603004e4457 100644 --- a/src/components/Table/TableQueue.jsx +++ b/src/components/Table/TableQueue.jsx @@ -3,6 +3,7 @@ import { connect } from 'react-redux'; import { GridContainer, Button } from '@trussworks/react-uswds'; import { useTable, useFilters, usePagination, useSortBy } from 'react-table'; import PropTypes from 'prop-types'; +import { useMutation } from '@tanstack/react-query'; import styles from './TableQueue.module.scss'; import TableCSVExportButton from './TableCSVExportButton'; @@ -27,6 +28,7 @@ import { getSelectionOptionLabel, } from 'components/Table/utils'; import { roleTypes } from 'constants/userRoles'; +import { saveBulkAssignmentData } from 'services/ghcApi'; const defaultPageSize = 20; const defaultPage = 1; @@ -56,7 +58,6 @@ const TableQueue = ({ isBulkAssignmentFFEnabled, officeUser, activeRole, - handleBulkAssignmentSave, queueType, }) => { const [isPageReload, setIsPageReload] = useState(true); @@ -108,9 +109,7 @@ const TableQueue = ({ setIsBulkAssignModalVisible(true); }; - const handleCloseBulkAssignModal = () => { - setIsBulkAssignModalVisible(false); - }; + const [tableData, setTableData] = useState([]); const { queueResult: { totalCount = 0, @@ -120,6 +119,7 @@ const TableQueue = ({ }, isInitialLoading: isLoading, isError, + refetch, } = useQueries({ sort: id, order: desc ? 'desc' : 'asc', @@ -127,6 +127,11 @@ const TableQueue = ({ currentPage, currentPageSize, viewAsGBLOC: selectedGbloc, + onSuccess: (res) => { + console.log(res, tableData); + + if (tableData.length < 1) setTableData(res.queueMoves); + }, }); // react-table setup below @@ -137,9 +142,17 @@ const TableQueue = ({ }), [], ); - const tableData = useMemo(() => data, [data]); + // let tableData = useMemo(() => data, [data]); + + const { mutate: mutateBulkAssignment } = useMutation(saveBulkAssignmentData, { + onSuccess: () => { + // reload page to refetch queue + // window.location.reload(); + // queryClient.invalidateQueries({ queryKey: SERVICES_COUNSELING_QUEUE, refetchType: 'all' }); + }, + }); - const tableColumns = useMemo(() => columns, [columns]); + // const tableColumns = useMemo(() => columns, [columns]); const { getTableProps, getTableBodyProps, @@ -157,7 +170,7 @@ const TableQueue = ({ state: { filters, pageIndex, pageSize, sortBy }, } = useTable( { - columns: tableColumns, + columns, data: tableData, initialState: { hiddenColumns: defaultHiddenColumns, @@ -318,13 +331,25 @@ const TableQueue = ({ return ''; }; + const handleCloseBulkAssignModal = () => { + refetch().then((res) => { + console.log('refetch data', res); + setTableData([...res.data.queueMoves]); + setIsBulkAssignModalVisible(false); + }); + }; + + const onSubmitBulk = (bulkAssignmentSavePayload) => { + mutateBulkAssignment({ queueType, ...bulkAssignmentSavePayload }); + }; + return (
{isBulkAssignModalVisible && ( diff --git a/src/hooks/queries.js b/src/hooks/queries.js index ce469702359..a8dca5a1254 100644 --- a/src/hooks/queries.js +++ b/src/hooks/queries.js @@ -619,13 +619,19 @@ export const useServicesCounselingQueueQueries = ({ currentPage = PAGINATION_PAGE_DEFAULT, currentPageSize = PAGINATION_PAGE_SIZE_DEFAULT, viewAsGBLOC, + onSuccess, }) => { - const { data = {}, ...servicesCounselingQueueQuery } = useQuery( + const { + refetch, + data = {}, + ...servicesCounselingQueueQuery + } = useQuery( [ SERVICES_COUNSELING_QUEUE, { sort, order, filters, currentPage, currentPageSize, needsPPMCloseout: false, viewAsGBLOC }, ], ({ queryKey }) => getServicesCounselingQueue(...queryKey), + { onSuccess }, ); const { isLoading, isError, isSuccess } = servicesCounselingQueueQuery; @@ -635,6 +641,7 @@ export const useServicesCounselingQueueQueries = ({ isLoading, isError, isSuccess, + refetch, }; }; diff --git a/src/pages/Office/MoveQueue/MoveQueue.jsx b/src/pages/Office/MoveQueue/MoveQueue.jsx index f94068cddd4..55dad428944 100644 --- a/src/pages/Office/MoveQueue/MoveQueue.jsx +++ b/src/pages/Office/MoveQueue/MoveQueue.jsx @@ -12,7 +12,7 @@ import { getMovesQueue, saveBulkAssignmentData } from 'services/ghcApi'; import { formatDateFromIso, serviceMemberAgencyLabel } from 'utils/formatters'; import MultiSelectCheckBoxFilter from 'components/Table/Filters/MultiSelectCheckBoxFilter'; import SelectFilter from 'components/Table/Filters/SelectFilter'; -import { MOVE_STATUS_OPTIONS, GBLOC, MOVE_STATUS_LABELS, BRANCH_OPTIONS } from 'constants/queues'; +import { MOVE_STATUS_OPTIONS, GBLOC, MOVE_STATUS_LABELS, BRANCH_OPTIONS, QUEUE_TYPES } from 'constants/queues'; import TableQueue from 'components/Table/TableQueue'; import LoadingPlaceholder from 'shared/LoadingPlaceholder'; import SomethingWentWrong from 'shared/SomethingWentWrong'; @@ -207,17 +207,6 @@ const MoveQueue = ({ isQueueManagementFFEnabled, userPrivileges, isBulkAssignmen fetchData(); }, []); - const { mutate: mutateBulkAssignment } = useMutation(saveBulkAssignmentData, { - onSuccess: () => { - // reload page to refetch queue - window.location.reload(); - }, - }); - - const onSubmitBulk = (bulkAssignmentSavePayload) => { - mutateBulkAssignment({ queueType: 'TASKORDER', ...bulkAssignmentSavePayload }); - }; - const onSubmit = useCallback((values) => { const payload = { moveCode: null, @@ -346,8 +335,7 @@ const MoveQueue = ({ isQueueManagementFFEnabled, userPrivileges, isBulkAssignmen key={queueType} isSupervisor={supervisor} isBulkAssignmentFFEnabled={isBulkAssignmentFFEnabled} - handleBulkAssignmentSave={onSubmitBulk} - queueType="TASK_ORDER" + queueType={QUEUE_TYPES.TASK_ORDER} />
); diff --git a/src/pages/Office/PaymentRequestQueue/PaymentRequestQueue.jsx b/src/pages/Office/PaymentRequestQueue/PaymentRequestQueue.jsx index a46518a2627..ba4c556574e 100644 --- a/src/pages/Office/PaymentRequestQueue/PaymentRequestQueue.jsx +++ b/src/pages/Office/PaymentRequestQueue/PaymentRequestQueue.jsx @@ -18,7 +18,7 @@ import { } from 'utils/formatters'; import SelectFilter from 'components/Table/Filters/SelectFilter'; import DateSelectFilter from 'components/Table/Filters/DateSelectFilter'; -import { BRANCH_OPTIONS, GBLOC } from 'constants/queues'; +import { BRANCH_OPTIONS, GBLOC, QUEUE_TYPES } from 'constants/queues'; import TableQueue from 'components/Table/TableQueue'; import LoadingPlaceholder from 'shared/LoadingPlaceholder'; import SomethingWentWrong from 'shared/SomethingWentWrong'; @@ -334,6 +334,7 @@ const PaymentRequestQueue = ({ isQueueManagementFFEnabled, userPrivileges, isBul key={queueType} isSupervisor={supervisor} isBulkAssignmentFFEnabled={isBulkAssignmentFFEnabled} + queueType={QUEUE_TYPES.PAYMENT_REQUEST} />
); diff --git a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx index 487b480d0a8..837b5d12dea 100644 --- a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx +++ b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx @@ -3,7 +3,7 @@ import { generatePath, useNavigate, Navigate, useParams, NavLink } from 'react-r import { connect } from 'react-redux'; import { Button, Dropdown } from '@trussworks/react-uswds'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { useMutation } from '@tanstack/react-query'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; import styles from './ServicesCounselingQueue.module.scss'; @@ -18,6 +18,7 @@ import { SERVICE_COUNSELING_PPM_TYPE_LABELS, SERVICE_COUNSELING_PPM_STATUS_OPTIONS, SERVICE_COUNSELING_PPM_STATUS_LABELS, + QUEUE_TYPES, } from 'constants/queues'; import { generalRoutes, servicesCounselingRoutes } from 'constants/routes'; import { elevatedPrivilegeTypes } from 'constants/userPrivileges'; @@ -54,6 +55,7 @@ import MultiSelectTypeAheadCheckBoxFilter from 'components/Table/Filters/MutliSe import handleQueueAssignment from 'utils/queues'; import { selectLoggedInUser } from 'store/entities/selectors'; import SelectedGblocContext from 'components/Office/GblocSwitcher/SelectedGblocContext'; +import { MOVES, MOVES_QUEUE, SERVICES_COUNSELING_PPM_QUEUE, SERVICES_COUNSELING_QUEUE } from 'constants/queryKeys'; export const counselingColumns = (moveLockFlag, originLocationList, supervisor, isQueueManagementEnabled) => { const cols = [ @@ -521,21 +523,6 @@ const ServicesCounselingQueue = ({ navigate(generatePath(servicesCounselingRoutes.CREATE_CUSTOMER_PATH)); }; - const { mutate: mutateBulkAssignment } = useMutation(saveBulkAssignmentData, { - onSuccess: () => { - // reload page to refetch queue - window.location.reload(); - }, - }); - - const onSubmitBulk = (bulkAssignmentSavePayload) => { - mutateBulkAssignment({ queueType: 'COUNSELING', ...bulkAssignmentSavePayload }); - }; - - const onSubmitBulkCloseout = (bulkAssignmentSavePayload) => { - mutateBulkAssignment({ queueType: 'CLOSEOUT', ...bulkAssignmentSavePayload }); - }; - const [search, setSearch] = useState({ moveCode: null, dodID: null, customerName: null }); const [searchHappened, setSearchHappened] = useState(false); const counselorMoveCreateFeatureFlag = isBooleanFlagEnabled('counselor_move_create'); @@ -687,8 +674,7 @@ const ServicesCounselingQueue = ({ key={queueType} isSupervisor={supervisor} isBulkAssignmentFFEnabled={isBulkAssignmentFFEnabled} - handleBulkAssignmentSave={onSubmitBulkCloseout} - queueType="CLOSEOUT" + queueType={QUEUE_TYPES.CLOSEOUT} /> ); @@ -718,8 +704,7 @@ const ServicesCounselingQueue = ({ key={queueType} isSupervisor={supervisor} isBulkAssignmentFFEnabled={isBulkAssignmentFFEnabled} - handleBulkAssignmentSave={onSubmitBulk} - queueType="COUNSELING" + queueType={QUEUE_TYPES.COUNSELING} /> ); From 5e4721fa03c59cceff0c5a6ce99879e0fb85ae36 Mon Sep 17 00:00:00 2001 From: loganwc Date: Tue, 28 Jan 2025 18:15:10 +0000 Subject: [PATCH 13/35] fix nil pointer with office user workload --- pkg/gen/ghcapi/embedded_spec.go | 6 ++++-- pkg/gen/ghcmessages/bulk_assignment_for_user.go | 2 +- swagger-def/ghc.yaml | 1 + swagger/ghc.yaml | 1 + 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/pkg/gen/ghcapi/embedded_spec.go b/pkg/gen/ghcapi/embedded_spec.go index 6fd4c87722d..0c8fc8f777b 100644 --- a/pkg/gen/ghcapi/embedded_spec.go +++ b/pkg/gen/ghcapi/embedded_spec.go @@ -6994,7 +6994,8 @@ func init() { "format": "uuid" }, "moveAssignments": { - "type": "integer" + "type": "integer", + "x-omitempty": false } } }, @@ -24131,7 +24132,8 @@ func init() { "format": "uuid" }, "moveAssignments": { - "type": "integer" + "type": "integer", + "x-omitempty": false } } }, diff --git a/pkg/gen/ghcmessages/bulk_assignment_for_user.go b/pkg/gen/ghcmessages/bulk_assignment_for_user.go index 57c74f2b034..7c0c46c1f31 100644 --- a/pkg/gen/ghcmessages/bulk_assignment_for_user.go +++ b/pkg/gen/ghcmessages/bulk_assignment_for_user.go @@ -24,7 +24,7 @@ type BulkAssignmentForUser struct { ID strfmt.UUID `json:"id,omitempty"` // move assignments - MoveAssignments int64 `json:"moveAssignments,omitempty"` + MoveAssignments int64 `json:"moveAssignments"` } // Validate validates this bulk assignment for user diff --git a/swagger-def/ghc.yaml b/swagger-def/ghc.yaml index 0246d00c369..24d62669e5e 100644 --- a/swagger-def/ghc.yaml +++ b/swagger-def/ghc.yaml @@ -7154,6 +7154,7 @@ definitions: format: uuid moveAssignments: type: integer + x-omitempty: false BulkAssignmentMoveData: format: uuid type: string diff --git a/swagger/ghc.yaml b/swagger/ghc.yaml index 0e954f6dd36..cd9aa97ddc1 100644 --- a/swagger/ghc.yaml +++ b/swagger/ghc.yaml @@ -7504,6 +7504,7 @@ definitions: format: uuid moveAssignments: type: integer + x-omitempty: false BulkAssignmentMoveData: format: uuid type: string From bb970dfb4a30c76cff3f8b4b2a62595cde35191c Mon Sep 17 00:00:00 2001 From: Alex Lusk Date: Tue, 28 Jan 2025 20:31:28 +0000 Subject: [PATCH 14/35] another attempt at refetch and redisplay on bulk assignment submit --- src/components/Table/TableQueue.jsx | 15 +++++++-------- .../ServicesCounselingQueue.jsx | 12 ++++++++---- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/components/Table/TableQueue.jsx b/src/components/Table/TableQueue.jsx index 603004e4457..0f0d656ea84 100644 --- a/src/components/Table/TableQueue.jsx +++ b/src/components/Table/TableQueue.jsx @@ -110,6 +110,7 @@ const TableQueue = ({ }; const [tableData, setTableData] = useState([]); + const { queueResult: { totalCount = 0, @@ -128,9 +129,7 @@ const TableQueue = ({ currentPageSize, viewAsGBLOC: selectedGbloc, onSuccess: (res) => { - console.log(res, tableData); - - if (tableData.length < 1) setTableData(res.queueMoves); + if (!tableData.length) setTableData(res.queueMoves); }, }); @@ -149,6 +148,10 @@ const TableQueue = ({ // reload page to refetch queue // window.location.reload(); // queryClient.invalidateQueries({ queryKey: SERVICES_COUNSELING_QUEUE, refetchType: 'all' }); + refetch().then((res) => { + setTableData([...res.data.queueMoves]); + setIsBulkAssignModalVisible(false); + }); }, }); @@ -332,11 +335,7 @@ const TableQueue = ({ }; const handleCloseBulkAssignModal = () => { - refetch().then((res) => { - console.log('refetch data', res); - setTableData([...res.data.queueMoves]); - setIsBulkAssignModalVisible(false); - }); + setIsBulkAssignModalVisible(false); }; const onSubmitBulk = (bulkAssignmentSavePayload) => { diff --git a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx index 837b5d12dea..1cd0dc31802 100644 --- a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx +++ b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx @@ -3,7 +3,7 @@ import { generatePath, useNavigate, Navigate, useParams, NavLink } from 'react-r import { connect } from 'react-redux'; import { Button, Dropdown } from '@trussworks/react-uswds'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { useMutation, useQueryClient } from '@tanstack/react-query'; +// import { useMutation, useQueryClient } from '@tanstack/react-query'; import styles from './ServicesCounselingQueue.module.scss'; @@ -33,7 +33,7 @@ import { getServicesCounselingOriginLocations, getServicesCounselingPPMQueue, getServicesCounselingQueue, - saveBulkAssignmentData, + // saveBulkAssignmentData, } from 'services/ghcApi'; import { DATE_FORMAT_STRING, DEFAULT_EMPTY_VALUE, MOVE_STATUSES } from 'shared/constants'; import { formatDateFromIso, serviceMemberAgencyLabel } from 'utils/formatters'; @@ -55,7 +55,7 @@ import MultiSelectTypeAheadCheckBoxFilter from 'components/Table/Filters/MutliSe import handleQueueAssignment from 'utils/queues'; import { selectLoggedInUser } from 'store/entities/selectors'; import SelectedGblocContext from 'components/Office/GblocSwitcher/SelectedGblocContext'; -import { MOVES, MOVES_QUEUE, SERVICES_COUNSELING_PPM_QUEUE, SERVICES_COUNSELING_QUEUE } from 'constants/queryKeys'; +// import { MOVES, MOVES_QUEUE, SERVICES_COUNSELING_PPM_QUEUE, SERVICES_COUNSELING_QUEUE } from 'constants/queryKeys'; export const counselingColumns = (moveLockFlag, originLocationList, supervisor, isQueueManagementEnabled) => { const cols = [ @@ -214,7 +214,11 @@ export const counselingColumns = (moveLockFlag, originLocationList, supervisor, > {row.availableOfficeUsers.map(({ lastName, firstName, officeUserId }) => ( - ))} From 70540fde79b84bf7bb36ff1310b5ddfccf70def7 Mon Sep 17 00:00:00 2001 From: loganwc Date: Tue, 28 Jan 2025 20:54:43 +0000 Subject: [PATCH 15/35] cleaned up some old attempts --- pkg/services/move/move_assignment.go | 2 +- .../BulkAssignment/BulkAssignmentModal.jsx | 3 +++ src/components/Table/TableQueue.jsx | 13 ++++++------- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/pkg/services/move/move_assignment.go b/pkg/services/move/move_assignment.go index a7b0c5290b0..c63e1360366 100644 --- a/pkg/services/move/move_assignment.go +++ b/pkg/services/move/move_assignment.go @@ -25,7 +25,7 @@ func (a moveAssigner) BulkMoveAssignment(appCtx appcontext.AppContext, queueType transactionErr := appCtx.NewTransaction(func(txnAppCtx appcontext.AppContext) error { for _, move := range movesToAssign { for _, officeUser := range officeUserData { - if officeUser.MoveAssignments > 0 { + if officeUser != nil && officeUser.MoveAssignments > 0 { officeUserId := uuid.FromStringOrNil(officeUser.ID.String()) switch queueType { diff --git a/src/components/BulkAssignment/BulkAssignmentModal.jsx b/src/components/BulkAssignment/BulkAssignmentModal.jsx index 80619376742..14c44070191 100644 --- a/src/components/BulkAssignment/BulkAssignmentModal.jsx +++ b/src/components/BulkAssignment/BulkAssignmentModal.jsx @@ -80,6 +80,8 @@ export const BulkAssignmentModal = ({ onClose, onSubmit, title, submitText, clos className={styles.BulkAssignmentAssignment} type="number" id={user.officeUserId} + defaultValue={0} + min={0} onChange={(event) => { handleChange(event); @@ -116,6 +118,7 @@ export const BulkAssignmentModal = ({ onClose, onSubmit, title, submitText, clos className="usa-button--submit" type="submit" data-testid="modalSubmitButton" + disabled={bulkAssignmentData?.bulkAssignmentMoveIDs < 1} > {submitText} diff --git a/src/components/Table/TableQueue.jsx b/src/components/Table/TableQueue.jsx index 603004e4457..389c99bfe76 100644 --- a/src/components/Table/TableQueue.jsx +++ b/src/components/Table/TableQueue.jsx @@ -128,8 +128,6 @@ const TableQueue = ({ currentPageSize, viewAsGBLOC: selectedGbloc, onSuccess: (res) => { - console.log(res, tableData); - if (tableData.length < 1) setTableData(res.queueMoves); }, }); @@ -142,17 +140,19 @@ const TableQueue = ({ }), [], ); - // let tableData = useMemo(() => data, [data]); + // const cachedTableData = useMemo(() => data, [data]); const { mutate: mutateBulkAssignment } = useMutation(saveBulkAssignmentData, { onSuccess: () => { // reload page to refetch queue // window.location.reload(); - // queryClient.invalidateQueries({ queryKey: SERVICES_COUNSELING_QUEUE, refetchType: 'all' }); + refetch().then((res) => { + setTableData([...res.data.queueMoves]); + }); }, }); - // const tableColumns = useMemo(() => columns, [columns]); + const tableColumns = useMemo(() => columns, [columns]); const { getTableProps, getTableBodyProps, @@ -170,7 +170,7 @@ const TableQueue = ({ state: { filters, pageIndex, pageSize, sortBy }, } = useTable( { - columns, + columns: tableColumns, data: tableData, initialState: { hiddenColumns: defaultHiddenColumns, @@ -333,7 +333,6 @@ const TableQueue = ({ const handleCloseBulkAssignModal = () => { refetch().then((res) => { - console.log('refetch data', res); setTableData([...res.data.queueMoves]); setIsBulkAssignModalVisible(false); }); From 098afee18e23a484ba988df9c9240fc9f501076b Mon Sep 17 00:00:00 2001 From: loganwc Date: Thu, 30 Jan 2025 03:39:28 +0000 Subject: [PATCH 16/35] some cleanup --- .../BulkAssignment/BulkAssignmentModal.jsx | 13 +++++++++++- .../BulkAssignmentModal.test.jsx | 20 +++++++++++++++++++ src/hooks/queries.test.jsx | 2 ++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/components/BulkAssignment/BulkAssignmentModal.jsx b/src/components/BulkAssignment/BulkAssignmentModal.jsx index 14c44070191..d27d9614567 100644 --- a/src/components/BulkAssignment/BulkAssignmentModal.jsx +++ b/src/components/BulkAssignment/BulkAssignmentModal.jsx @@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react'; import PropTypes from 'prop-types'; import { Button } from '@trussworks/react-uswds'; import { Formik } from 'formik'; +import * as Yup from 'yup'; import styles from './BulkAssignmentModal.module.scss'; @@ -18,12 +19,16 @@ const initialValues = { export const BulkAssignmentModal = ({ onClose, onSubmit, title, submitText, closeText, queueType }) => { // fetch bulk assignment data const [bulkAssignmentData, setBulkAssignmentData] = useState(null); + const [isDisabled, setIsDisabled] = useState(false); useEffect(() => { const fetchData = async () => { try { getBulkAssignmentData(queueType).then((data) => { setBulkAssignmentData(data); + if (data.bulkAssignmentMoveIDs === undefined) { + setIsDisabled(true); + } }); } catch (err) { setBulkAssignmentData({}); @@ -37,6 +42,10 @@ export const BulkAssignmentModal = ({ onClose, onSubmit, title, submitText, clos // adds move data to the initialValues obj initialValues.moveData = bulkAssignmentData?.bulkAssignmentMoveIDs; + const validationSchema = Yup.object().shape({ + assignment: Yup.number().min(0).typeError('Assignment must be a number'), + }); + return ( onClose()} /> @@ -53,6 +62,7 @@ export const BulkAssignmentModal = ({ onClose, onSubmit, title, submitText, clos onSubmit({ bulkAssignmentSavePayload }); onClose(); }} + validationSchema={validationSchema} initialValues={initialValues} > {({ handleChange, setValues, values }) => { @@ -79,6 +89,7 @@ export const BulkAssignmentModal = ({ onClose, onSubmit, title, submitText, clos {submitText} diff --git a/src/components/BulkAssignment/BulkAssignmentModal.test.jsx b/src/components/BulkAssignment/BulkAssignmentModal.test.jsx index 354744d7a2a..5bc02eb439c 100644 --- a/src/components/BulkAssignment/BulkAssignmentModal.test.jsx +++ b/src/components/BulkAssignment/BulkAssignmentModal.test.jsx @@ -95,4 +95,24 @@ describe('BulkAssignmentModal', () => { }); expect(screen.getAllByTestId('bulkAssignmentUserWorkload')[0]).toHaveTextContent('1'); }); + + it('submits the bulk assignment data', async () => { + render(); + const saveButton = await screen.getByTestId('modalSubmitButton'); + await userEvent.click(saveButton); + await waitFor(() => { + const payload = { + bulkAssignmentSavePayload: { + moveData: [ + 'b3baf6ce-f43b-437c-85be-e1145c0ddb96', + '962ce8d2-03a2-435c-94ca-6b9ef6c226c1', + 'fee7f916-35a6-4c0b-9ea6-a1d8094b3ed3', + ], + userData: [], + }, + }; + + expect(onSubmit).toHaveBeenCalledWith(payload); + }); + }); }); diff --git a/src/hooks/queries.test.jsx b/src/hooks/queries.test.jsx index 86679de027e..e185c41ab09 100644 --- a/src/hooks/queries.test.jsx +++ b/src/hooks/queries.test.jsx @@ -916,6 +916,7 @@ describe('useMovesQueueQueries', () => { isLoading: false, isError: false, isSuccess: true, + refetch: result.current.refetch, }); }); }); @@ -1028,6 +1029,7 @@ describe('useServicesCounselingQueuePPMQueries', () => { isLoading: false, isError: false, isSuccess: true, + refetch: result.current.refetch, }); }); }); From 6f299b6e3c5026668b10d6e1d88979fe61bee6e6 Mon Sep 17 00:00:00 2001 From: loganwc Date: Thu, 30 Jan 2025 17:02:24 +0000 Subject: [PATCH 17/35] add payment requests to bulk assign endpoint and some more code coverage --- pkg/services/move/move_assignment.go | 12 ++++++++++++ .../BulkAssignment/BulkAssignmentModal.jsx | 1 + .../BulkAssignmentModal.test.jsx | 19 ++++++++++++++++++- 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/pkg/services/move/move_assignment.go b/pkg/services/move/move_assignment.go index c63e1360366..4e8a47ea339 100644 --- a/pkg/services/move/move_assignment.go +++ b/pkg/services/move/move_assignment.go @@ -1,6 +1,8 @@ package move import ( + "slices" + "github.com/gofrs/uuid" "github.com/transcom/mymove/pkg/appcontext" @@ -23,6 +25,9 @@ func (a moveAssigner) BulkMoveAssignment(appCtx appcontext.AppContext, queueType } transactionErr := appCtx.NewTransaction(func(txnAppCtx appcontext.AppContext) error { + // track moves we've already assigned for payment requests + var paymentRequestMoveList []uuid.UUID + for _, move := range movesToAssign { for _, officeUser := range officeUserData { if officeUser != nil && officeUser.MoveAssignments > 0 { @@ -35,6 +40,13 @@ func (a moveAssigner) BulkMoveAssignment(appCtx appcontext.AppContext, queueType move.SCAssignedID = &officeUserId case string(models.QueueTypeTaskOrder): move.TOOAssignedID = &officeUserId + case string(models.QueueTypePaymentRequest): + if !slices.Contains(paymentRequestMoveList, move.ID) { + move.TIOAssignedID = &officeUserId + + // add move id to list so we can ignore them for the rest of the loop + paymentRequestMoveList = append(paymentRequestMoveList, move.ID) + } } officeUser.MoveAssignments -= 1 diff --git a/src/components/BulkAssignment/BulkAssignmentModal.jsx b/src/components/BulkAssignment/BulkAssignmentModal.jsx index d27d9614567..b0212c481c8 100644 --- a/src/components/BulkAssignment/BulkAssignmentModal.jsx +++ b/src/components/BulkAssignment/BulkAssignmentModal.jsx @@ -91,6 +91,7 @@ export const BulkAssignmentModal = ({ onClose, onSubmit, title, submitText, clos type="number" name="assignment" id={user.officeUserId} + data-testid="assignment" defaultValue={0} min={0} onChange={(event) => { diff --git a/src/components/BulkAssignment/BulkAssignmentModal.test.jsx b/src/components/BulkAssignment/BulkAssignmentModal.test.jsx index 5bc02eb439c..a6573f58502 100644 --- a/src/components/BulkAssignment/BulkAssignmentModal.test.jsx +++ b/src/components/BulkAssignment/BulkAssignmentModal.test.jsx @@ -98,6 +98,18 @@ describe('BulkAssignmentModal', () => { it('submits the bulk assignment data', async () => { render(); + const userTable = await screen.findByRole('table'); + expect(userTable).toBeInTheDocument(); + expect(screen.getByText('User')).toBeInTheDocument(); + expect(screen.getByText('Workload')).toBeInTheDocument(); + expect(screen.getByText('Assignment')).toBeInTheDocument(); + await act(async () => { + expect(await screen.getByText('user, sc')).toBeInTheDocument(); + const assignment = await screen.getAllByTestId('assignment')[0]; + await userEvent.type(assignment, '1'); + }); + expect(screen.getAllByTestId('bulkAssignmentUserWorkload')[0]).toHaveTextContent('1'); + const saveButton = await screen.getByTestId('modalSubmitButton'); await userEvent.click(saveButton); await waitFor(() => { @@ -108,7 +120,12 @@ describe('BulkAssignmentModal', () => { '962ce8d2-03a2-435c-94ca-6b9ef6c226c1', 'fee7f916-35a6-4c0b-9ea6-a1d8094b3ed3', ], - userData: [], + userData: [ + { + ID: '045c3048-df9a-4d44-88ed-8cd6e2100e08', + moveAssignments: 1, + }, + ], }, }; From dc1b7ee9e30a4b102b8e219f8e55ef23e9c9c429 Mon Sep 17 00:00:00 2001 From: Paul Stonebraker Date: Thu, 30 Jan 2025 18:30:06 +0000 Subject: [PATCH 18/35] add move history event for SaveBulkAssignmentData --- .../SaveBulkAssignmentData.jsx | 23 +++++++ .../SaveBulkAssignmentData.test.jsx | 65 +++++++++++++++++++ .../MoveHistory/EventTemplates/index.js | 1 + .../MoveHistory/UIDisplay/Operations.js | 1 + 4 files changed, 90 insertions(+) create mode 100644 src/constants/MoveHistory/EventTemplates/SaveBulkAssignmentData/SaveBulkAssignmentData.jsx create mode 100644 src/constants/MoveHistory/EventTemplates/SaveBulkAssignmentData/SaveBulkAssignmentData.test.jsx diff --git a/src/constants/MoveHistory/EventTemplates/SaveBulkAssignmentData/SaveBulkAssignmentData.jsx b/src/constants/MoveHistory/EventTemplates/SaveBulkAssignmentData/SaveBulkAssignmentData.jsx new file mode 100644 index 00000000000..2f4ffcb8e4c --- /dev/null +++ b/src/constants/MoveHistory/EventTemplates/SaveBulkAssignmentData/SaveBulkAssignmentData.jsx @@ -0,0 +1,23 @@ +import React from 'react'; + +import o from 'constants/MoveHistory/UIDisplay/Operations'; +import a from 'constants/MoveHistory/Database/Actions'; +import t from 'constants/MoveHistory/Database/Tables'; +import LabeledDetails from 'pages/Office/MoveHistory/LabeledDetails'; +import { formatAssignedOfficeUserFromContext } from 'utils/formatters'; + +const formatChangedValues = (historyRecord) => { + const newChangedValues = { + ...formatAssignedOfficeUserFromContext(historyRecord), + }; + + return { ...historyRecord, changedValues: newChangedValues }; +}; + +export default { + action: a.UPDATE, + eventName: o.saveBulkAssignmentData, + tableName: t.moves, + getEventNameDisplay: () => 'Updated move', + getDetails: (historyRecord) => , +}; diff --git a/src/constants/MoveHistory/EventTemplates/SaveBulkAssignmentData/SaveBulkAssignmentData.test.jsx b/src/constants/MoveHistory/EventTemplates/SaveBulkAssignmentData/SaveBulkAssignmentData.test.jsx new file mode 100644 index 00000000000..1fd2dfa766f --- /dev/null +++ b/src/constants/MoveHistory/EventTemplates/SaveBulkAssignmentData/SaveBulkAssignmentData.test.jsx @@ -0,0 +1,65 @@ +import { screen, render } from '@testing-library/react'; + +import e from 'constants/MoveHistory/EventTemplates/SaveBulkAssignmentData/SaveBulkAssignmentData'; +import getTemplate from 'constants/MoveHistory/TemplateManager'; + +describe('When given a move that has been assigned', () => { + const historyRecord = { + action: 'UPDATE', + eventName: 'saveBulkAssignmentData', + tableName: 'moves', + changedValues: { + sc_assigned_id: 'fb625e3c-067c-49d7-8fd9-88ef040e6137', + }, + oldValues: { + sc_assigned_id: null, + }, + context: [{ assigned_office_user_last_name: 'Daniels', assigned_office_user_first_name: 'Jayden' }], + }; + + it('correctly matches the template', () => { + const template = getTemplate(historyRecord); + expect(template).toMatchObject(e); + }); + + it('displays the proper name in the event name display column', () => { + const template = getTemplate(historyRecord); + + render(template.getEventNameDisplay(historyRecord)); + expect(screen.getByText('Updated move')).toBeInTheDocument(); + }); + + describe('displays the proper details for', () => { + it('services counselor', () => { + const template = getTemplate(historyRecord); + + render(template.getDetails(historyRecord)); + expect(screen.getByText('Counselor assigned')).toBeInTheDocument(); + expect(screen.getByText(': Daniels, Jayden')).toBeInTheDocument(); + }); + it('task ordering officer', () => { + historyRecord.changedValues = { too_assigned_id: 'fb625e3c-067c-49d7-8fd9-88ef040e6137' }; + historyRecord.oldValues = { too_assigned_id: null }; + historyRecord.context = [ + { assigned_office_user_last_name: 'Robinson', assigned_office_user_first_name: 'Brian' }, + ]; + + const template = getTemplate(historyRecord); + + render(template.getDetails(historyRecord)); + expect(screen.getByText('Task ordering officer assigned')).toBeInTheDocument(); + expect(screen.getByText(': Robinson, Brian')).toBeInTheDocument(); + }); + it('task invoicing officer', () => { + historyRecord.changedValues = { tio_assigned_id: 'fb625e3c-067c-49d7-8fd9-88ef040e6137' }; + historyRecord.oldValues = { tio_assigned_id: null }; + historyRecord.context = [{ assigned_office_user_last_name: 'Luvu', assigned_office_user_first_name: 'Frankie' }]; + + const template = getTemplate(historyRecord); + + render(template.getDetails(historyRecord)); + expect(screen.getByText('Task invoicing officer assigned')).toBeInTheDocument(); + expect(screen.getByText(': Luvu, Frankie')).toBeInTheDocument(); + }); + }); +}); diff --git a/src/constants/MoveHistory/EventTemplates/index.js b/src/constants/MoveHistory/EventTemplates/index.js index b729c57984c..492782184f6 100644 --- a/src/constants/MoveHistory/EventTemplates/index.js +++ b/src/constants/MoveHistory/EventTemplates/index.js @@ -116,3 +116,4 @@ export { default as deleteAssignedOfficeUser } from './UpdateAssignedOfficeUser/ export { default as UpdatePaymentRequestStatusMoves } from './UpdatePaymentRequestStatus/UpdatePaymentRequestStatusMoves'; export { default as reviewShipmentAddressUpdate } from './ReviewShipmentAddressUpdate/reviewShipmentAddressUpdate'; export { default as FinishDocumentReviewMoves } from './FinishDocumentReview/FinishDocumentReviewMoves'; +export { default as saveBulkAssignmentData } from './SaveBulkAssignmentData/SaveBulkAssignmentData'; diff --git a/src/constants/MoveHistory/UIDisplay/Operations.js b/src/constants/MoveHistory/UIDisplay/Operations.js index f780e8d7f59..a55962d3b60 100644 --- a/src/constants/MoveHistory/UIDisplay/Operations.js +++ b/src/constants/MoveHistory/UIDisplay/Operations.js @@ -69,4 +69,5 @@ export default { updateAssignedOfficeUser: 'updateAssignedOfficeUser', // ghc.yaml deleteAssignedOfficeUser: 'deleteAssignedOfficeUser', // ghc.yaml reviewShipmentAddressUpdate: 'reviewShipmentAddressUpdate', // ghc.yaml + saveBulkAssignmentData: 'saveBulkAssignmentData', // ghc.yaml }; From 8e3c02eee803d8b3594ec8f263e6c4c433bb10df Mon Sep 17 00:00:00 2001 From: loganwc Date: Fri, 7 Feb 2025 21:52:54 +0000 Subject: [PATCH 19/35] redux the things --- src/components/Table/TableQueue.jsx | 25 ++++++++---- src/hooks/queries.js | 7 +++- src/hooks/queries.test.jsx | 1 + src/pages/Office/MoveQueue/MoveQueue.jsx | 25 +++++++++--- .../PaymentRequestQueue.jsx | 24 ++++++++++-- .../ServicesCounselingQueue.jsx | 38 +++++++++++++++---- src/store/general/action.test.js | 18 ++++++++- src/store/general/actions.js | 7 ++++ src/store/general/reducer.js | 9 ++++- src/store/general/reducer.test.js | 9 ++++- src/store/general/selectors.js | 5 +++ src/store/general/selectors.test.js | 4 +- 12 files changed, 142 insertions(+), 30 deletions(-) diff --git a/src/components/Table/TableQueue.jsx b/src/components/Table/TableQueue.jsx index d28e1ba3256..4ce0f137209 100644 --- a/src/components/Table/TableQueue.jsx +++ b/src/components/Table/TableQueue.jsx @@ -29,6 +29,7 @@ import { } from 'components/Table/utils'; import { roleTypes } from 'constants/userRoles'; import { saveBulkAssignmentData } from 'services/ghcApi'; +import { setRefetchQueue as setRefetchQueueAction } from 'store/general/actions'; const defaultPageSize = 20; const defaultPage = 1; @@ -59,6 +60,8 @@ const TableQueue = ({ officeUser, activeRole, queueType, + refetchQueue, + setRefetchQueue, }) => { const [isPageReload, setIsPageReload] = useState(true); useEffect(() => { @@ -138,14 +141,14 @@ const TableQueue = ({ [], ); - const { mutate: mutateBulkAssignment } = useMutation(saveBulkAssignmentData, { - onSuccess: () => { - // refetch queue + const { mutate: mutateBulkAssignment } = useMutation(saveBulkAssignmentData, {}); + + useEffect(() => { + if (refetchQueue) refetch().then(() => { - setIsBulkAssignModalVisible(false); + setRefetchQueue(false); }); - }, - }); + }, [refetch, setRefetchQueue, refetchQueue]); const tableColumns = useMemo(() => columns, [columns]); const { @@ -327,7 +330,10 @@ const TableQueue = ({ }; const handleCloseBulkAssignModal = () => { - setIsBulkAssignModalVisible(false); + refetch().then(() => { + setIsBulkAssignModalVisible(false); + }); + // setIsBulkAssignModalVisible(false); }; const onSubmitBulk = (bulkAssignmentSavePayload) => { @@ -464,7 +470,10 @@ const mapStateToProps = (state) => { return { officeUser: user?.office_user || {}, activeRole: state.auth.activeRole, + refetchQueue: state?.generalState?.refetchQueue || false, }; }; -export default connect(mapStateToProps)(TableQueue); +const mapDispatchToProps = { setRefetchQueue: setRefetchQueueAction }; + +export default connect(mapStateToProps, mapDispatchToProps)(TableQueue); diff --git a/src/hooks/queries.js b/src/hooks/queries.js index 8c4eafe309c..f2d01455482 100644 --- a/src/hooks/queries.js +++ b/src/hooks/queries.js @@ -660,7 +660,11 @@ export const usePaymentRequestQueueQueries = ({ currentPageSize = PAGINATION_PAGE_SIZE_DEFAULT, viewAsGBLOC, }) => { - const { data = {}, ...paymentRequestsQueueQuery } = useQuery( + const { + refetch, + data = {}, + ...paymentRequestsQueueQuery + } = useQuery( [PAYMENT_REQUESTS_QUEUE, { sort, order, filters, currentPage, currentPageSize, viewAsGBLOC }], ({ queryKey }) => getPaymentRequestsQueue(...queryKey), ); @@ -672,6 +676,7 @@ export const usePaymentRequestQueueQueries = ({ isLoading, isError, isSuccess, + refetch, }; }; diff --git a/src/hooks/queries.test.jsx b/src/hooks/queries.test.jsx index e185c41ab09..fdcc5008a15 100644 --- a/src/hooks/queries.test.jsx +++ b/src/hooks/queries.test.jsx @@ -947,6 +947,7 @@ describe('usePaymentRequestsQueueQueries', () => { isLoading: false, isError: false, isSuccess: true, + refetch: result.current.refetch, }); }); }); diff --git a/src/pages/Office/MoveQueue/MoveQueue.jsx b/src/pages/Office/MoveQueue/MoveQueue.jsx index 536a07023b2..7854cc3b61f 100644 --- a/src/pages/Office/MoveQueue/MoveQueue.jsx +++ b/src/pages/Office/MoveQueue/MoveQueue.jsx @@ -2,6 +2,7 @@ import React, { useCallback, useEffect, useState } from 'react'; import { useNavigate, NavLink, useParams, Navigate, generatePath } from 'react-router-dom'; import { Dropdown } from '@trussworks/react-uswds'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { connect } from 'react-redux'; import styles from './MoveQueue.module.scss'; @@ -28,8 +29,9 @@ import NotFound from 'components/NotFound/NotFound'; import { isBooleanFlagEnabled } from 'utils/featureFlags'; import handleQueueAssignment from 'utils/queues'; import { elevatedPrivilegeTypes } from 'constants/userPrivileges'; +import { setRefetchQueue as setRefetchQueueAction } from 'store/general/actions'; -export const columns = (moveLockFlag, isQueueManagementEnabled, showBranchFilter = true) => { +export const columns = (moveLockFlag, isQueueManagementEnabled, setRefetchQueue, showBranchFilter = true) => { const cols = [ createHeader('ID', 'id', { id: 'id' }), createHeader( @@ -161,8 +163,11 @@ export const columns = (moveLockFlag, isQueueManagementEnabled, showBranchFilter ) : (
handleQueueAssignment(row.id, e.target.value, roleTypes.TOO)} + key={row.id} + onChange={(e) => { + handleQueueAssignment(row.id, e.target.value, roleTypes.TOO); + setRefetchQueue(true); + }} title="Assigned dropdown" > @@ -192,7 +197,7 @@ export const columns = (moveLockFlag, isQueueManagementEnabled, showBranchFilter return cols; }; -const MoveQueue = ({ isQueueManagementFFEnabled, userPrivileges, isBulkAssignmentFFEnabled }) => { +const MoveQueue = ({ isQueueManagementFFEnabled, userPrivileges, isBulkAssignmentFFEnabled, setRefetchQueue }) => { const navigate = useNavigate(); const { queueType } = useParams(); const [search, setSearch] = useState({ moveCode: null, dodID: null, customerName: null, paymentRequestCode: null }); @@ -326,7 +331,7 @@ const MoveQueue = ({ isQueueManagementFFEnabled, userPrivileges, isBulkAssignmen defaultSortedColumns={[{ id: 'status', desc: false }]} disableMultiSort disableSortBy={false} - columns={columns(moveLockFlag, isQueueManagementFFEnabled, showBranchFilter)} + columns={columns(moveLockFlag, isQueueManagementFFEnabled, showBranchFilter, setRefetchQueue)} title="All moves" handleClick={handleClick} useQueries={useMovesQueueQueries} @@ -346,4 +351,12 @@ const MoveQueue = ({ isQueueManagementFFEnabled, userPrivileges, isBulkAssignmen return ; }; -export default MoveQueue; +const mapStateToProps = (state) => { + return { + setRefetchQueue: state.generalState.setRefetchQueue, + }; +}; + +const mapDispatchToProps = { setRefetchQueue: setRefetchQueueAction }; + +export default connect(mapStateToProps, mapDispatchToProps)(MoveQueue); diff --git a/src/pages/Office/PaymentRequestQueue/PaymentRequestQueue.jsx b/src/pages/Office/PaymentRequestQueue/PaymentRequestQueue.jsx index a54a536358e..7c12dcc6951 100644 --- a/src/pages/Office/PaymentRequestQueue/PaymentRequestQueue.jsx +++ b/src/pages/Office/PaymentRequestQueue/PaymentRequestQueue.jsx @@ -2,6 +2,7 @@ import React, { useCallback, useEffect, useState } from 'react'; import { useNavigate, NavLink, useParams, Navigate } from 'react-router-dom'; import { Dropdown } from '@trussworks/react-uswds'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { connect } from 'react-redux'; import styles from './PaymentRequestQueue.module.scss'; @@ -32,8 +33,9 @@ import { isBooleanFlagEnabled } from 'utils/featureFlags'; import { DEFAULT_EMPTY_VALUE, PAYMENT_REQUEST_STATUS } from 'shared/constants'; import handleQueueAssignment from 'utils/queues'; import { elevatedPrivilegeTypes } from 'constants/userPrivileges'; +import { setRefetchQueue as setRefetchQueueAction } from 'store/general/actions'; -export const columns = (moveLockFlag, isQueueManagementEnabled, showBranchFilter = true) => { +export const columns = (moveLockFlag, isQueueManagementEnabled, setRefetchQueue, showBranchFilter = true) => { const cols = [ createHeader( ' ', @@ -163,6 +165,7 @@ export const columns = (moveLockFlag, isQueueManagementEnabled, showBranchFilter defaultValue={row.assignedTo?.officeUserId} onChange={(e) => { handleQueueAssignment(row.moveID, e.target.value, roleTypes.TIO); + setRefetchQueue(true); }} title="Assigned dropdown" > @@ -195,7 +198,12 @@ export const columns = (moveLockFlag, isQueueManagementEnabled, showBranchFilter return cols; }; -const PaymentRequestQueue = ({ isQueueManagementFFEnabled, userPrivileges, isBulkAssignmentFFEnabled }) => { +const PaymentRequestQueue = ({ + isQueueManagementFFEnabled, + userPrivileges, + isBulkAssignmentFFEnabled, + setRefetchQueue, +}) => { const { queueType } = useParams(); const navigate = useNavigate(); const [search, setSearch] = useState({ moveCode: null, dodID: null, customerName: null, paymentRequestCode: null }); @@ -326,7 +334,7 @@ const PaymentRequestQueue = ({ isQueueManagementFFEnabled, userPrivileges, isBul defaultSortedColumns={[{ id: 'age', desc: true }]} disableMultiSort disableSortBy={false} - columns={columns(moveLockFlag, isQueueManagementFFEnabled, showBranchFilter)} + columns={columns(moveLockFlag, isQueueManagementFFEnabled, showBranchFilter, setRefetchQueue)} title="Payment requests" handleClick={handleClick} useQueries={usePaymentRequestQueueQueries} @@ -346,4 +354,12 @@ const PaymentRequestQueue = ({ isQueueManagementFFEnabled, userPrivileges, isBul return ; }; -export default PaymentRequestQueue; +const mapStateToProps = (state) => { + return { + setRefetchQueue: state.generalState.setRefetchQueue, + }; +}; + +const mapDispatchToProps = { setRefetchQueue: setRefetchQueueAction }; + +export default connect(mapStateToProps, mapDispatchToProps)(PaymentRequestQueue); diff --git a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx index 2bc4ebc31ab..0521ea44345 100644 --- a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx +++ b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx @@ -53,8 +53,15 @@ import MultiSelectTypeAheadCheckBoxFilter from 'components/Table/Filters/MutliSe import handleQueueAssignment from 'utils/queues'; import { selectLoggedInUser } from 'store/entities/selectors'; import SelectedGblocContext from 'components/Office/GblocSwitcher/SelectedGblocContext'; +import { setRefetchQueue as setRefetchQueueAction } from 'store/general/actions'; -export const counselingColumns = (moveLockFlag, originLocationList, supervisor, isQueueManagementEnabled) => { +export const counselingColumns = ( + moveLockFlag, + originLocationList, + supervisor, + isQueueManagementEnabled, + setRefetchQueue, +) => { const cols = [ createHeader( ' ', @@ -205,8 +212,11 @@ export const counselingColumns = (moveLockFlag, originLocationList, supervisor, ) : (
handleQueueAssignment(row.id, e.target.value, roleTypes.SERVICES_COUNSELOR)} + key={row.id} + onChange={(e) => { + handleQueueAssignment(row.id, e.target.value, roleTypes.SERVICES_COUNSELOR); + setRefetchQueue(true); + }} title="Assigned dropdown" > @@ -241,6 +251,7 @@ export const closeoutColumns = ( ppmCloseoutOriginLocationList, supervisor, isQueueManagementEnabled, + setRefetchQueue, ) => { const cols = [ createHeader( @@ -406,8 +417,10 @@ export const closeoutColumns = ( ) : (
handleQueueAssignment(row.id, e.target.value, roleTypes.SERVICES_COUNSELOR)} + onChange={(e) => { + handleQueueAssignment(row.id, e.target.value, roleTypes.SERVICES_COUNSELOR); + setRefetchQueue(true); + }} title="Assigned dropdown" > @@ -442,6 +455,7 @@ const ServicesCounselingQueue = ({ isQueueManagementFFEnabled, officeUser, isBulkAssignmentFFEnabled, + setRefetchQueue, }) => { const { queueType } = useParams(); const { data, isLoading, isError } = useUserQueries(); @@ -667,6 +681,7 @@ const ServicesCounselingQueue = ({ ppmCloseoutOriginLocationList, supervisor, isQueueManagementFFEnabled, + setRefetchQueue, )} title="Moves" handleClick={handleClick} @@ -697,7 +712,13 @@ const ServicesCounselingQueue = ({ defaultSortedColumns={[{ id: 'submittedAt', desc: false }]} disableMultiSort disableSortBy={false} - columns={counselingColumns(moveLockFlag, originLocationList, supervisor, isQueueManagementFFEnabled)} + columns={counselingColumns( + moveLockFlag, + originLocationList, + supervisor, + isQueueManagementFFEnabled, + setRefetchQueue, + )} title="Moves" handleClick={handleClick} useQueries={useServicesCounselingQueueQueries} @@ -756,7 +777,10 @@ const mapStateToProps = (state) => { return { officeUser: user?.office_user || {}, + setRefetchQueue: state.generalState.setRefetchQueue, }; }; -export default connect(mapStateToProps)(ServicesCounselingQueue); +const mapDispatchToProps = { setRefetchQueue: setRefetchQueueAction }; + +export default connect(mapStateToProps, mapDispatchToProps)(ServicesCounselingQueue); diff --git a/src/store/general/action.test.js b/src/store/general/action.test.js index 5dba930c961..b1b3ba7b585 100644 --- a/src/store/general/action.test.js +++ b/src/store/general/action.test.js @@ -1,4 +1,11 @@ -import { setMoveId, SET_MOVE_ID, SET_CAN_ADD_ORDERS, setCanAddOrders } from './actions'; +import { + setMoveId, + SET_MOVE_ID, + SET_CAN_ADD_ORDERS, + setCanAddOrders, + SET_REFETCH_QUEUE, + setRefetchQueue, +} from './actions'; describe('GeneralState actions', () => { it('setMoveId returns the expected action', () => { @@ -18,4 +25,13 @@ describe('GeneralState actions', () => { expect(setCanAddOrders(true)).toEqual(expectedAction); }); + + it('setRefetchQueue returns the expected action', () => { + const expectedAction = { + type: SET_REFETCH_QUEUE, + payload: true, + }; + + expect(setRefetchQueue(true)).toEqual(expectedAction); + }); }); diff --git a/src/store/general/actions.js b/src/store/general/actions.js index 2b89b9ac3bb..8941cc11212 100644 --- a/src/store/general/actions.js +++ b/src/store/general/actions.js @@ -12,3 +12,10 @@ export const setCanAddOrders = (value) => ({ type: SET_CAN_ADD_ORDERS, payload: value, }); + +export const SET_REFETCH_QUEUE = 'SET_REFETCH_QUEUE'; + +export const setRefetchQueue = (value) => ({ + type: SET_REFETCH_QUEUE, + payload: value, +}); diff --git a/src/store/general/reducer.js b/src/store/general/reducer.js index b27848dfae3..3693d538147 100644 --- a/src/store/general/reducer.js +++ b/src/store/general/reducer.js @@ -1,10 +1,11 @@ // Reducer created to store needed information in state -import { SET_CAN_ADD_ORDERS, SET_MOVE_ID } from './actions'; +import { SET_CAN_ADD_ORDERS, SET_MOVE_ID, SET_REFETCH_QUEUE } from './actions'; export const initialState = { // Select the moveId that is set from clicking on Go To Move on the MultiMoveLandingPage moveId: '', canAddOrders: false, + refetchQueue: false, }; const generalStateReducer = (state = initialState, action = {}) => { @@ -22,6 +23,12 @@ const generalStateReducer = (state = initialState, action = {}) => { canAddOrders: action.payload, }; } + case SET_REFETCH_QUEUE: { + return { + ...state, + refetchQueue: action.payload, + }; + } default: return state; } diff --git a/src/store/general/reducer.test.js b/src/store/general/reducer.test.js index 23c9cd24775..160b2fe2297 100644 --- a/src/store/general/reducer.test.js +++ b/src/store/general/reducer.test.js @@ -1,5 +1,5 @@ import generalStateReducer, { initialState } from './reducer'; -import { setCanAddOrders, setMoveId } from './actions'; +import { setCanAddOrders, setMoveId, setRefetchQueue } from './actions'; describe('generalStateReducer', () => { it('returns the initial state by default', () => { @@ -19,4 +19,11 @@ describe('generalStateReducer', () => { canAddOrders: true, }); }); + + it('handles the setShouldRefetchQueue', () => { + expect(generalStateReducer(initialState, setRefetchQueue(true))).toEqual({ + ...initialState, + shouldRefetchQueue: true, + }); + }); }); diff --git a/src/store/general/selectors.js b/src/store/general/selectors.js index 90ff57d3569..d554afbbabf 100644 --- a/src/store/general/selectors.js +++ b/src/store/general/selectors.js @@ -3,6 +3,11 @@ export function selectCurrentMoveId(state) { return state.generalState.moveId; } +export function selectRefetchQueue(state) { + return state.generalState.refetchQueue; +} + export default { selectCurrentMoveId, + selectRefetchQueue, }; diff --git a/src/store/general/selectors.test.js b/src/store/general/selectors.test.js index 7d3ad18df21..89dddb8d4f9 100644 --- a/src/store/general/selectors.test.js +++ b/src/store/general/selectors.test.js @@ -1,13 +1,15 @@ -import { selectCurrentMoveId } from './selectors'; +import { selectCurrentMoveId, selectRefetchQueue } from './selectors'; describe('selectCurrentMoveId', () => { it('returns the moveId value', () => { const testState = { generalState: { moveId: 'test', + refetchQueue: false, }, }; expect(selectCurrentMoveId(testState)).toEqual(testState.generalState.moveId); + expect(selectRefetchQueue(testState)).toEqual(testState.generalState.refetchQueue); }); }); From 936e3e8eb1bcea3a71b21faebeae1785165d8b22 Mon Sep 17 00:00:00 2001 From: loganwc Date: Fri, 7 Feb 2025 22:38:19 +0000 Subject: [PATCH 20/35] this fixes things that I broke when merging 22296 into this branch --- src/pages/Office/PaymentRequestQueue/PaymentRequestQueue.jsx | 2 +- src/store/general/reducer.test.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/Office/PaymentRequestQueue/PaymentRequestQueue.jsx b/src/pages/Office/PaymentRequestQueue/PaymentRequestQueue.jsx index 7c12dcc6951..c0a63332b2a 100644 --- a/src/pages/Office/PaymentRequestQueue/PaymentRequestQueue.jsx +++ b/src/pages/Office/PaymentRequestQueue/PaymentRequestQueue.jsx @@ -334,7 +334,7 @@ const PaymentRequestQueue = ({ defaultSortedColumns={[{ id: 'age', desc: true }]} disableMultiSort disableSortBy={false} - columns={columns(moveLockFlag, isQueueManagementFFEnabled, showBranchFilter, setRefetchQueue)} + columns={columns(moveLockFlag, isQueueManagementFFEnabled, setRefetchQueue, showBranchFilter)} title="Payment requests" handleClick={handleClick} useQueries={usePaymentRequestQueueQueries} diff --git a/src/store/general/reducer.test.js b/src/store/general/reducer.test.js index 160b2fe2297..ab02adfa8b5 100644 --- a/src/store/general/reducer.test.js +++ b/src/store/general/reducer.test.js @@ -23,7 +23,7 @@ describe('generalStateReducer', () => { it('handles the setShouldRefetchQueue', () => { expect(generalStateReducer(initialState, setRefetchQueue(true))).toEqual({ ...initialState, - shouldRefetchQueue: true, + refetchQueue: true, }); }); }); From 12ff81c21b78f0f819ba67d98d065103d708460a Mon Sep 17 00:00:00 2001 From: loganwc Date: Tue, 11 Feb 2025 17:29:48 +0000 Subject: [PATCH 21/35] added backup date for closeout/payment request fetch for bulk assignment modal --- pkg/gen/ghcapi/embedded_spec.go | 4 ++-- pkg/services/move/move_fetcher.go | 10 ++++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/pkg/gen/ghcapi/embedded_spec.go b/pkg/gen/ghcapi/embedded_spec.go index 974e53cc411..9642a0753f5 100644 --- a/pkg/gen/ghcapi/embedded_spec.go +++ b/pkg/gen/ghcapi/embedded_spec.go @@ -1516,7 +1516,7 @@ func init() { }, "/move-task-orders/{moveTaskOrderID}/status": { "patch": { - "description": "Changes move task order status to make it available to prime", + "description": "Changes move task order status", "consumes": [ "application/json" ], @@ -1526,7 +1526,7 @@ func init() { "tags": [ "moveTaskOrder" ], - "summary": "Change the status of a move task order to make it available to prime", + "summary": "Change the status of a move task order", "operationId": "updateMoveTaskOrderStatus", "parameters": [ { diff --git a/pkg/services/move/move_fetcher.go b/pkg/services/move/move_fetcher.go index b9b6bbce9e3..99d7fcafdf4 100644 --- a/pkg/services/move/move_fetcher.go +++ b/pkg/services/move/move_fetcher.go @@ -188,7 +188,10 @@ func (f moveFetcherBulkAssignment) FetchMovesForBulkAssignmentCloseout(appCtx ap query := `SELECT moves.id, - ppm_shipments.submitted_at AS earliest_date + MIN(LEAST( + ppm_shipments.submitted_at, + '0000-01-01', + )) AS earliest_date FROM moves INNER JOIN orders ON orders.id = moves.orders_id INNER JOIN service_members ON service_members.id = orders.service_member_id @@ -299,7 +302,10 @@ func (f moveFetcherBulkAssignment) FetchMovesForBulkAssignmentPaymentRequest(app sqlQuery := ` SELECT moves.id, - min(payment_requests.requested_at) AS earliest_date + MIN(LEAST( + payment_requests.requested_at, + '0000-01-01', + )) AS earliest_date FROM moves INNER JOIN orders ON orders.id = moves.orders_id INNER JOIN service_members ON orders.service_member_id = service_members.id From d0007dfef114f1c77bd705f6fc85647816c613e4 Mon Sep 17 00:00:00 2001 From: loganwc Date: Tue, 11 Feb 2025 17:46:47 +0000 Subject: [PATCH 22/35] added backup date for closeout/payment request fetch for bulk assignment modal --- pkg/services/move/move_fetcher.go | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/pkg/services/move/move_fetcher.go b/pkg/services/move/move_fetcher.go index 99d7fcafdf4..79c916b010b 100644 --- a/pkg/services/move/move_fetcher.go +++ b/pkg/services/move/move_fetcher.go @@ -188,10 +188,7 @@ func (f moveFetcherBulkAssignment) FetchMovesForBulkAssignmentCloseout(appCtx ap query := `SELECT moves.id, - MIN(LEAST( - ppm_shipments.submitted_at, - '0000-01-01', - )) AS earliest_date + COALESCE(ppm_shipments.submitted_at, '0001-01-01') AS earliest_date FROM moves INNER JOIN orders ON orders.id = moves.orders_id INNER JOIN service_members ON service_members.id = orders.service_member_id @@ -302,10 +299,7 @@ func (f moveFetcherBulkAssignment) FetchMovesForBulkAssignmentPaymentRequest(app sqlQuery := ` SELECT moves.id, - MIN(LEAST( - payment_requests.requested_at, - '0000-01-01', - )) AS earliest_date + COALESCE(payment_requests.requested_at, '0001-01-01') AS earliest_date FROM moves INNER JOIN orders ON orders.id = moves.orders_id INNER JOIN service_members ON orders.service_member_id = service_members.id From bdaa0840ce929cb7debb121a8ed502accfb1b68b Mon Sep 17 00:00:00 2001 From: loganwc Date: Tue, 11 Feb 2025 18:42:07 +0000 Subject: [PATCH 23/35] remove comment --- src/components/Table/TableQueue.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/Table/TableQueue.jsx b/src/components/Table/TableQueue.jsx index 4ce0f137209..f10fd286d6a 100644 --- a/src/components/Table/TableQueue.jsx +++ b/src/components/Table/TableQueue.jsx @@ -333,7 +333,6 @@ const TableQueue = ({ refetch().then(() => { setIsBulkAssignModalVisible(false); }); - // setIsBulkAssignModalVisible(false); }; const onSubmitBulk = (bulkAssignmentSavePayload) => { From ae7d85fbda3b1c2c51ec679033d75250861198ce Mon Sep 17 00:00:00 2001 From: loganwc Date: Tue, 11 Feb 2025 19:17:53 +0000 Subject: [PATCH 24/35] moved modal visibility toggle out of promise to fix flashing --- src/components/Table/TableQueue.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/Table/TableQueue.jsx b/src/components/Table/TableQueue.jsx index f10fd286d6a..29995287d4f 100644 --- a/src/components/Table/TableQueue.jsx +++ b/src/components/Table/TableQueue.jsx @@ -330,9 +330,9 @@ const TableQueue = ({ }; const handleCloseBulkAssignModal = () => { - refetch().then(() => { - setIsBulkAssignModalVisible(false); - }); + refetch(); + + setIsBulkAssignModalVisible(false); }; const onSubmitBulk = (bulkAssignmentSavePayload) => { From 843ad230712c5e88eecf6adb57de8ab063df935c Mon Sep 17 00:00:00 2001 From: loganwc Date: Tue, 11 Feb 2025 20:06:31 +0000 Subject: [PATCH 25/35] move some logic and fixed some design silliness --- .../BulkAssignment/BulkAssignmentModal.jsx | 52 ++++++++++--------- .../BulkAssignmentModal.module.scss | 5 ++ 2 files changed, 32 insertions(+), 25 deletions(-) diff --git a/src/components/BulkAssignment/BulkAssignmentModal.jsx b/src/components/BulkAssignment/BulkAssignmentModal.jsx index b0212c481c8..71ea2a6f22e 100644 --- a/src/components/BulkAssignment/BulkAssignmentModal.jsx +++ b/src/components/BulkAssignment/BulkAssignmentModal.jsx @@ -66,6 +66,31 @@ export const BulkAssignmentModal = ({ onClose, onSubmit, title, submitText, clos initialValues={initialValues} > {({ handleChange, setValues, values }) => { + const handleAssignmentChange = (event, i) => { + handleChange(event); + + let newUserAssignment; + if (event.target.value !== '') { + newUserAssignment = { + ID: event.target.id, + moveAssignments: +event.target.value, + }; + } else { + newUserAssignment = { + ID: event.target.id, + moveAssignments: 0, + }; + } + + const newValues = values; + newValues.userData[i] = newUserAssignment; + + setValues({ + ...values, + userData: newValues.userData, + }); + }; + return (
Workload Assignment
@@ -79,7 +60,30 @@ export const BulkAssignmentModal = ({ onClose, onSubmit, title, submitText, clos className={styles.BulkAssignmentAssignment} type="number" id={user.officeUserId} - onChange={addUserAssignment} + onChange={(event) => { + handleChange(event); + + let newUserAssignment; + if (event.target.value !== '') { + newUserAssignment = { + ID: event.target.id, + moveAssignments: +event.target.value, + }; + } else { + newUserAssignment = { + ID: event.target.id, + moveAssignments: 0, + }; + } + + const newValues = values; + newValues.userData[i] = newUserAssignment; + + setValues({ + ...values, + userData: newValues.userData, + }); + }} />
@@ -78,7 +103,7 @@ export const BulkAssignmentModal = ({ onClose, onSubmit, title, submitText, clos return ( @@ -94,30 +119,7 @@ export const BulkAssignmentModal = ({ onClose, onSubmit, title, submitText, clos data-testid="assignment" defaultValue={0} min={0} - onChange={(event) => { - handleChange(event); - - let newUserAssignment; - if (event.target.value !== '') { - newUserAssignment = { - ID: event.target.id, - moveAssignments: +event.target.value, - }; - } else { - newUserAssignment = { - ID: event.target.id, - moveAssignments: 0, - }; - } - - const newValues = values; - newValues.userData[i] = newUserAssignment; - - setValues({ - ...values, - userData: newValues.userData, - }); - }} + onChange={(event) => handleAssignmentChange(event, i)} /> diff --git a/src/components/BulkAssignment/BulkAssignmentModal.module.scss b/src/components/BulkAssignment/BulkAssignmentModal.module.scss index 3b358d2db76..e15cabe50fe 100644 --- a/src/components/BulkAssignment/BulkAssignmentModal.module.scss +++ b/src/components/BulkAssignment/BulkAssignmentModal.module.scss @@ -1,5 +1,6 @@ .BulkModal { min-width: 650px !important; + max-width: 60vw; overflow-y: auto; max-height: 90vh; } @@ -20,6 +21,10 @@ width: 60px; text-align: center; } + .officeUserFormattedName { + overflow: hidden; + text-overflow: ellipsis; + } } } From ad626145dc034d32c9837d80963edffc0170cc93 Mon Sep 17 00:00:00 2001 From: loganwc Date: Wed, 12 Feb 2025 19:00:04 +0000 Subject: [PATCH 26/35] add some checks and fixes --- pkg/handlers/ghcapi/queues.go | 2 +- pkg/services/move/move_assignment.go | 12 +------ pkg/services/move/move_fetcher.go | 6 ++-- .../BulkAssignment/BulkAssignmentModal.jsx | 35 +++++++++++++++++-- .../BulkAssignmentModal.module.scss | 9 +++++ .../BulkAssignmentModal.test.jsx | 4 +++ 6 files changed, 50 insertions(+), 18 deletions(-) diff --git a/pkg/handlers/ghcapi/queues.go b/pkg/handlers/ghcapi/queues.go index 432a562e20d..10aac34b35a 100644 --- a/pkg/handlers/ghcapi/queues.go +++ b/pkg/handlers/ghcapi/queues.go @@ -740,7 +740,7 @@ func (h SaveBulkAssignmentDataHandler) Handle( return queues.NewGetServicesCounselingQueueInternalServerError(), err } - return queues.NewSaveBulkAssignmentDataOK().WithPayload(nil), nil + return queues.NewSaveBulkAssignmentDataOK(), nil }) } diff --git a/pkg/services/move/move_assignment.go b/pkg/services/move/move_assignment.go index 4e8a47ea339..a55211f9e81 100644 --- a/pkg/services/move/move_assignment.go +++ b/pkg/services/move/move_assignment.go @@ -1,8 +1,6 @@ package move import ( - "slices" - "github.com/gofrs/uuid" "github.com/transcom/mymove/pkg/appcontext" @@ -25,9 +23,6 @@ func (a moveAssigner) BulkMoveAssignment(appCtx appcontext.AppContext, queueType } transactionErr := appCtx.NewTransaction(func(txnAppCtx appcontext.AppContext) error { - // track moves we've already assigned for payment requests - var paymentRequestMoveList []uuid.UUID - for _, move := range movesToAssign { for _, officeUser := range officeUserData { if officeUser != nil && officeUser.MoveAssignments > 0 { @@ -41,12 +36,7 @@ func (a moveAssigner) BulkMoveAssignment(appCtx appcontext.AppContext, queueType case string(models.QueueTypeTaskOrder): move.TOOAssignedID = &officeUserId case string(models.QueueTypePaymentRequest): - if !slices.Contains(paymentRequestMoveList, move.ID) { - move.TIOAssignedID = &officeUserId - - // add move id to list so we can ignore them for the rest of the loop - paymentRequestMoveList = append(paymentRequestMoveList, move.ID) - } + move.TIOAssignedID = &officeUserId } officeUser.MoveAssignments -= 1 diff --git a/pkg/services/move/move_fetcher.go b/pkg/services/move/move_fetcher.go index 79c916b010b..fc05649dd06 100644 --- a/pkg/services/move/move_fetcher.go +++ b/pkg/services/move/move_fetcher.go @@ -188,7 +188,7 @@ func (f moveFetcherBulkAssignment) FetchMovesForBulkAssignmentCloseout(appCtx ap query := `SELECT moves.id, - COALESCE(ppm_shipments.submitted_at, '0001-01-01') AS earliest_date + COALESCE(MIN(ppm_shipments.submitted_at), '0001-01-01') AS earliest_date FROM moves INNER JOIN orders ON orders.id = moves.orders_id INNER JOIN service_members ON service_members.id = orders.service_member_id @@ -216,7 +216,7 @@ func (f moveFetcherBulkAssignment) FetchMovesForBulkAssignmentCloseout(appCtx ap query += ` AND (ppm_shipments.status IN ($2)) AND (orders.orders_type NOT IN ($3, $4, $5)) - GROUP BY moves.id, ppm_shipments.submitted_at + GROUP BY moves.id ORDER BY earliest_date ASC` err := appCtx.DB().RawQuery(query, @@ -299,7 +299,7 @@ func (f moveFetcherBulkAssignment) FetchMovesForBulkAssignmentPaymentRequest(app sqlQuery := ` SELECT moves.id, - COALESCE(payment_requests.requested_at, '0001-01-01') AS earliest_date + COALESCE(MIN(payment_requests.requested_at), '0001-01-01') AS earliest_date FROM moves INNER JOIN orders ON orders.id = moves.orders_id INNER JOIN service_members ON orders.service_member_id = service_members.id diff --git a/src/components/BulkAssignment/BulkAssignmentModal.jsx b/src/components/BulkAssignment/BulkAssignmentModal.jsx index 71ea2a6f22e..1d907d2994b 100644 --- a/src/components/BulkAssignment/BulkAssignmentModal.jsx +++ b/src/components/BulkAssignment/BulkAssignmentModal.jsx @@ -17,17 +17,37 @@ const initialValues = { }; export const BulkAssignmentModal = ({ onClose, onSubmit, title, submitText, closeText, queueType }) => { - // fetch bulk assignment data + const [isLoading, setIsLoading] = useState(true); + const [isError, setIsError] = useState(false); const [bulkAssignmentData, setBulkAssignmentData] = useState(null); const [isDisabled, setIsDisabled] = useState(false); + const [numberOfMoves, setNumberOfMoves] = useState(0); + + const errorMessage = 'Cannot assign more moves than are available.'; + + const initUserData = (availableOfficeUsers) => { + const officeUsers = []; + availableOfficeUsers.forEach((user) => { + const newUserAssignment = { + ID: user.officeUserId, + moveAssignments: 0, + }; + officeUsers.push(newUserAssignment); + }); + initialValues.userData = officeUsers; + setIsLoading(false); + }; useEffect(() => { const fetchData = async () => { try { getBulkAssignmentData(queueType).then((data) => { setBulkAssignmentData(data); + initUserData(data?.availableOfficeUsers); + if (data.bulkAssignmentMoveIDs === undefined) { setIsDisabled(true); + setNumberOfMoves(0); } }); } catch (err) { @@ -46,18 +66,26 @@ export const BulkAssignmentModal = ({ onClose, onSubmit, title, submitText, clos assignment: Yup.number().min(0).typeError('Assignment must be a number'), }); + if (isLoading) return null; + return ( onClose()} />

- {title} ( - {bulkAssignmentData?.bulkAssignmentMoveIDs == null ? 0 : bulkAssignmentData?.bulkAssignmentMoveIDs?.length}) + {title} ({numberOfMoves})

{ + const totalAssignment = values?.userData?.reduce((sum, item) => sum + item.moveAssignments, 0); + + if (totalAssignment > numberOfMoves) { + setIsError(true); + return; + } + const bulkAssignmentSavePayload = values; onSubmit({ bulkAssignmentSavePayload }); onClose(); @@ -145,6 +173,7 @@ export const BulkAssignmentModal = ({ onClose, onSubmit, title, submitText, clos {closeText} + {isError &&
{errorMessage}
} ); }} diff --git a/src/components/BulkAssignment/BulkAssignmentModal.module.scss b/src/components/BulkAssignment/BulkAssignmentModal.module.scss index e15cabe50fe..e6954661c8e 100644 --- a/src/components/BulkAssignment/BulkAssignmentModal.module.scss +++ b/src/components/BulkAssignment/BulkAssignmentModal.module.scss @@ -3,6 +3,15 @@ max-width: 60vw; overflow-y: auto; max-height: 90vh; + + .errorMessage { + margin: 0 auto; + display: flex; + text-align: center; + align-items: center; + color: $error; + font-size: large; + } } .BulkAssignmentTable { diff --git a/src/components/BulkAssignment/BulkAssignmentModal.test.jsx b/src/components/BulkAssignment/BulkAssignmentModal.test.jsx index a6573f58502..69ff7fff6e0 100644 --- a/src/components/BulkAssignment/BulkAssignmentModal.test.jsx +++ b/src/components/BulkAssignment/BulkAssignmentModal.test.jsx @@ -125,6 +125,10 @@ describe('BulkAssignmentModal', () => { ID: '045c3048-df9a-4d44-88ed-8cd6e2100e08', moveAssignments: 1, }, + { + ID: '4b1f2722-b0bf-4b16-b8c4-49b4e49ba42a', + moveAssignments: 0, + }, ], }, }; From 59c045204150011b8b9892ed5b95dc971b3f2cb6 Mon Sep 17 00:00:00 2001 From: loganwc Date: Thu, 13 Feb 2025 15:57:13 +0000 Subject: [PATCH 27/35] change from 200 to 204 response and removed coalsce from pr date --- pkg/gen/ghcapi/embedded_spec.go | 14 ++---- .../save_bulk_assignment_data_responses.go | 44 +++++-------------- pkg/handlers/ghcapi/queues.go | 2 +- pkg/handlers/ghcapi/queues_test.go | 2 +- pkg/services/move/move_fetcher.go | 2 +- swagger-def/ghc.yaml | 6 +-- swagger/ghc.yaml | 6 +-- 7 files changed, 23 insertions(+), 53 deletions(-) diff --git a/pkg/gen/ghcapi/embedded_spec.go b/pkg/gen/ghcapi/embedded_spec.go index 9642a0753f5..471532a58f0 100644 --- a/pkg/gen/ghcapi/embedded_spec.go +++ b/pkg/gen/ghcapi/embedded_spec.go @@ -4441,11 +4441,8 @@ func init() { } ], "responses": { - "200": { - "description": "Successfully returned bulk assignment data", - "schema": { - "$ref": "#/definitions/BulkAssignmentData" - } + "204": { + "description": "assigned" }, "401": { "$ref": "#/responses/PermissionDenied" @@ -21306,11 +21303,8 @@ func init() { } ], "responses": { - "200": { - "description": "Successfully returned bulk assignment data", - "schema": { - "$ref": "#/definitions/BulkAssignmentData" - } + "204": { + "description": "assigned" }, "401": { "description": "The request was denied", diff --git a/pkg/gen/ghcapi/ghcoperations/queues/save_bulk_assignment_data_responses.go b/pkg/gen/ghcapi/ghcoperations/queues/save_bulk_assignment_data_responses.go index 5470e3abc10..99a269fdfda 100644 --- a/pkg/gen/ghcapi/ghcoperations/queues/save_bulk_assignment_data_responses.go +++ b/pkg/gen/ghcapi/ghcoperations/queues/save_bulk_assignment_data_responses.go @@ -13,49 +13,29 @@ import ( "github.com/transcom/mymove/pkg/gen/ghcmessages" ) -// SaveBulkAssignmentDataOKCode is the HTTP code returned for type SaveBulkAssignmentDataOK -const SaveBulkAssignmentDataOKCode int = 200 +// SaveBulkAssignmentDataNoContentCode is the HTTP code returned for type SaveBulkAssignmentDataNoContent +const SaveBulkAssignmentDataNoContentCode int = 204 /* -SaveBulkAssignmentDataOK Successfully returned bulk assignment data +SaveBulkAssignmentDataNoContent assigned -swagger:response saveBulkAssignmentDataOK +swagger:response saveBulkAssignmentDataNoContent */ -type SaveBulkAssignmentDataOK struct { - - /* - In: Body - */ - Payload *ghcmessages.BulkAssignmentData `json:"body,omitempty"` +type SaveBulkAssignmentDataNoContent struct { } -// NewSaveBulkAssignmentDataOK creates SaveBulkAssignmentDataOK with default headers values -func NewSaveBulkAssignmentDataOK() *SaveBulkAssignmentDataOK { - - return &SaveBulkAssignmentDataOK{} -} - -// WithPayload adds the payload to the save bulk assignment data o k response -func (o *SaveBulkAssignmentDataOK) WithPayload(payload *ghcmessages.BulkAssignmentData) *SaveBulkAssignmentDataOK { - o.Payload = payload - return o -} +// NewSaveBulkAssignmentDataNoContent creates SaveBulkAssignmentDataNoContent with default headers values +func NewSaveBulkAssignmentDataNoContent() *SaveBulkAssignmentDataNoContent { -// SetPayload sets the payload to the save bulk assignment data o k response -func (o *SaveBulkAssignmentDataOK) SetPayload(payload *ghcmessages.BulkAssignmentData) { - o.Payload = payload + return &SaveBulkAssignmentDataNoContent{} } // WriteResponse to the client -func (o *SaveBulkAssignmentDataOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { +func (o *SaveBulkAssignmentDataNoContent) 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 - } - } + rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses + + rw.WriteHeader(204) } // SaveBulkAssignmentDataUnauthorizedCode is the HTTP code returned for type SaveBulkAssignmentDataUnauthorized diff --git a/pkg/handlers/ghcapi/queues.go b/pkg/handlers/ghcapi/queues.go index 10aac34b35a..0dcf4857b78 100644 --- a/pkg/handlers/ghcapi/queues.go +++ b/pkg/handlers/ghcapi/queues.go @@ -740,7 +740,7 @@ func (h SaveBulkAssignmentDataHandler) Handle( return queues.NewGetServicesCounselingQueueInternalServerError(), err } - return queues.NewSaveBulkAssignmentDataOK(), nil + return queues.NewSaveBulkAssignmentDataNoContent(), nil }) } diff --git a/pkg/handlers/ghcapi/queues_test.go b/pkg/handlers/ghcapi/queues_test.go index c684d32d8e8..ccc7e906af3 100644 --- a/pkg/handlers/ghcapi/queues_test.go +++ b/pkg/handlers/ghcapi/queues_test.go @@ -2136,6 +2136,6 @@ func (suite *HandlerSuite) TestSaveBulkAssignmentDataHandler() { } response := handler.Handle(params) suite.IsNotErrResponse(response) - suite.IsType(&queues.SaveBulkAssignmentDataOK{}, response) + suite.IsType(&queues.SaveBulkAssignmentDataNoContent{}, response) }) } diff --git a/pkg/services/move/move_fetcher.go b/pkg/services/move/move_fetcher.go index fc05649dd06..a6f81949cd4 100644 --- a/pkg/services/move/move_fetcher.go +++ b/pkg/services/move/move_fetcher.go @@ -299,7 +299,7 @@ func (f moveFetcherBulkAssignment) FetchMovesForBulkAssignmentPaymentRequest(app sqlQuery := ` SELECT moves.id, - COALESCE(MIN(payment_requests.requested_at), '0001-01-01') AS earliest_date + MIN(payment_requests.requested_at) AS earliest_date FROM moves INNER JOIN orders ON orders.id = moves.orders_id INNER JOIN service_members ON orders.service_member_id = service_members.id diff --git a/swagger-def/ghc.yaml b/swagger-def/ghc.yaml index d67f9961337..8d89f5bc95d 100644 --- a/swagger-def/ghc.yaml +++ b/swagger-def/ghc.yaml @@ -3609,10 +3609,8 @@ paths: schema: $ref: '#/definitions/BulkAssignmentSavePayload' responses: - '200': - description: Successfully returned bulk assignment data - schema: - $ref: '#/definitions/BulkAssignmentData' + '204': + description: assigned '401': $ref: '#/responses/PermissionDenied' '404': diff --git a/swagger/ghc.yaml b/swagger/ghc.yaml index 9d99afeb4ab..f389fc9ef74 100644 --- a/swagger/ghc.yaml +++ b/swagger/ghc.yaml @@ -3738,10 +3738,8 @@ paths: schema: $ref: '#/definitions/BulkAssignmentSavePayload' responses: - '200': - description: Successfully returned bulk assignment data - schema: - $ref: '#/definitions/BulkAssignmentData' + '204': + description: assigned '401': $ref: '#/responses/PermissionDenied' '404': From 7261c53a613fccf2f954f27ce90c5e5ffd32d6c1 Mon Sep 17 00:00:00 2001 From: loganwc Date: Thu, 13 Feb 2025 16:24:00 +0000 Subject: [PATCH 28/35] fixed some stuff --- src/components/BulkAssignment/BulkAssignmentModal.jsx | 3 +++ src/components/BulkAssignment/BulkAssignmentModal.module.scss | 2 ++ 2 files changed, 5 insertions(+) diff --git a/src/components/BulkAssignment/BulkAssignmentModal.jsx b/src/components/BulkAssignment/BulkAssignmentModal.jsx index 1d907d2994b..a75df740d39 100644 --- a/src/components/BulkAssignment/BulkAssignmentModal.jsx +++ b/src/components/BulkAssignment/BulkAssignmentModal.jsx @@ -48,6 +48,8 @@ export const BulkAssignmentModal = ({ onClose, onSubmit, title, submitText, clos if (data.bulkAssignmentMoveIDs === undefined) { setIsDisabled(true); setNumberOfMoves(0); + } else { + setNumberOfMoves(data.bulkAssignmentMoveIDs.length); } }); } catch (err) { @@ -96,6 +98,7 @@ export const BulkAssignmentModal = ({ onClose, onSubmit, title, submitText, clos {({ handleChange, setValues, values }) => { const handleAssignmentChange = (event, i) => { handleChange(event); + setIsError(false); let newUserAssignment; if (event.target.value !== '') { diff --git a/src/components/BulkAssignment/BulkAssignmentModal.module.scss b/src/components/BulkAssignment/BulkAssignmentModal.module.scss index e6954661c8e..e126a0295f9 100644 --- a/src/components/BulkAssignment/BulkAssignmentModal.module.scss +++ b/src/components/BulkAssignment/BulkAssignmentModal.module.scss @@ -1,3 +1,5 @@ +@import 'shared/styles/colors.scss'; + .BulkModal { min-width: 650px !important; max-width: 60vw; From de05b84405f35e42789fef9c3c91994696b18432 Mon Sep 17 00:00:00 2001 From: loganwc Date: Tue, 18 Feb 2025 18:59:25 +0000 Subject: [PATCH 29/35] fixed too queue not rerendering and some misc cleanup --- pkg/handlers/ghcapi/queues.go | 6 - pkg/services/move/move_assignment_test.go | 47 ++++ .../BulkAssignment/BulkAssignmentModal.jsx | 221 +++++++++--------- .../BulkAssignmentModal.module.scss | 10 + .../HeadquartersQueues/HeadquartersQueues.jsx | 8 +- src/pages/Office/MoveQueue/MoveQueue.jsx | 4 +- 6 files changed, 170 insertions(+), 126 deletions(-) diff --git a/pkg/handlers/ghcapi/queues.go b/pkg/handlers/ghcapi/queues.go index 0dcf4857b78..5f8a602a1a5 100644 --- a/pkg/handlers/ghcapi/queues.go +++ b/pkg/handlers/ghcapi/queues.go @@ -734,12 +734,6 @@ func (h SaveBulkAssignmentDataHandler) Handle( return queues.NewGetBulkAssignmentDataInternalServerError(), err } - if err != nil { - appCtx.Logger(). - Error("error fetching list of moves for office user", zap.Error(err)) - return queues.NewGetServicesCounselingQueueInternalServerError(), err - } - return queues.NewSaveBulkAssignmentDataNoContent(), nil }) } diff --git a/pkg/services/move/move_assignment_test.go b/pkg/services/move/move_assignment_test.go index d9f612b9166..5c4ced099af 100644 --- a/pkg/services/move/move_assignment_test.go +++ b/pkg/services/move/move_assignment_test.go @@ -202,4 +202,51 @@ func (suite *MoveServiceSuite) TestBulkMoveAssignment() { suite.Equal(officeUser.ID, *move2.TOOAssignedID) suite.Nil(move3.SCAssignedID) }) + + suite.Run("successfully assigns payment requests to a TIO user", func() { + transportationOffice, move1, move2, move3 := setupTestData() + officeUser := factory.BuildOfficeUserWithPrivileges(suite.DB(), []factory.Customization{ + { + Model: models.OfficeUser{ + Email: "officeuser1@example.com", + Active: true, + }, + }, + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + { + Model: models.User{ + Privileges: []models.Privilege{ + { + PrivilegeType: models.PrivilegeTypeSupervisor, + }, + }, + Roles: []roles.Role{ + { + RoleType: roles.RoleTypeTIO, + }, + }, + }, + }, + }, nil) + + moves := []models.Move{move1, move2, move3} + userData := []*ghcmessages.BulkAssignmentForUser{ + {ID: strfmt.UUID(officeUser.ID.String()), MoveAssignments: 2}, + } + + _, err := moveAssigner.BulkMoveAssignment(suite.AppContextForTest(), string(models.QueueTypePaymentRequest), userData, moves) + suite.NoError(err) + + // reload move data to check assigned + suite.NoError(suite.DB().Reload(&move1)) + suite.NoError(suite.DB().Reload(&move2)) + + suite.Equal(officeUser.ID, *move1.TIOAssignedID) + suite.Equal(officeUser.ID, *move2.TIOAssignedID) + suite.Nil(move3.TIOAssignedID) + }) } diff --git a/src/components/BulkAssignment/BulkAssignmentModal.jsx b/src/components/BulkAssignment/BulkAssignmentModal.jsx index a75df740d39..fbe1df58755 100644 --- a/src/components/BulkAssignment/BulkAssignmentModal.jsx +++ b/src/components/BulkAssignment/BulkAssignmentModal.jsx @@ -17,7 +17,6 @@ const initialValues = { }; export const BulkAssignmentModal = ({ onClose, onSubmit, title, submitText, closeText, queueType }) => { - const [isLoading, setIsLoading] = useState(true); const [isError, setIsError] = useState(false); const [bulkAssignmentData, setBulkAssignmentData] = useState(null); const [isDisabled, setIsDisabled] = useState(false); @@ -35,7 +34,6 @@ export const BulkAssignmentModal = ({ onClose, onSubmit, title, submitText, clos officeUsers.push(newUserAssignment); }); initialValues.userData = officeUsers; - setIsLoading(false); }; useEffect(() => { @@ -68,121 +66,116 @@ export const BulkAssignmentModal = ({ onClose, onSubmit, title, submitText, clos assignment: Yup.number().min(0).typeError('Assignment must be a number'), }); - if (isLoading) return null; - return ( - - onClose()} /> - -

- {title} ({numberOfMoves}) -

-
-
- { - const totalAssignment = values?.userData?.reduce((sum, item) => sum + item.moveAssignments, 0); - - if (totalAssignment > numberOfMoves) { - setIsError(true); - return; - } - - const bulkAssignmentSavePayload = values; - onSubmit({ bulkAssignmentSavePayload }); - onClose(); - }} - validationSchema={validationSchema} - initialValues={initialValues} - > - {({ handleChange, setValues, values }) => { - const handleAssignmentChange = (event, i) => { - handleChange(event); - setIsError(false); - - let newUserAssignment; - if (event.target.value !== '') { - newUserAssignment = { - ID: event.target.id, - moveAssignments: +event.target.value, - }; - } else { - newUserAssignment = { - ID: event.target.id, - moveAssignments: 0, - }; +
+ + onClose()} /> + +

+ {title} ({numberOfMoves}) +

+
+
+ { + const totalAssignment = values?.userData?.reduce((sum, item) => sum + item.moveAssignments, 0); + + if (totalAssignment > numberOfMoves) { + setIsError(true); + return; } - const newValues = values; - newValues.userData[i] = newUserAssignment; - - setValues({ - ...values, - userData: newValues.userData, - }); - }; - - return ( -
-
-

+

{user.lastName}, {user.firstName}

- - - - - - {bulkAssignmentData?.availableOfficeUsers?.map((user, i) => { - return ( - - - - - - ); - })} -
UserWorkloadAssignment
-

- {user.lastName}, {user.firstName} -

-
-

{user.workload || 0}

-
- handleAssignmentChange(event, i)} - /> -
- - - - - {isError &&
{errorMessage}
} - - ); - }} - -
-
+ const bulkAssignmentSavePayload = values; + onSubmit({ bulkAssignmentSavePayload }); + onClose(); + }} + validationSchema={validationSchema} + initialValues={initialValues} + > + {({ handleChange, setValues, values }) => { + const handleAssignmentChange = (event, i) => { + handleChange(event); + setIsError(false); + + let newUserAssignment; + if (event.target.value !== '') { + newUserAssignment = { + ID: event.target.id, + moveAssignments: +event.target.value, + }; + } else { + newUserAssignment = { + ID: event.target.id, + moveAssignments: 0, + }; + } + + const newValues = values; + newValues.userData[i] = newUserAssignment; + + setValues({ + ...values, + userData: newValues.userData, + }); + }; + + return ( +
+ + + + + + + {bulkAssignmentData?.availableOfficeUsers?.map((user, i) => { + return ( + + + + + + ); + })} +
UserWorkloadAssignment
+

+ {user.lastName}, {user.firstName} +

+
+

{user.workload || 0}

+
+ handleAssignmentChange(event, i)} + /> +
+ + + + + {isError &&
{errorMessage}
} +
+ ); + }} + + + + ); }; diff --git a/src/components/BulkAssignment/BulkAssignmentModal.module.scss b/src/components/BulkAssignment/BulkAssignmentModal.module.scss index e126a0295f9..30eccea2cae 100644 --- a/src/components/BulkAssignment/BulkAssignmentModal.module.scss +++ b/src/components/BulkAssignment/BulkAssignmentModal.module.scss @@ -5,6 +5,16 @@ max-width: 60vw; overflow-y: auto; max-height: 90vh; + overflow-x: hidden; + + button, + :global(.usa-button) { + margin: 0; + flex-grow: 0; + flex-basis: auto; + text-decoration: none; + font-size: 1rem; + } .errorMessage { margin: 0 auto; diff --git a/src/pages/Office/HeadquartersQueues/HeadquartersQueues.jsx b/src/pages/Office/HeadquartersQueues/HeadquartersQueues.jsx index 1786b4525ff..ba5cd4919f1 100644 --- a/src/pages/Office/HeadquartersQueues/HeadquartersQueues.jsx +++ b/src/pages/Office/HeadquartersQueues/HeadquartersQueues.jsx @@ -237,7 +237,7 @@ const HeadquartersQueue = ({ isQueueManagementFFEnabled }) => { defaultSortedColumns={[{ id: 'status', desc: false }]} disableMultiSort disableSortBy={false} - columns={tooQueueColumns(moveLockFlag, isQueueManagementFFEnabled, showBranchFilter)} + columns={tooQueueColumns(moveLockFlag, isQueueManagementFFEnabled, null, showBranchFilter)} title="All moves" handleClick={handleClickNavigateToDetails} useQueries={useMovesQueueQueries} @@ -263,7 +263,7 @@ const HeadquartersQueue = ({ isQueueManagementFFEnabled }) => { defaultSortedColumns={[{ id: 'age', desc: true }]} disableMultiSort disableSortBy={false} - columns={tioQueueColumns(moveLockFlag, isQueueManagementFFEnabled, showBranchFilter)} + columns={tioQueueColumns(moveLockFlag, isQueueManagementFFEnabled, null, showBranchFilter)} title="Payment requests" handleClick={handleClickNavigateToPaymentRequests} useQueries={usePaymentRequestQueueQueries} @@ -289,7 +289,7 @@ const HeadquartersQueue = ({ isQueueManagementFFEnabled }) => { defaultSortedColumns={[{ id: 'closeoutInitiated', desc: false }]} disableMultiSort disableSortBy={false} - columns={closeoutColumns(moveLockFlag, inPPMCloseoutGBLOC, null, null, isQueueManagementFFEnabled)} + columns={closeoutColumns(moveLockFlag, inPPMCloseoutGBLOC, null, null, isQueueManagementFFEnabled, null)} title="Moves" handleClick={handleClickNavigateToDetails} useQueries={useServicesCounselingQueuePPMQueries} @@ -316,7 +316,7 @@ const HeadquartersQueue = ({ isQueueManagementFFEnabled }) => { defaultSortedColumns={[{ id: 'submittedAt', desc: false }]} disableMultiSort disableSortBy={false} - columns={counselingColumns(moveLockFlag, null, null, isQueueManagementFFEnabled)} + columns={counselingColumns(moveLockFlag, null, null, isQueueManagementFFEnabled, null)} title="Moves" handleClick={handleClickNavigateToDetails} useQueries={useServicesCounselingQueueQueries} diff --git a/src/pages/Office/MoveQueue/MoveQueue.jsx b/src/pages/Office/MoveQueue/MoveQueue.jsx index 7854cc3b61f..5fea721e656 100644 --- a/src/pages/Office/MoveQueue/MoveQueue.jsx +++ b/src/pages/Office/MoveQueue/MoveQueue.jsx @@ -171,7 +171,7 @@ export const columns = (moveLockFlag, isQueueManagementEnabled, setRefetchQueue, title="Assigned dropdown" > - {row.availableOfficeUsers?.map(({ lastName, firstName, officeUserId }) => ( + {row.availableOfficeUsers.map(({ lastName, firstName, officeUserId }) => (