diff --git a/go.mod b/go.mod index e12bab64a3e..35a96b64286 100644 --- a/go.mod +++ b/go.mod @@ -96,7 +96,7 @@ require ( golang.org/x/oauth2 v0.20.0 golang.org/x/text v0.16.0 golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d - google.golang.org/grpc v1.64.0 + google.golang.org/grpc v1.64.1 gopkg.in/dnaeon/go-vcr.v3 v3.2.0 gotest.tools/gotestsum v1.12.0 pault.ag/go/pksigner v1.0.2 diff --git a/go.sum b/go.sum index b3cf1002289..d18dcea6e5e 100644 --- a/go.sum +++ b/go.sum @@ -906,8 +906,8 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= -google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= diff --git a/pkg/gen/ghcapi/configure_mymove.go b/pkg/gen/ghcapi/configure_mymove.go index 6b1f9d72204..1b64e1035af 100644 --- a/pkg/gen/ghcapi/configure_mymove.go +++ b/pkg/gen/ghcapi/configure_mymove.go @@ -299,6 +299,11 @@ func configureAPI(api *ghcoperations.MymoveAPI) http.Handler { return middleware.NotImplemented("operation report_violations.GetReportViolationsByReportID has not yet been implemented") }) } + if api.QueuesGetServicesCounselingOriginListHandler == nil { + api.QueuesGetServicesCounselingOriginListHandler = queues.GetServicesCounselingOriginListHandlerFunc(func(params queues.GetServicesCounselingOriginListParams) middleware.Responder { + return middleware.NotImplemented("operation queues.GetServicesCounselingOriginList has not yet been implemented") + }) + } if api.QueuesGetServicesCounselingQueueHandler == nil { api.QueuesGetServicesCounselingQueueHandler = queues.GetServicesCounselingQueueHandlerFunc(func(params queues.GetServicesCounselingQueueParams) middleware.Responder { return middleware.NotImplemented("operation queues.GetServicesCounselingQueue has not yet been implemented") diff --git a/pkg/gen/ghcapi/embedded_spec.go b/pkg/gen/ghcapi/embedded_spec.go index c8fd5824f0f..baca5d44671 100644 --- a/pkg/gen/ghcapi/embedded_spec.go +++ b/pkg/gen/ghcapi/embedded_spec.go @@ -3797,7 +3797,12 @@ func init() { "in": "query" }, { - "type": "string", + "uniqueItems": true, + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "multi", "description": "filters the name of the origin duty location on the orders", "name": "originDutyLocation", "in": "query" @@ -3884,6 +3889,41 @@ func init() { } } }, + "/queues/counseling/origin-list": { + "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. This pulls the availalble origin duty locations.\n", + "produces": [ + "application/json" + ], + "tags": [ + "queues" + ], + "summary": "Gets queued list of all moves origin locations in the counselors queue", + "operationId": "getServicesCounselingOriginList", + "parameters": [ + { + "type": "boolean", + "description": "Only used for Services Counseling queue. If true, show PPM moves origin locations that are ready for closeout. Otherwise, show all other moves origin locations.", + "name": "needsPPMCloseout", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Successfully returned all moves matching the criteria", + "schema": { + "$ref": "#/definitions/Locations" + } + }, + "403": { + "$ref": "#/responses/PermissionDenied" + }, + "500": { + "$ref": "#/responses/ServerError" + } + } + } + }, "/queues/moves": { "get": { "description": "An office TOO user will be assigned a transportation office that will determine which moves are displayed in their queue based on the origin duty location. GHC moves will show up here onced they have reached the submitted status sent by the customer and have move task orders, shipments, and service items to approve.\n", @@ -3956,7 +3996,12 @@ func init() { "in": "query" }, { - "type": "string", + "uniqueItems": true, + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "multi", "name": "originDutyLocation", "in": "query" }, @@ -7537,6 +7582,29 @@ func init() { } } }, + "Location": { + "type": "object", + "required": [ + "label", + "value" + ], + "properties": { + "label": { + "type": "string", + "example": "Label for display" + }, + "value": { + "type": "string", + "example": "Value for location" + } + } + }, + "Locations": { + "type": "array", + "items": { + "$ref": "#/definitions/Location" + } + }, "LockedOfficeUser": { "type": "object", "properties": { @@ -17433,7 +17501,12 @@ func init() { "in": "query" }, { - "type": "string", + "uniqueItems": true, + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "multi", "description": "filters the name of the origin duty location on the orders", "name": "originDutyLocation", "in": "query" @@ -17526,6 +17599,47 @@ func init() { } } }, + "/queues/counseling/origin-list": { + "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. This pulls the availalble origin duty locations.\n", + "produces": [ + "application/json" + ], + "tags": [ + "queues" + ], + "summary": "Gets queued list of all moves origin locations in the counselors queue", + "operationId": "getServicesCounselingOriginList", + "parameters": [ + { + "type": "boolean", + "description": "Only used for Services Counseling queue. If true, show PPM moves origin locations that are ready for closeout. Otherwise, show all other moves origin locations.", + "name": "needsPPMCloseout", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Successfully returned all moves matching the criteria", + "schema": { + "$ref": "#/definitions/Locations" + } + }, + "403": { + "description": "The request was denied", + "schema": { + "$ref": "#/definitions/Error" + } + }, + "500": { + "description": "A server error occurred", + "schema": { + "$ref": "#/definitions/Error" + } + } + } + } + }, "/queues/moves": { "get": { "description": "An office TOO user will be assigned a transportation office that will determine which moves are displayed in their queue based on the origin duty location. GHC moves will show up here onced they have reached the submitted status sent by the customer and have move task orders, shipments, and service items to approve.\n", @@ -17598,7 +17712,12 @@ func init() { "in": "query" }, { - "type": "string", + "uniqueItems": true, + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "multi", "name": "originDutyLocation", "in": "query" }, @@ -21522,6 +21641,29 @@ func init() { } } }, + "Location": { + "type": "object", + "required": [ + "label", + "value" + ], + "properties": { + "label": { + "type": "string", + "example": "Label for display" + }, + "value": { + "type": "string", + "example": "Value for location" + } + } + }, + "Locations": { + "type": "array", + "items": { + "$ref": "#/definitions/Location" + } + }, "LockedOfficeUser": { "type": "object", "properties": { diff --git a/pkg/gen/ghcapi/ghcoperations/mymove_api.go b/pkg/gen/ghcapi/ghcoperations/mymove_api.go index 62bc937215a..89e0a2606b2 100644 --- a/pkg/gen/ghcapi/ghcoperations/mymove_api.go +++ b/pkg/gen/ghcapi/ghcoperations/mymove_api.go @@ -206,6 +206,9 @@ func NewMymoveAPI(spec *loads.Document) *MymoveAPI { ReportViolationsGetReportViolationsByReportIDHandler: report_violations.GetReportViolationsByReportIDHandlerFunc(func(params report_violations.GetReportViolationsByReportIDParams) middleware.Responder { return middleware.NotImplemented("operation report_violations.GetReportViolationsByReportID has not yet been implemented") }), + QueuesGetServicesCounselingOriginListHandler: queues.GetServicesCounselingOriginListHandlerFunc(func(params queues.GetServicesCounselingOriginListParams) middleware.Responder { + return middleware.NotImplemented("operation queues.GetServicesCounselingOriginList has not yet been implemented") + }), QueuesGetServicesCounselingQueueHandler: queues.GetServicesCounselingQueueHandlerFunc(func(params queues.GetServicesCounselingQueueParams) middleware.Responder { return middleware.NotImplemented("operation queues.GetServicesCounselingQueue has not yet been implemented") }), @@ -476,6 +479,8 @@ type MymoveAPI struct { QueuesGetPaymentRequestsQueueHandler queues.GetPaymentRequestsQueueHandler // ReportViolationsGetReportViolationsByReportIDHandler sets the operation handler for the get report violations by report ID operation ReportViolationsGetReportViolationsByReportIDHandler report_violations.GetReportViolationsByReportIDHandler + // QueuesGetServicesCounselingOriginListHandler sets the operation handler for the get services counseling origin list operation + QueuesGetServicesCounselingOriginListHandler queues.GetServicesCounselingOriginListHandler // QueuesGetServicesCounselingQueueHandler sets the operation handler for the get services counseling queue operation QueuesGetServicesCounselingQueueHandler queues.GetServicesCounselingQueueHandler // MtoShipmentGetShipmentHandler sets the operation handler for the get shipment operation @@ -785,6 +790,9 @@ func (o *MymoveAPI) Validate() error { if o.ReportViolationsGetReportViolationsByReportIDHandler == nil { unregistered = append(unregistered, "report_violations.GetReportViolationsByReportIDHandler") } + if o.QueuesGetServicesCounselingOriginListHandler == nil { + unregistered = append(unregistered, "queues.GetServicesCounselingOriginListHandler") + } if o.QueuesGetServicesCounselingQueueHandler == nil { unregistered = append(unregistered, "queues.GetServicesCounselingQueueHandler") } @@ -1196,6 +1204,10 @@ func (o *MymoveAPI) initHandlerCache() { if o.handlers["GET"] == nil { o.handlers["GET"] = make(map[string]http.Handler) } + o.handlers["GET"]["/queues/counseling/origin-list"] = queues.NewGetServicesCounselingOriginList(o.context, o.QueuesGetServicesCounselingOriginListHandler) + if o.handlers["GET"] == nil { + o.handlers["GET"] = make(map[string]http.Handler) + } o.handlers["GET"]["/queues/counseling"] = queues.NewGetServicesCounselingQueue(o.context, o.QueuesGetServicesCounselingQueueHandler) if o.handlers["GET"] == nil { o.handlers["GET"] = make(map[string]http.Handler) diff --git a/pkg/gen/ghcapi/ghcoperations/queues/get_moves_queue_parameters.go b/pkg/gen/ghcapi/ghcoperations/queues/get_moves_queue_parameters.go index fc3ab67853a..d1b2e29857f 100644 --- a/pkg/gen/ghcapi/ghcoperations/queues/get_moves_queue_parameters.go +++ b/pkg/gen/ghcapi/ghcoperations/queues/get_moves_queue_parameters.go @@ -67,9 +67,11 @@ type GetMovesQueueParams struct { */ OrderType *string /* + Unique: true In: query + Collection Format: multi */ - OriginDutyLocation *string + OriginDutyLocation []string /*requested page of results In: query */ @@ -356,21 +358,38 @@ func (o *GetMovesQueueParams) bindOrderType(rawData []string, hasKey bool, forma return nil } -// bindOriginDutyLocation binds and validates parameter OriginDutyLocation from query. +// bindOriginDutyLocation binds and validates array parameter OriginDutyLocation from query. +// +// Arrays are parsed according to CollectionFormat: "multi" (defaults to "csv" when empty). func (o *GetMovesQueueParams) bindOriginDutyLocation(rawData []string, hasKey bool, formats strfmt.Registry) error { - var raw string - if len(rawData) > 0 { - raw = rawData[len(rawData)-1] + // CollectionFormat: multi + originDutyLocationIC := rawData + if len(originDutyLocationIC) == 0 { + return nil } - // Required: false - // AllowEmptyValue: false + var originDutyLocationIR []string + for _, originDutyLocationIV := range originDutyLocationIC { + originDutyLocationI := originDutyLocationIV - if raw == "" { // empty values pass all other validations - return nil + originDutyLocationIR = append(originDutyLocationIR, originDutyLocationI) } - o.OriginDutyLocation = &raw + o.OriginDutyLocation = originDutyLocationIR + if err := o.validateOriginDutyLocation(formats); err != nil { + return err + } + + return nil +} + +// validateOriginDutyLocation carries on validations for parameter OriginDutyLocation +func (o *GetMovesQueueParams) validateOriginDutyLocation(formats strfmt.Registry) error { + + // uniqueItems: true + if err := validate.UniqueItems("originDutyLocation", "query", o.OriginDutyLocation); err != nil { + return err + } return nil } diff --git a/pkg/gen/ghcapi/ghcoperations/queues/get_moves_queue_urlbuilder.go b/pkg/gen/ghcapi/ghcoperations/queues/get_moves_queue_urlbuilder.go index 58e60db93f4..946968fd504 100644 --- a/pkg/gen/ghcapi/ghcoperations/queues/get_moves_queue_urlbuilder.go +++ b/pkg/gen/ghcapi/ghcoperations/queues/get_moves_queue_urlbuilder.go @@ -24,7 +24,7 @@ type GetMovesQueueURL struct { Locator *string Order *string OrderType *string - OriginDutyLocation *string + OriginDutyLocation []string Page *int64 PerPage *int64 RequestedMoveDate *string @@ -129,12 +129,18 @@ func (o *GetMovesQueueURL) Build() (*url.URL, error) { qs.Set("orderType", orderTypeQ) } - var originDutyLocationQ string - if o.OriginDutyLocation != nil { - originDutyLocationQ = *o.OriginDutyLocation + var originDutyLocationIR []string + for _, originDutyLocationI := range o.OriginDutyLocation { + originDutyLocationIS := originDutyLocationI + if originDutyLocationIS != "" { + originDutyLocationIR = append(originDutyLocationIR, originDutyLocationIS) + } } - if originDutyLocationQ != "" { - qs.Set("originDutyLocation", originDutyLocationQ) + + originDutyLocation := swag.JoinByFormat(originDutyLocationIR, "multi") + + for _, qsv := range originDutyLocation { + qs.Add("originDutyLocation", qsv) } var pageQ string diff --git a/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_origin_list.go b/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_origin_list.go new file mode 100644 index 00000000000..7cdd86ead69 --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_origin_list.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" +) + +// GetServicesCounselingOriginListHandlerFunc turns a function with the right signature into a get services counseling origin list handler +type GetServicesCounselingOriginListHandlerFunc func(GetServicesCounselingOriginListParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn GetServicesCounselingOriginListHandlerFunc) Handle(params GetServicesCounselingOriginListParams) middleware.Responder { + return fn(params) +} + +// GetServicesCounselingOriginListHandler interface for that can handle valid get services counseling origin list params +type GetServicesCounselingOriginListHandler interface { + Handle(GetServicesCounselingOriginListParams) middleware.Responder +} + +// NewGetServicesCounselingOriginList creates a new http.Handler for the get services counseling origin list operation +func NewGetServicesCounselingOriginList(ctx *middleware.Context, handler GetServicesCounselingOriginListHandler) *GetServicesCounselingOriginList { + return &GetServicesCounselingOriginList{Context: ctx, Handler: handler} +} + +/* + GetServicesCounselingOriginList swagger:route GET /queues/counseling/origin-list queues getServicesCounselingOriginList + +# Gets queued list of all moves origin locations in the counselors queue + +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. This pulls the availalble origin duty locations. +*/ +type GetServicesCounselingOriginList struct { + Context *middleware.Context + Handler GetServicesCounselingOriginListHandler +} + +func (o *GetServicesCounselingOriginList) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewGetServicesCounselingOriginListParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_origin_list_parameters.go b/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_origin_list_parameters.go new file mode 100644 index 00000000000..5e55bad2298 --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_origin_list_parameters.go @@ -0,0 +1,83 @@ +// 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/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// NewGetServicesCounselingOriginListParams creates a new GetServicesCounselingOriginListParams object +// +// There are no default values defined in the spec. +func NewGetServicesCounselingOriginListParams() GetServicesCounselingOriginListParams { + + return GetServicesCounselingOriginListParams{} +} + +// GetServicesCounselingOriginListParams contains all the bound params for the get services counseling origin list operation +// typically these are obtained from a http.Request +// +// swagger:parameters getServicesCounselingOriginList +type GetServicesCounselingOriginListParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /*Only used for Services Counseling queue. If true, show PPM moves origin locations that are ready for closeout. Otherwise, show all other moves origin locations. + In: query + */ + NeedsPPMCloseout *bool +} + +// 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 NewGetServicesCounselingOriginListParams() beforehand. +func (o *GetServicesCounselingOriginListParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + qs := runtime.Values(r.URL.Query()) + + qNeedsPPMCloseout, qhkNeedsPPMCloseout, _ := qs.GetOK("needsPPMCloseout") + if err := o.bindNeedsPPMCloseout(qNeedsPPMCloseout, qhkNeedsPPMCloseout, route.Formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindNeedsPPMCloseout binds and validates parameter NeedsPPMCloseout from query. +func (o *GetServicesCounselingOriginListParams) bindNeedsPPMCloseout(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + + value, err := swag.ConvertBool(raw) + if err != nil { + return errors.InvalidType("needsPPMCloseout", "query", "bool", raw) + } + o.NeedsPPMCloseout = &value + + return nil +} diff --git a/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_origin_list_responses.go b/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_origin_list_responses.go new file mode 100644 index 00000000000..d4c1355b782 --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_origin_list_responses.go @@ -0,0 +1,152 @@ +// 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" +) + +// GetServicesCounselingOriginListOKCode is the HTTP code returned for type GetServicesCounselingOriginListOK +const GetServicesCounselingOriginListOKCode int = 200 + +/* +GetServicesCounselingOriginListOK Successfully returned all moves matching the criteria + +swagger:response getServicesCounselingOriginListOK +*/ +type GetServicesCounselingOriginListOK struct { + + /* + In: Body + */ + Payload ghcmessages.Locations `json:"body,omitempty"` +} + +// NewGetServicesCounselingOriginListOK creates GetServicesCounselingOriginListOK with default headers values +func NewGetServicesCounselingOriginListOK() *GetServicesCounselingOriginListOK { + + return &GetServicesCounselingOriginListOK{} +} + +// WithPayload adds the payload to the get services counseling origin list o k response +func (o *GetServicesCounselingOriginListOK) WithPayload(payload ghcmessages.Locations) *GetServicesCounselingOriginListOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get services counseling origin list o k response +func (o *GetServicesCounselingOriginListOK) SetPayload(payload ghcmessages.Locations) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetServicesCounselingOriginListOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + payload := o.Payload + if payload == nil { + // return empty array + payload = ghcmessages.Locations{} + } + + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } +} + +// GetServicesCounselingOriginListForbiddenCode is the HTTP code returned for type GetServicesCounselingOriginListForbidden +const GetServicesCounselingOriginListForbiddenCode int = 403 + +/* +GetServicesCounselingOriginListForbidden The request was denied + +swagger:response getServicesCounselingOriginListForbidden +*/ +type GetServicesCounselingOriginListForbidden struct { + + /* + In: Body + */ + Payload *ghcmessages.Error `json:"body,omitempty"` +} + +// NewGetServicesCounselingOriginListForbidden creates GetServicesCounselingOriginListForbidden with default headers values +func NewGetServicesCounselingOriginListForbidden() *GetServicesCounselingOriginListForbidden { + + return &GetServicesCounselingOriginListForbidden{} +} + +// WithPayload adds the payload to the get services counseling origin list forbidden response +func (o *GetServicesCounselingOriginListForbidden) WithPayload(payload *ghcmessages.Error) *GetServicesCounselingOriginListForbidden { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get services counseling origin list forbidden response +func (o *GetServicesCounselingOriginListForbidden) SetPayload(payload *ghcmessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetServicesCounselingOriginListForbidden) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(403) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// GetServicesCounselingOriginListInternalServerErrorCode is the HTTP code returned for type GetServicesCounselingOriginListInternalServerError +const GetServicesCounselingOriginListInternalServerErrorCode int = 500 + +/* +GetServicesCounselingOriginListInternalServerError A server error occurred + +swagger:response getServicesCounselingOriginListInternalServerError +*/ +type GetServicesCounselingOriginListInternalServerError struct { + + /* + In: Body + */ + Payload *ghcmessages.Error `json:"body,omitempty"` +} + +// NewGetServicesCounselingOriginListInternalServerError creates GetServicesCounselingOriginListInternalServerError with default headers values +func NewGetServicesCounselingOriginListInternalServerError() *GetServicesCounselingOriginListInternalServerError { + + return &GetServicesCounselingOriginListInternalServerError{} +} + +// WithPayload adds the payload to the get services counseling origin list internal server error response +func (o *GetServicesCounselingOriginListInternalServerError) WithPayload(payload *ghcmessages.Error) *GetServicesCounselingOriginListInternalServerError { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get services counseling origin list internal server error response +func (o *GetServicesCounselingOriginListInternalServerError) SetPayload(payload *ghcmessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetServicesCounselingOriginListInternalServerError) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(500) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_origin_list_urlbuilder.go b/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_origin_list_urlbuilder.go new file mode 100644 index 00000000000..1a71962fdd6 --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_origin_list_urlbuilder.go @@ -0,0 +1,105 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package queues + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" + + "github.com/go-openapi/swag" +) + +// GetServicesCounselingOriginListURL generates an URL for the get services counseling origin list operation +type GetServicesCounselingOriginListURL struct { + NeedsPPMCloseout *bool + + _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 *GetServicesCounselingOriginListURL) WithBasePath(bp string) *GetServicesCounselingOriginListURL { + 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 *GetServicesCounselingOriginListURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *GetServicesCounselingOriginListURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/queues/counseling/origin-list" + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/ghc/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + qs := make(url.Values) + + var needsPPMCloseoutQ string + if o.NeedsPPMCloseout != nil { + needsPPMCloseoutQ = swag.FormatBool(*o.NeedsPPMCloseout) + } + if needsPPMCloseoutQ != "" { + qs.Set("needsPPMCloseout", needsPPMCloseoutQ) + } + + _result.RawQuery = qs.Encode() + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *GetServicesCounselingOriginListURL) 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 *GetServicesCounselingOriginListURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *GetServicesCounselingOriginListURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on GetServicesCounselingOriginListURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on GetServicesCounselingOriginListURL") + } + + 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 *GetServicesCounselingOriginListURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_parameters.go b/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_parameters.go index 1cfea089e86..3a2c3ec3d59 100644 --- a/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_parameters.go +++ b/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_parameters.go @@ -75,9 +75,11 @@ type GetServicesCounselingQueueParams struct { */ OrderType *string /*filters the name of the origin duty location on the orders + Unique: true In: query + Collection Format: multi */ - OriginDutyLocation *string + OriginDutyLocation []string /*filters the GBLOC of the service member's origin duty location In: query */ @@ -451,21 +453,38 @@ func (o *GetServicesCounselingQueueParams) bindOrderType(rawData []string, hasKe return nil } -// bindOriginDutyLocation binds and validates parameter OriginDutyLocation from query. +// bindOriginDutyLocation binds and validates array parameter OriginDutyLocation from query. +// +// Arrays are parsed according to CollectionFormat: "multi" (defaults to "csv" when empty). func (o *GetServicesCounselingQueueParams) bindOriginDutyLocation(rawData []string, hasKey bool, formats strfmt.Registry) error { - var raw string - if len(rawData) > 0 { - raw = rawData[len(rawData)-1] + // CollectionFormat: multi + originDutyLocationIC := rawData + if len(originDutyLocationIC) == 0 { + return nil } - // Required: false - // AllowEmptyValue: false + var originDutyLocationIR []string + for _, originDutyLocationIV := range originDutyLocationIC { + originDutyLocationI := originDutyLocationIV - if raw == "" { // empty values pass all other validations - return nil + originDutyLocationIR = append(originDutyLocationIR, originDutyLocationI) } - o.OriginDutyLocation = &raw + o.OriginDutyLocation = originDutyLocationIR + if err := o.validateOriginDutyLocation(formats); err != nil { + return err + } + + return nil +} + +// validateOriginDutyLocation carries on validations for parameter OriginDutyLocation +func (o *GetServicesCounselingQueueParams) validateOriginDutyLocation(formats strfmt.Registry) error { + + // uniqueItems: true + if err := validate.UniqueItems("originDutyLocation", "query", o.OriginDutyLocation); err != nil { + return err + } return nil } diff --git a/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_urlbuilder.go b/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_urlbuilder.go index d205028476e..b6bf4eca32d 100644 --- a/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_urlbuilder.go +++ b/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_urlbuilder.go @@ -26,7 +26,7 @@ type GetServicesCounselingQueueURL struct { NeedsPPMCloseout *bool Order *string OrderType *string - OriginDutyLocation *string + OriginDutyLocation []string OriginGBLOC *string Page *int64 PerPage *int64 @@ -151,12 +151,18 @@ func (o *GetServicesCounselingQueueURL) Build() (*url.URL, error) { qs.Set("orderType", orderTypeQ) } - var originDutyLocationQ string - if o.OriginDutyLocation != nil { - originDutyLocationQ = *o.OriginDutyLocation + var originDutyLocationIR []string + for _, originDutyLocationI := range o.OriginDutyLocation { + originDutyLocationIS := originDutyLocationI + if originDutyLocationIS != "" { + originDutyLocationIR = append(originDutyLocationIR, originDutyLocationIS) + } } - if originDutyLocationQ != "" { - qs.Set("originDutyLocation", originDutyLocationQ) + + originDutyLocation := swag.JoinByFormat(originDutyLocationIR, "multi") + + for _, qsv := range originDutyLocation { + qs.Add("originDutyLocation", qsv) } var originGBLOCQ string diff --git a/pkg/gen/ghcmessages/location.go b/pkg/gen/ghcmessages/location.go new file mode 100644 index 00000000000..cfe5a0e0f4e --- /dev/null +++ b/pkg/gen/ghcmessages/location.go @@ -0,0 +1,90 @@ +// 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" +) + +// Location location +// +// swagger:model Location +type Location struct { + + // label + // Example: Label for display + // Required: true + Label *string `json:"label"` + + // value + // Example: Value for location + // Required: true + Value *string `json:"value"` +} + +// Validate validates this location +func (m *Location) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateLabel(formats); err != nil { + res = append(res, err) + } + + if err := m.validateValue(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *Location) validateLabel(formats strfmt.Registry) error { + + if err := validate.Required("label", "body", m.Label); err != nil { + return err + } + + return nil +} + +func (m *Location) validateValue(formats strfmt.Registry) error { + + if err := validate.Required("value", "body", m.Value); err != nil { + return err + } + + return nil +} + +// ContextValidate validates this location based on context it is used +func (m *Location) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *Location) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *Location) UnmarshalBinary(b []byte) error { + var res Location + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/pkg/gen/ghcmessages/locations.go b/pkg/gen/ghcmessages/locations.go new file mode 100644 index 00000000000..073139e4061 --- /dev/null +++ b/pkg/gen/ghcmessages/locations.go @@ -0,0 +1,78 @@ +// 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" +) + +// Locations locations +// +// swagger:model Locations +type Locations []*Location + +// Validate validates this locations +func (m Locations) Validate(formats strfmt.Registry) error { + var res []error + + for i := 0; i < len(m); i++ { + if swag.IsZero(m[i]) { // not required + continue + } + + if m[i] != nil { + if err := m[i].Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName(strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName(strconv.Itoa(i)) + } + return err + } + } + + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// ContextValidate validate this locations based on the context it is used +func (m Locations) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + for i := 0; i < len(m); i++ { + + if m[i] != nil { + + if swag.IsZero(m[i]) { // not required + return nil + } + + if err := m[i].ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName(strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName(strconv.Itoa(i)) + } + return err + } + } + + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/pkg/handlers/ghcapi/api.go b/pkg/handlers/ghcapi/api.go index 0bcb0da5ef3..ae8baf88810 100644 --- a/pkg/handlers/ghcapi/api.go +++ b/pkg/handlers/ghcapi/api.go @@ -525,6 +525,11 @@ func NewGhcAPIHandler(handlerConfig handlers.HandlerConfig) *ghcops.MymoveAPI { movelocker.NewMoveUnlocker(), } + ghcAPI.QueuesGetServicesCounselingOriginListHandler = GetServicesCounselingOriginListHandler{ + handlerConfig, + order.NewOrderFetcher(), + } + ghcAPI.TacTacValidationHandler = TacValidationHandler{ handlerConfig, } diff --git a/pkg/handlers/ghcapi/queues.go b/pkg/handlers/ghcapi/queues.go index 4775a6890d3..2f4ea541757 100644 --- a/pkg/handlers/ghcapi/queues.go +++ b/pkg/handlers/ghcapi/queues.go @@ -2,6 +2,7 @@ package ghcapi import ( "fmt" + "slices" "github.com/go-openapi/runtime/middleware" "github.com/gobuffalo/pop/v6" @@ -369,3 +370,58 @@ func (h GetServicesCounselingQueueHandler) Handle( return queues.NewGetServicesCounselingQueueOK().WithPayload(result), nil }) } + +// GetServicesCounselingOriginListHandler returns the origin list for the Service Counselor user via GET /queues/counselor/origin-list +type GetServicesCounselingOriginListHandler struct { + handlers.HandlerConfig + services.OrderFetcher +} + +// Handle returns the list of origin list for the services counselor +func (h GetServicesCounselingOriginListHandler) Handle( + params queues.GetServicesCounselingOriginListParams, +) middleware.Responder { + return h.AuditableAppContextFromRequestWithErrors(params.HTTPRequest, + func(appCtx appcontext.AppContext) (middleware.Responder, error) { + if !appCtx.Session().IsOfficeUser() || + !appCtx.Session().Roles.HasRole(roles.RoleTypeServicesCounselor) { + forbiddenErr := apperror.NewForbiddenError( + "user is not authenticated with an office role", + ) + appCtx.Logger().Error(forbiddenErr.Error()) + return queues.NewGetServicesCounselingQueueForbidden(), forbiddenErr + } + + ListOrderParams := services.ListOrderParams{ + NeedsPPMCloseout: params.NeedsPPMCloseout, + } + + if params.NeedsPPMCloseout != nil && *params.NeedsPPMCloseout { + ListOrderParams.Status = []string{string(models.MoveStatusAPPROVED), string(models.MoveStatusServiceCounselingCompleted)} + } else { + ListOrderParams.Status = []string{string(models.MoveStatusNeedsServiceCounseling)} + } + + moves, err := h.OrderFetcher.ListAllOrderLocations( + appCtx, + appCtx.Session().OfficeUserID, + &ListOrderParams, + ) + if err != nil { + appCtx.Logger(). + Error("error fetching list of moves for office user", zap.Error(err)) + return queues.NewGetServicesCounselingQueueInternalServerError(), err + } + + var originLocationList []*ghcmessages.Location + for _, value := range moves { + locationString := value.Orders.OriginDutyLocation.Name + location := ghcmessages.Location{Label: &locationString, Value: &locationString} + if !slices.Contains(originLocationList, &location) { + originLocationList = append(originLocationList, &location) + } + } + + return queues.NewGetServicesCounselingOriginListOK().WithPayload(originLocationList), nil + }) +} diff --git a/pkg/handlers/ghcapi/queues_test.go b/pkg/handlers/ghcapi/queues_test.go index 84ee35dd18f..b02539f1769 100644 --- a/pkg/handlers/ghcapi/queues_test.go +++ b/pkg/handlers/ghcapi/queues_test.go @@ -848,9 +848,11 @@ func (suite *HandlerSuite) TestGetMoveQueuesHandlerCustomerInfoFilters() { }) suite.Run("returns results matching OriginDutyLocation name search term", func() { + var originDutyLocations []string + originDutyLocations = append(originDutyLocations, dutyLocation1.Name) params := queues.GetMovesQueueParams{ HTTPRequest: request, - OriginDutyLocation: &dutyLocation1.Name, + OriginDutyLocation: originDutyLocations, } // Validate incoming payload: no body to validate @@ -869,12 +871,14 @@ func (suite *HandlerSuite) TestGetMoveQueuesHandlerCustomerInfoFilters() { }) suite.Run("returns results with multiple filters applied", func() { + var originDutyLocations []string + originDutyLocations = append(originDutyLocations, dutyLocation1.Name) params := queues.GetMovesQueueParams{ HTTPRequest: request, LastName: models.StringPointer("Dar"), DodID: serviceMember1.Edipi, Locator: &move1.Locator, - OriginDutyLocation: &dutyLocation1.Name, + OriginDutyLocation: originDutyLocations, } // Validate incoming payload: no body to validate diff --git a/pkg/models/privileges.go b/pkg/models/privileges.go index dbe6e420dba..58982bcae7f 100644 --- a/pkg/models/privileges.go +++ b/pkg/models/privileges.go @@ -21,6 +21,12 @@ const ( PrivilegeTypeSafety PrivilegeType = "safety" ) +const ( + // PrivilegeTypeSupervisor is the Task Ordering Officer Role + PrivilegeSearchTypeSupervisor PrivilegeType = "SUPERVISOR" + PrivilegeSearchTypeSafety PrivilegeType = "SAFETY" +) + // Privilege represents a Privilege for users type Privilege struct { ID uuid.UUID `json:"id" db:"id"` diff --git a/pkg/services/mocks/OrderFetcher.go b/pkg/services/mocks/OrderFetcher.go index 0c1afcff3a4..19362c5b4db 100644 --- a/pkg/services/mocks/OrderFetcher.go +++ b/pkg/services/mocks/OrderFetcher.go @@ -44,6 +44,32 @@ func (_m *OrderFetcher) FetchOrder(appCtx appcontext.AppContext, orderID uuid.UU return r0, r1 } +// ListAllOrderLocations provides a mock function with given fields: appCtx, officeUserID, params +func (_m *OrderFetcher) ListAllOrderLocations(appCtx appcontext.AppContext, officeUserID uuid.UUID, params *services.ListOrderParams) ([]models.Move, error) { + ret := _m.Called(appCtx, officeUserID, params) + + var r0 []models.Move + var r1 error + if rf, ok := ret.Get(0).(func(appcontext.AppContext, uuid.UUID, *services.ListOrderParams) ([]models.Move, error)); ok { + return rf(appCtx, officeUserID, params) + } + if rf, ok := ret.Get(0).(func(appcontext.AppContext, uuid.UUID, *services.ListOrderParams) []models.Move); ok { + r0 = rf(appCtx, officeUserID, params) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]models.Move) + } + } + + if rf, ok := ret.Get(1).(func(appcontext.AppContext, uuid.UUID, *services.ListOrderParams) error); ok { + r1 = rf(appCtx, officeUserID, params) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // ListOrders provides a mock function with given fields: appCtx, officeUserID, params func (_m *OrderFetcher) ListOrders(appCtx appcontext.AppContext, officeUserID uuid.UUID, params *services.ListOrderParams) ([]models.Move, int, error) { ret := _m.Called(appCtx, officeUserID, params) diff --git a/pkg/services/order.go b/pkg/services/order.go index d4d22997c52..c3f11b6fb0e 100644 --- a/pkg/services/order.go +++ b/pkg/services/order.go @@ -19,6 +19,7 @@ import ( type OrderFetcher interface { FetchOrder(appCtx appcontext.AppContext, orderID uuid.UUID) (*models.Order, error) ListOrders(appCtx appcontext.AppContext, officeUserID uuid.UUID, params *ListOrderParams) ([]models.Move, int, error) + ListAllOrderLocations(appCtx appcontext.AppContext, officeUserID uuid.UUID, params *ListOrderParams) ([]models.Move, error) } // OrderUpdater is the service object interface for updating fields of an Order @@ -49,7 +50,7 @@ type ListOrderParams struct { DodID *string LastName *string DestinationDutyLocation *string - OriginDutyLocation *string + OriginDutyLocation []string OriginGBLOC *string SubmittedAt *time.Time AppearedInTOOAt *time.Time diff --git a/pkg/services/order/order_fetcher.go b/pkg/services/order/order_fetcher.go index 7cd76f07867..c95e1634524 100644 --- a/pkg/services/order/order_fetcher.go +++ b/pkg/services/order/order_fetcher.go @@ -278,6 +278,166 @@ func (f orderFetcher) ListOrders(appCtx appcontext.AppContext, officeUserID uuid return moves, count, nil } +func (f orderFetcher) ListAllOrderLocations(appCtx appcontext.AppContext, officeUserID uuid.UUID, params *services.ListOrderParams) ([]models.Move, error) { + var moves []models.Move + var transportationOffice models.TransportationOffice + // select the GBLOC associated with the transportation office of the session's current office user + err := appCtx.DB().Q(). + Join("office_users", "transportation_offices.id = office_users.transportation_office_id"). + Where("office_users.id = ?", officeUserID).First(&transportationOffice) + + if err != nil { + return []models.Move{}, err + } + + privileges, err := models.FetchPrivilegesForUser(appCtx.DB(), appCtx.Session().UserID) + if err != nil { + appCtx.Logger().Error("Error retreiving user privileges", zap.Error(err)) + } + + officeUserGbloc := transportationOffice.Gbloc + + // Alright let's build our query based on the filters we got from the handler. These use the FilterOption type above. + // Essentially these are private functions that return query objects that we can mash together to form a complete + // query from modular parts. + + // The services counselor queue does not base exclude marine results. + // Only the TIO and TOO queues should. + needsCounseling := false + if len(params.Status) > 0 { + for _, status := range params.Status { + if status == string(models.MoveStatusNeedsServiceCounseling) { + needsCounseling = true + } + } + } + + ppmCloseoutGblocs := officeUserGbloc == "NAVY" || officeUserGbloc == "TVCB" || officeUserGbloc == "USCG" + + // Services Counselors in closeout GBLOCs should only see closeout moves + if needsCounseling && ppmCloseoutGblocs && params.NeedsPPMCloseout != nil && !*params.NeedsPPMCloseout { + return []models.Move{}, nil + } + + branchQuery := branchFilter(params.Branch, needsCounseling, ppmCloseoutGblocs) + + // If the user is associated with the USMC GBLOC we want to show them ALL the USMC moves, so let's override here. + // We also only want to do the gbloc filtering thing if we aren't a USMC user, which we cover with the else. + // var gblocQuery QueryOption + var gblocToFilterBy *string + if officeUserGbloc == "USMC" && !needsCounseling { + branchQuery = branchFilter(models.StringPointer(string(models.AffiliationMARINES)), needsCounseling, ppmCloseoutGblocs) + gblocToFilterBy = &officeUserGbloc + } + + // We need to use three different GBLOC filter queries because: + // - The Services Counselor queue filters based on the GBLOC of the origin duty location's + // transportation office + // - There is a separate queue for the GBLOCs: NAVY, TVCB and USCG. These GBLOCs are used by SC doing PPM Closeout + // - The TOO queue uses the GBLOC we get from examining the postal code of the first shipment's + // pickup address. However, if that shipment happens to be an NTS-Release, we instead drop + // back to the GBLOC of the origin duty location's transportation office since an NTS-Release + // does not populate the pickup address field. + var gblocQuery QueryOption + if ppmCloseoutGblocs { + gblocQuery = gblocFilterForPPMCloseoutForNavyMarineAndCG(gblocToFilterBy) + } else if needsCounseling { + gblocQuery = gblocFilterForSC(gblocToFilterBy) + } else if params.NeedsPPMCloseout != nil && *params.NeedsPPMCloseout { + gblocQuery = gblocFilterForSCinArmyAirForce(gblocToFilterBy) + } else { + gblocQuery = gblocFilterForTOO(gblocToFilterBy) + } + moveStatusQuery := moveStatusFilter(params.Status) + // Adding to an array so we can iterate over them and apply the filters after the query structure is set below + options := [15]QueryOption{branchQuery, moveStatusQuery, gblocQuery} + + var query *pop.Query + if ppmCloseoutGblocs { + query = appCtx.DB().Q().Scope(utilities.ExcludeDeletedScope(models.MTOShipment{})).EagerPreload( + "Orders.ServiceMember", + "Orders.NewDutyLocation.Address", + "Orders.OriginDutyLocation.Address", + "Orders.Entitlement", + "Orders.OrdersType", + "MTOShipments.PPMShipment", + "LockedByOfficeUser", + ).InnerJoin("orders", "orders.id = moves.orders_id"). + InnerJoin("service_members", "orders.service_member_id = service_members.id"). + InnerJoin("mto_shipments", "moves.id = mto_shipments.move_id"). + InnerJoin("ppm_shipments", "ppm_shipments.shipment_id = mto_shipments.id"). + InnerJoin("duty_locations as origin_dl", "orders.origin_duty_location_id = origin_dl.id"). + LeftJoin("duty_locations as dest_dl", "dest_dl.id = orders.new_duty_location_id"). + LeftJoin("office_users", "office_users.id = moves.locked_by"). + Where("show = ?", models.BoolPointer(true)) + + if !privileges.HasPrivilege(models.PrivilegeTypeSafety) { + query.Where("orders.orders_type != (?)", models.PrivilegeSearchTypeSafety) + } + } else { + query = appCtx.DB().Q().Scope(utilities.ExcludeDeletedScope(models.MTOShipment{})).EagerPreload( + "Orders.ServiceMember", + "Orders.NewDutyLocation.Address", + "Orders.OriginDutyLocation.Address", + "Orders.Entitlement", + "Orders.OrdersType", + "MTOShipments", + "MTOServiceItems", + "ShipmentGBLOC", + "MTOShipments.PPMShipment", + "CloseoutOffice", + "LockedByOfficeUser", + ).InnerJoin("orders", "orders.id = moves.orders_id"). + InnerJoin("service_members", "orders.service_member_id = service_members.id"). + InnerJoin("mto_shipments", "moves.id = mto_shipments.move_id"). + InnerJoin("duty_locations as origin_dl", "orders.origin_duty_location_id = origin_dl.id"). + // Need to use left join because some duty locations do not have transportation offices + LeftJoin("transportation_offices as origin_to", "origin_dl.transportation_office_id = origin_to.id"). + // If a customer puts in an invalid ZIP for their pickup address, it won't show up in this view, + // and we don't want it to get hidden from services counselors. + LeftJoin("move_to_gbloc", "move_to_gbloc.move_id = moves.id"). + LeftJoin("duty_locations as dest_dl", "dest_dl.id = orders.new_duty_location_id"). + LeftJoin("office_users", "office_users.id = moves.locked_by"). + Where("show = ?", models.BoolPointer(true)) + + if !privileges.HasPrivilege(models.PrivilegeTypeSafety) { + query.Where("orders.orders_type != (?)", models.PrivilegeSearchTypeSafety) + } + + if params.NeedsPPMCloseout != nil { + if *params.NeedsPPMCloseout { + query.InnerJoin("ppm_shipments", "ppm_shipments.shipment_id = mto_shipments.id"). + Where("ppm_shipments.status IN (?)", models.PPMShipmentStatusWaitingOnCustomer, models.PPMShipmentStatusNeedsCloseout, models.PPMShipmentStatusCloseoutComplete). + Where("service_members.affiliation NOT IN (?)", models.AffiliationNAVY, models.AffiliationMARINES, models.AffiliationCOASTGUARD) + } else { + query.LeftJoin("ppm_shipments", "ppm_shipments.shipment_id = mto_shipments.id"). + Where("(ppm_shipments.status IS NULL OR ppm_shipments.status NOT IN (?))", models.PPMShipmentStatusWaitingOnCustomer, models.PPMShipmentStatusNeedsCloseout, models.PPMShipmentStatusCloseoutComplete) + } + } else { + if appCtx.Session().Roles.HasRole(roles.RoleTypeTOO) { + query.Where("(moves.ppm_type IS NULL OR (moves.ppm_type = (?) or (moves.ppm_type = (?) and origin_dl.provides_services_counseling = 'false')))", models.MovePPMTypePARTIAL, models.MovePPMTypeFULL) + } + query.LeftJoin("ppm_shipments", "ppm_shipments.shipment_id = mto_shipments.id") + } + } + + for _, option := range options { + if option != nil { + option(query) // mutates + } + } + + var groupByColumms []string + groupByColumms = append(groupByColumms, "service_members.id", "orders.id", "origin_dl.id") + + err = query.GroupBy("moves.id", groupByColumms...).All(&moves) + if err != nil { + return []models.Move{}, err + } + + return moves, nil +} + // NewOrderFetcher creates a new struct with the service dependencies func NewOrderFetcher() services.OrderFetcher { return &orderFetcher{} @@ -354,11 +514,10 @@ func locatorFilter(locator *string) QueryOption { } } -func originDutyLocationFilter(originDutyLocation *string) QueryOption { +func originDutyLocationFilter(originDutyLocation []string) QueryOption { return func(query *pop.Query) { - if originDutyLocation != nil { - nameSearch := fmt.Sprintf("%s%%", *originDutyLocation) - query.Where("origin_dl.name ILIKE ?", nameSearch) + if len(originDutyLocation) > 0 { + query.Where("origin_dl.name IN (?)", originDutyLocation) } } } diff --git a/pkg/services/order/order_fetcher_test.go b/pkg/services/order/order_fetcher_test.go index e44ce4730bf..533e7bc2040 100644 --- a/pkg/services/order/order_fetcher_test.go +++ b/pkg/services/order/order_fetcher_test.go @@ -1852,3 +1852,23 @@ func (suite *OrderServiceSuite) TestListOrdersForTOOWithPPMWithOneDeletedShipmen suite.Equal(1, moveCount) suite.Len(moves, 1) } + +func (suite *OrderServiceSuite) TestListAllOrderLocations() { + suite.Run("returns a list of all order locations in the current users queue", func() { + orderFetcher := NewOrderFetcher() + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeServicesCounselor}) + session := auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: officeUser.User.Roles, + OfficeUserID: officeUser.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } + + params := services.ListOrderParams{} + moves, err := orderFetcher.ListAllOrderLocations(suite.AppContextWithSessionForTest(&session), officeUser.ID, ¶ms) + + suite.FatalNoError(err) + suite.Equal(0, len(moves)) + }) +} diff --git a/src/components/Table/Filters/MultiSelectTypeAheadCheckBoxFilter.module.scss b/src/components/Table/Filters/MultiSelectTypeAheadCheckBoxFilter.module.scss new file mode 100644 index 00000000000..3d744d6f0f3 --- /dev/null +++ b/src/components/Table/Filters/MultiSelectTypeAheadCheckBoxFilter.module.scss @@ -0,0 +1,90 @@ +@import 'shared/styles/_basics'; +@import 'shared/styles/_mixins'; +@import 'shared/styles/colors'; + +:global { + :local(.MultiSelectTypeAheadCheckBoxFilterWrapper) { + @include u-radius(0); + @include u-padding-top(1); + @include u-font-weight(normal); + @include u-font-size('body', 4); + + label { + @include u-margin-y(0.5); + } + + .MultiSelectTypeAheadCheckBoxFilter__placeholder { + color: $base-darkest; + position: absolute; + top: 50%; + transform: translateY(-50%); + box-sizing: border-box; + } + + .MultiSelectTypeAheadCheckBoxFilter__control { + height: 2.5rem; + @include u-radius(0); + @include u-minw(15); + max-width: 200px; + border-color: $base-darkest; + } + + .MultiSelectTypeAheadCheckBoxFilter__control:hover { + border-color: $base-darkest; + } + + .MultiSelectTypeAheadCheckBoxFilter__control--is-focused { + box-shadow: none; + border-color: $base-darkest; + outline: 0.25rem solid #2491ff !important; + outline-offset: 0; + } + + .MultiSelectTypeAheadCheckBoxFilter__menu { + @include u-margin-top(0.5); + @include u-radius(0); + } + + .MultiSelectTypeAheadCheckBoxFilter__value-container div { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .MultiSelectTypeAheadCheckBoxFilter__value-container div span + span:before { + content: ', '; + } + + .MultiSelectTypeAheadCheckBoxFilter__indicator-separator { + display: none; + } + + .MultiSelectTypeAheadCheckBoxFilter__option { + border: 2px solid $base-darkest; + border-left: 0; + border-right: 0; + background-color: white; + color: $base-darkest; + + .usa-checkbox label { + text-indent: unset; + } + + } + + .MultiSelectTypeAheadCheckBoxFilter__option:not(:first-child), + :global .MultiSelectCheckBoxFilter__option:first-child { + border-top: 0; + } + + .MultiSelectTypeAheadCheckBoxFilter__option:last-child { + border-bottom: 0; + } + + .MultiSelectTypeAheadCheckBoxFilter__indicator svg { + color: $base-darker; + height: 0.9rem; + width: 0.9rem; + } + } +} diff --git a/src/components/Table/Filters/MultiSelectTypeAheadCheckboxFilter.test.jsx b/src/components/Table/Filters/MultiSelectTypeAheadCheckboxFilter.test.jsx new file mode 100644 index 00000000000..66ed62ee004 --- /dev/null +++ b/src/components/Table/Filters/MultiSelectTypeAheadCheckboxFilter.test.jsx @@ -0,0 +1,64 @@ +import React from 'react'; +import Select from 'react-select'; +import { mount } from 'enzyme'; + +import MutliSelectTypeAheadCheckboxFilter from './MutliSelectTypeAheadCheckboxFilter'; + +const column = { + filterValue: '', + setFilter: jest.fn(), +}; +const optionsConstants = [ + { value: 'ARMY', label: 'Army' }, + { value: 'NAVY', label: 'Navy' }, +]; +const optionsStrings = [ + { value: 'approval requested', label: 'Approval Requested' }, + { value: 'paid', label: 'Paid' }, +]; + +describe('MutliSelectTypeAheadCheckboxFilter', () => { + it('renders without crashing', () => { + const wrapper = mount( + , + ); + expect(wrapper.find('[data-testid="MultiSelectTypeAheadCheckBoxFilter"]').length).toBe(1); + }); + + describe('It renders the expected placeholder text', () => { + it('from an array of constant valued objects when a value is chosen', () => { + const wrapper = mount(); + const input = wrapper.find(Select).find('input'); + input.simulate('keyDown', { key: 'ArrowDown', keyCode: 40 }); + input.simulate('keyDown', { key: 'Enter', keyCode: 13 }); + + expect(wrapper.find('[data-testid="multi-value-container"]').text()).toEqual('Army'); + }); + + it('from an array of string valued objects when a value is chosen', () => { + const wrapper = mount(); + const input = wrapper.find(Select).find('input'); + input.simulate('keyDown', { key: 'ArrowDown', keyCode: 40 }); + input.simulate('keyDown', { key: 'Enter', keyCode: 13 }); + + expect(wrapper.find('[data-testid="multi-value-container"]').text()).toEqual('Approval Requested'); + }); + + it('from an array of objects when multiple values are chosen', () => { + const wrapper = mount(); + const input = wrapper.find(Select).find('input'); + input.simulate('keyDown', { key: 'ArrowDown', keyCode: 40 }); + input.simulate('keyDown', { key: 'Enter', keyCode: 13 }); + input.simulate('keyDown', { key: 'ArrowDown', keyCode: 40 }); + input.simulate('keyDown', { key: 'ArrowDown', keyCode: 40 }); + input.simulate('keyDown', { key: 'Enter', keyCode: 13 }); + + expect(wrapper.find('[data-testid="multi-value-container"]').at(0).text()).toEqual('Approval Requested'); + expect(wrapper.find('[data-testid="multi-value-container"]').at(1).text()).toEqual('Paid'); + }); + }); +}); diff --git a/src/components/Table/Filters/MutliSelectTypeAheadCheckboxFilter.jsx b/src/components/Table/Filters/MutliSelectTypeAheadCheckboxFilter.jsx new file mode 100644 index 00000000000..f4e2c99adc7 --- /dev/null +++ b/src/components/Table/Filters/MutliSelectTypeAheadCheckboxFilter.jsx @@ -0,0 +1,106 @@ +/* eslint-disable react/jsx-props-no-spreading */ +import React from 'react'; +import { bool, string, shape, node, func, arrayOf } from 'prop-types'; +import AsyncSelect, { components } from 'react-select'; +import { Checkbox } from '@trussworks/react-uswds'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; + +import styles from './MultiSelectTypeAheadCheckBoxFilter.module.scss'; + +const Option = (props) => { + const { + isSelected, + label, + innerProps: { id }, + } = props; + return ( + + {}} /> + + ); +}; + +Option.propTypes = { + isSelected: bool.isRequired, + label: string.isRequired, + innerProps: shape({ + id: string.isRequired, + }).isRequired, +}; + +const DropdownIndicator = (props) => { + return ( + + + + ); +}; + +const ValueContainer = ({ children, ...props }) => { + return ( + +
{children}
+
+ ); +}; + +ValueContainer.propTypes = { + children: node.isRequired, +}; + +const MultiValueContainer = ({ data: { label } }) => { + return {label}; +}; + +MultiValueContainer.propTypes = { + data: shape({ + label: string.isRequired, + }).isRequired, +}; + +const MultiSelectTypeAheadCheckBoxFilter = ({ options, placeholder, column: { filterValue, setFilter } }) => { + const onChange = (value) => { + let paramFilterValue = []; + if (value) { + value.forEach((val) => { + paramFilterValue.push(`${val.value}`); + }); + } else { + paramFilterValue = undefined; + } + setFilter(paramFilterValue || undefined); + }; + + return ( +
+ +
+ ); +}; + +// Values come from react-table +MultiSelectTypeAheadCheckBoxFilter.propTypes = { + options: arrayOf( + shape({ + label: string.isRequired, + value: string.isRequired, + }).isRequired, + ).isRequired, + column: shape({ + filterValue: node, + setFilter: func, + }).isRequired, +}; + +export default MultiSelectTypeAheadCheckBoxFilter; diff --git a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx index fdb115ddb8b..271477122bc 100644 --- a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx +++ b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx @@ -18,6 +18,7 @@ import { SERVICE_COUNSELING_PPM_STATUS_LABELS, } from 'constants/queues'; import { generalRoutes, servicesCounselingRoutes } from 'constants/routes'; +import { elevatedPrivilegeTypes } from 'constants/userPrivileges'; import { useServicesCounselingQueueQueries, useServicesCounselingQueuePPMQueries, @@ -25,6 +26,7 @@ import { useMoveSearchQueries, useCustomerSearchQueries, } from 'hooks/queries'; +import { getServicesCounselingOriginLocations } from 'services/ghcApi'; import { DATE_FORMAT_STRING, MOVE_STATUSES } from 'shared/constants'; import { formatDateFromIso, serviceMemberAgencyLabel } from 'utils/formatters'; import LoadingPlaceholder from 'shared/LoadingPlaceholder'; @@ -41,8 +43,9 @@ import { CHECK_SPECIAL_ORDERS_TYPES, SPECIAL_ORDERS_TYPES } from 'constants/orde import ConnectedFlashMessage from 'containers/FlashMessage/FlashMessage'; import { isNullUndefinedOrWhitespace } from 'shared/utils'; import CustomerSearchForm from 'components/CustomerSearchForm/CustomerSearchForm'; +import MultiSelectTypeAheadCheckBoxFilter from 'components/Table/Filters/MutliSelectTypeAheadCheckboxFilter'; -const counselingColumns = (moveLockFlag) => [ +const counselingColumns = (moveLockFlag, originLocationList, supervisor) => [ createHeader(' ', (row) => { const now = new Date(); // this will render a lock icon if the move is locked & if the lockExpiresAt value is after right now @@ -134,12 +137,31 @@ const counselingColumns = (moveLockFlag) => [ createHeader('Origin GBLOC', 'originGBLOC', { disableSortBy: true, }), // If the user is in the USMC GBLOC they will have many different GBLOCs and will want to sort and filter - createHeader('Origin duty location', 'originDutyLocation.name', { - id: 'originDutyLocation', - isFilterable: true, - }), + supervisor + ? createHeader( + 'Origin duty location', + (row) => { + return `${row.originDutyLocation.name}`; + }, + { + id: 'originDutyLocation', + isFilterable: true, + Filter: (props) => ( + + ), + }, + ) + : createHeader('Origin duty location', 'originDutyLocation.name', { + id: 'originDutyLocation', + isFilterable: true, + }), ]; -const closeoutColumns = (moveLockFlag, ppmCloseoutGBLOC) => [ +const closeoutColumns = (moveLockFlag, ppmCloseoutGBLOC, ppmCloseoutOriginLocationList, supervisor) => [ createHeader(' ', (row) => { const now = new Date(); // this will render a lock icon if the move is locked & if the lockExpiresAt value is after right now @@ -232,10 +254,29 @@ const closeoutColumns = (moveLockFlag, ppmCloseoutGBLOC) => [ ), }, ), - createHeader('Origin duty location', 'originDutyLocation.name', { - id: 'originDutyLocation', - isFilterable: true, - }), + supervisor + ? createHeader( + 'Origin duty location', + (row) => { + return `${row.originDutyLocation.name}`; + }, + { + id: 'originDutyLocation', + isFilterable: true, + Filter: (props) => ( + + ), + }, + ) + : createHeader('Origin duty location', 'originDutyLocation.name', { + id: 'originDutyLocation', + isFilterable: true, + }), createHeader('Destination duty location', 'destinationDutyLocation.name', { id: 'destinationDutyLocation', isFilterable: true, @@ -248,7 +289,7 @@ const closeoutColumns = (moveLockFlag, ppmCloseoutGBLOC) => [ }), ]; -const ServicesCounselingQueue = () => { +const ServicesCounselingQueue = ({ userPrivileges }) => { const { queueType } = useParams(); const { data, isLoading, isError } = useUserQueries(); @@ -257,9 +298,29 @@ const ServicesCounselingQueue = () => { const [isCounselorMoveCreateFFEnabled, setisCounselorMoveCreateFFEnabled] = useState(false); const [moveLockFlag, setMoveLockFlag] = useState(false); const [setErrorState] = useState({ hasError: false, error: undefined, info: undefined }); + const [originLocationList, setOriginLocationList] = useState([]); + const [ppmCloseoutOriginLocationList, setPpmCloseoutOriginLocationList] = useState([]); + const supervisor = userPrivileges + ? userPrivileges.some((p) => p.privilegeType === elevatedPrivilegeTypes.SUPERVISOR) + : false; // Feature Flag useEffect(() => { + const getOriginLocationList = (needsPPMCloseout) => { + if (supervisor) { + getServicesCounselingOriginLocations(needsPPMCloseout).then((response) => { + if (needsPPMCloseout) { + setPpmCloseoutOriginLocationList(response); + } else { + setOriginLocationList(response); + } + }); + } + }; + + getOriginLocationList(true); + getOriginLocationList(false); + const fetchData = async () => { try { const isEnabled = await isCounselorMoveCreateEnabled(); @@ -278,7 +339,7 @@ const ServicesCounselingQueue = () => { } }; fetchData(); - }, [setErrorState]); + }, [setErrorState, supervisor]); const handleEditProfileClick = (locator) => { navigate(generatePath(servicesCounselingRoutes.BASE_CUSTOMER_INFO_EDIT_PATH, { moveCode: locator })); @@ -438,7 +499,7 @@ const ServicesCounselingQueue = () => { defaultSortedColumns={[{ id: 'closeoutInitiated', desc: false }]} disableMultiSort disableSortBy={false} - columns={closeoutColumns(moveLockFlag, inPPMCloseoutGBLOC)} + columns={closeoutColumns(moveLockFlag, inPPMCloseoutGBLOC, ppmCloseoutOriginLocationList, supervisor)} title="Moves" handleClick={handleClick} useQueries={useServicesCounselingQueuePPMQueries} @@ -459,7 +520,7 @@ const ServicesCounselingQueue = () => { defaultSortedColumns={[{ id: 'submittedAt', desc: false }]} disableMultiSort disableSortBy={false} - columns={counselingColumns(moveLockFlag)} + columns={counselingColumns(moveLockFlag, originLocationList, supervisor)} title="Moves" handleClick={handleClick} useQueries={useServicesCounselingQueueQueries} diff --git a/src/pages/Office/index.jsx b/src/pages/Office/index.jsx index a5ad28edb83..1563bc166cd 100644 --- a/src/pages/Office/index.jsx +++ b/src/pages/Office/index.jsx @@ -273,7 +273,7 @@ export class OfficeApp extends Component { end element={ - + } /> diff --git a/src/services/ghcApi.js b/src/services/ghcApi.js index b4a5a7d748e..f9efa40ac20 100644 --- a/src/services/ghcApi.js +++ b/src/services/ghcApi.js @@ -600,6 +600,19 @@ export async function getServicesCounselingQueue( ); } +export async function getServicesCounselingOriginLocations(needsPPMCloseout) { + const operationPath = 'queues.getServicesCounselingOriginList'; + + return makeGHCRequest( + operationPath, + { + needsPPMCloseout, + }, + + { schemaKey: 'Locations', normalize: false }, + ); +} + export async function getServicesCounselingPPMQueue( key, { sort, order, filters = [], currentPage = 1, currentPageSize = 20, needsPPMCloseout = true }, diff --git a/swagger-def/ghc.yaml b/swagger-def/ghc.yaml index 4667b772c1d..d1a4b15418d 100644 --- a/swagger-def/ghc.yaml +++ b/swagger-def/ghc.yaml @@ -3137,7 +3137,11 @@ paths: description: filters the GBLOC of the service member's origin duty location - in: query name: originDutyLocation - type: string + type: array + uniqueItems: true + collectionFormat: multi + items: + type: string description: filters the name of the origin duty location on the orders - in: query name: destinationDutyLocation @@ -3193,6 +3197,30 @@ paths: $ref: '#/responses/PermissionDenied' '500': $ref: '#/responses/ServerError' + /queues/counseling/origin-list: + get: + produces: + - application/json + summary: Gets queued list of all moves origin locations in the counselors queue + 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. This pulls the availalble origin duty locations. + operationId: getServicesCounselingOriginList + tags: + - queues + parameters: + - in: query + name: needsPPMCloseout + type: boolean + description: Only used for Services Counseling queue. If true, show PPM moves origin locations that are ready for closeout. Otherwise, show all other moves origin locations. + responses: + '200': + description: Successfully returned all moves matching the criteria + schema: + $ref: '#/definitions/Locations' + '403': + $ref: '#/responses/PermissionDenied' + '500': + $ref: '#/responses/ServerError' /queues/prime-moves: get: summary: getPrimeMovesQueue @@ -3296,7 +3324,11 @@ paths: type: string - in: query name: originDutyLocation - type: string + type: array + uniqueItems: true + collectionFormat: multi + items: + type: string - in: query name: destinationDutyLocation type: string @@ -4827,6 +4859,22 @@ definitions: eTag: type: string type: object + Location: + type: object + properties: + label: + type: string + example: Label for display + value: + type: string + example: Value for location + required: + - label + - value + Locations: + type: array + items: + $ref: '#/definitions/Location' OrderBody: type: object properties: diff --git a/swagger/ghc.yaml b/swagger/ghc.yaml index 12c147239d9..3d2f7b0d89c 100644 --- a/swagger/ghc.yaml +++ b/swagger/ghc.yaml @@ -3256,7 +3256,11 @@ paths: description: filters the GBLOC of the service member's origin duty location - in: query name: originDutyLocation - type: string + type: array + uniqueItems: true + collectionFormat: multi + items: + type: string description: filters the name of the origin duty location on the orders - in: query name: destinationDutyLocation @@ -3314,6 +3318,36 @@ paths: $ref: '#/responses/PermissionDenied' '500': $ref: '#/responses/ServerError' + /queues/counseling/origin-list: + get: + produces: + - application/json + summary: Gets queued list of all moves origin locations in the counselors queue + 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. This pulls the availalble origin duty + locations. + operationId: getServicesCounselingOriginList + tags: + - queues + parameters: + - in: query + name: needsPPMCloseout + type: boolean + description: >- + Only used for Services Counseling queue. If true, show PPM moves + origin locations that are ready for closeout. Otherwise, show all + other moves origin locations. + responses: + '200': + description: Successfully returned all moves matching the criteria + schema: + $ref: '#/definitions/Locations' + '403': + $ref: '#/responses/PermissionDenied' + '500': + $ref: '#/responses/ServerError' /queues/prime-moves: get: summary: getPrimeMovesQueue @@ -3434,7 +3468,11 @@ paths: type: string - in: query name: originDutyLocation - type: string + type: array + uniqueItems: true + collectionFormat: multi + items: + type: string - in: query name: destinationDutyLocation type: string @@ -5008,6 +5046,22 @@ definitions: eTag: type: string type: object + Location: + type: object + properties: + label: + type: string + example: Label for display + value: + type: string + example: Value for location + required: + - label + - value + Locations: + type: array + items: + $ref: '#/definitions/Location' OrderBody: type: object properties: