diff --git a/pkg/gen/ghcapi/embedded_spec.go b/pkg/gen/ghcapi/embedded_spec.go index 3b2a8686a83..e0e77eb2f53 100644 --- a/pkg/gen/ghcapi/embedded_spec.go +++ b/pkg/gen/ghcapi/embedded_spec.go @@ -6843,7 +6843,8 @@ func init() { "example": "c56a4180-65aa-42ec-a945-5fd21dec0538" }, "workload": { - "type": "integer" + "type": "integer", + "x-omitempty": false } } }, @@ -23958,7 +23959,8 @@ func init() { "example": "c56a4180-65aa-42ec-a945-5fd21dec0538" }, "workload": { - "type": "integer" + "type": "integer", + "x-omitempty": false } } }, diff --git a/pkg/gen/ghcmessages/available_office_user.go b/pkg/gen/ghcmessages/available_office_user.go index bb9b84b48dc..80e15c0e96b 100644 --- a/pkg/gen/ghcmessages/available_office_user.go +++ b/pkg/gen/ghcmessages/available_office_user.go @@ -34,7 +34,7 @@ type AvailableOfficeUser struct { OfficeUserID strfmt.UUID `json:"officeUserId,omitempty"` // workload - Workload int64 `json:"workload,omitempty"` + Workload int64 `json:"workload"` } // Validate validates this available office user diff --git a/pkg/handlers/ghcapi/queues.go b/pkg/handlers/ghcapi/queues.go index ed4980df650..7763d9634de 100644 --- a/pkg/handlers/ghcapi/queues.go +++ b/pkg/handlers/ghcapi/queues.go @@ -611,6 +611,27 @@ func (h GetBulkAssignmentDataHandler) Handle( return queues.NewGetBulkAssignmentDataInternalServerError(), err } + officeUserData = payloads.BulkAssignmentData(appCtx, moves, officeUsers, officeUser.TransportationOffice.ID) + case string(models.QueueTypeCloseout): + // 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 + moves, err := h.MoveFetcherBulkAssignment.FetchMovesForBulkAssignmentCloseout( + appCtx, officeUser.TransportationOffice.Gbloc, officeUser.TransportationOffice.ID, + ) + if err != nil { + appCtx.Logger().Error("Error retreiving moves", zap.Error(err)) + return queues.NewGetBulkAssignmentDataInternalServerError(), err + } + officeUserData = payloads.BulkAssignmentData(appCtx, moves, officeUsers, officeUser.TransportationOffice.ID) case string(models.QueueTypeTaskOrder): // fetch the TOOs who work at their office diff --git a/pkg/handlers/ghcapi/queues_test.go b/pkg/handlers/ghcapi/queues_test.go index aa5b78144cc..4711af0de0a 100644 --- a/pkg/handlers/ghcapi/queues_test.go +++ b/pkg/handlers/ghcapi/queues_test.go @@ -1819,4 +1819,77 @@ func (suite *HandlerSuite) TestGetBulkAssignmentDataHandler() { suite.Len(payload.AvailableOfficeUsers, 1) suite.Len(payload.BulkAssignmentMoveIDs, 1) }) + suite.Run("returns properly formatted closeout bulk assignment data", 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) + + submittedAt := time.Now() + + // move to appear in the return + factory.BuildMoveWithPPMShipment(suite.DB(), []factory.Customization{ + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CloseoutOffice, + }, + { + Model: models.PPMShipment{ + Status: models.PPMShipmentStatusNeedsCloseout, + SubmittedAt: &submittedAt, + }, + }, + { + Model: models.Move{ + Status: models.MoveStatusAPPROVED, + }, + }, + }, nil) + + request := httptest.NewRequest("GET", "/queues/bulk-assignment", nil) + request = suite.AuthenticateOfficeRequest(request, officeUser) + params := queues.GetBulkAssignmentDataParams{ + HTTPRequest: request, + QueueType: models.StringPointer("CLOSEOUT"), + } + handlerConfig := suite.HandlerConfig() + handler := GetBulkAssignmentDataHandler{ + handlerConfig, + officeusercreator.NewOfficeUserFetcherPop(), + movefetcher.NewMoveFetcherBulkAssignment(), + } + response := handler.Handle(params) + suite.IsNotErrResponse(response) + suite.IsType(&queues.GetBulkAssignmentDataOK{}, response) + payload := response.(*queues.GetBulkAssignmentDataOK).Payload + suite.NoError(payload.Validate(strfmt.Default)) + suite.Len(payload.AvailableOfficeUsers, 1) + suite.Len(payload.BulkAssignmentMoveIDs, 1) + }) } diff --git a/pkg/services/move.go b/pkg/services/move.go index 8ddda83a8ec..0262e8da8ae 100644 --- a/pkg/services/move.go +++ b/pkg/services/move.go @@ -31,6 +31,7 @@ type MoveFetcher interface { type MoveFetcherBulkAssignment interface { FetchMovesForBulkAssignmentCounseling(appCtx appcontext.AppContext, gbloc string, officeId uuid.UUID) ([]models.MoveWithEarliestDate, error) + FetchMovesForBulkAssignmentCloseout(appCtx appcontext.AppContext, gbloc string, officeId uuid.UUID) ([]models.MoveWithEarliestDate, error) FetchMovesForBulkAssignmentTaskOrder(appCtx appcontext.AppContext, gbloc string, officeId uuid.UUID) ([]models.MoveWithEarliestDate, error) } diff --git a/pkg/services/move/move_fetcher.go b/pkg/services/move/move_fetcher.go index 62d562a6973..cdcc578cec0 100644 --- a/pkg/services/move/move_fetcher.go +++ b/pkg/services/move/move_fetcher.go @@ -166,6 +166,60 @@ 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, + ppm_shipments.submitted_at 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.submitted_at + 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 diff --git a/pkg/services/move/move_fetcher_test.go b/pkg/services/move/move_fetcher_test.go index 92fb23751bb..ea6f7813fab 100644 --- a/pkg/services/move/move_fetcher_test.go +++ b/pkg/services/move/move_fetcher_test.go @@ -434,6 +434,90 @@ func (suite *MoveServiceSuite) TestMoveFetcherBulkAssignment() { suite.Equal(assignedMove.Orders.OrdersType, internalmessages.OrdersTypePERMANENTCHANGEOFSTATION) }) + suite.Run("Closeout returns non Navy/USCG/USMC ppms in needs closeout status", func() { + moveFetcher := NewMoveFetcherBulkAssignment() + transportationOffice := factory.BuildTransportationOffice(suite.DB(), nil, nil) + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CloseoutOffice, + }, + }, []roles.RoleType{roles.RoleTypeServicesCounselor}) + + submittedAt := time.Now() + + // create non USMC/USCG/NAVY ppm in need closeout status + factory.BuildMoveWithPPMShipment(suite.DB(), []factory.Customization{ + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CloseoutOffice, + }, + { + Model: models.PPMShipment{ + Status: models.PPMShipmentStatusNeedsCloseout, + SubmittedAt: &submittedAt, + }, + }, + { + Model: models.Move{ + Status: models.MoveStatusAPPROVED, + }, + }, + }, nil) + + // create non closeout needed ppm + factory.BuildMoveWithPPMShipment(suite.DB(), []factory.Customization{ + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CloseoutOffice, + }, + { + Model: models.PPMShipment{ + Status: models.PPMShipmentStatusWaitingOnCustomer, + SubmittedAt: &submittedAt, + }, + }, + { + Model: models.Move{ + Status: models.MoveStatusAPPROVED, + }, + }, + }, nil) + + marine := models.AffiliationMARINES + marinePPM := factory.BuildPPMShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusAPPROVED, + }, + }, + { + Model: models.MTOShipment{ + ShipmentType: models.MTOShipmentTypePPM, + }, + }, + { + Model: models.PPMShipment{ + Status: models.PPMShipmentStatusNeedsCloseout, + SubmittedAt: &submittedAt, + }, + }, + { + Model: models.ServiceMember{ + Affiliation: &marine, + }, + }, + }, nil) + + moves, err := moveFetcher.FetchMovesForBulkAssignmentCloseout(suite.AppContextForTest(), "KKFA", officeUser.TransportationOffice.ID) + suite.FatalNoError(err) + suite.Equal(1, len(moves)) + suite.NotEqual(marinePPM.ID, moves[0].ID) + }) + suite.Run("TOO: Returns moves that fulfill the query criteria", func() { moveFetcher := NewMoveFetcherBulkAssignment() transportationOffice := factory.BuildTransportationOffice(suite.DB(), nil, nil) diff --git a/swagger-def/ghc.yaml b/swagger-def/ghc.yaml index fefd12c57f9..2c6bd1d2dee 100644 --- a/swagger-def/ghc.yaml +++ b/swagger-def/ghc.yaml @@ -7126,6 +7126,7 @@ definitions: type: boolean workload: type: integer + x-omitempty: false BulkAssignmentData: type: object properties: diff --git a/swagger/ghc.yaml b/swagger/ghc.yaml index f3d46937381..14b49bd9fc6 100644 --- a/swagger/ghc.yaml +++ b/swagger/ghc.yaml @@ -7479,6 +7479,7 @@ definitions: type: boolean workload: type: integer + x-omitempty: false BulkAssignmentData: type: object properties: