diff --git a/pkg/handlers/ghcapi/queues.go b/pkg/handlers/ghcapi/queues.go index 7763d9634de..0735cfc8b4f 100644 --- a/pkg/handlers/ghcapi/queues.go +++ b/pkg/handlers/ghcapi/queues.go @@ -653,6 +653,27 @@ func (h GetBulkAssignmentDataHandler) Handle( return queues.NewGetBulkAssignmentDataInternalServerError(), err } + officeUserData = payloads.BulkAssignmentData(appCtx, moves, officeUsers, officeUser.TransportationOffice.ID) + case string(models.QueueTypePaymentRequest): + // fetch the TIOs who work at their office + officeUsers, err := h.OfficeUserFetcherPop.FetchOfficeUsersWithWorkloadByRoleAndOffice( + appCtx, + roles.RoleTypeTIO, + 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.FetchMovesForBulkAssignmentPaymentRequest( + 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) } return queues.NewGetBulkAssignmentDataOK().WithPayload(&officeUserData), nil diff --git a/pkg/handlers/ghcapi/queues_test.go b/pkg/handlers/ghcapi/queues_test.go index 4711af0de0a..5c4008aa5d0 100644 --- a/pkg/handlers/ghcapi/queues_test.go +++ b/pkg/handlers/ghcapi/queues_test.go @@ -1655,7 +1655,7 @@ func (suite *HandlerSuite) TestGetServicesCounselingQueueHandler() { } func (suite *HandlerSuite) TestGetBulkAssignmentDataHandler() { - suite.Run("returns an unauthorized error when an attempt is made by a non supervisor", func() { + suite.Run("SC - returns an unauthorized error when an attempt is made by a non supervisor", func() { officeUser := factory.BuildOfficeUserWithPrivileges(suite.DB(), []factory.Customization{ { Model: models.OfficeUser{ @@ -1689,7 +1689,7 @@ func (suite *HandlerSuite) TestGetBulkAssignmentDataHandler() { suite.IsNotErrResponse(response) suite.IsType(&queues.GetBulkAssignmentDataUnauthorized{}, response) }) - suite.Run("returns properly formatted bulk assignment data", func() { + suite.Run("SC - returns properly formatted bulk assignment data", func() { transportationOffice := factory.BuildTransportationOffice(suite.DB(), nil, nil) officeUser := factory.BuildOfficeUserWithPrivileges(suite.DB(), []factory.Customization{ @@ -1892,4 +1892,118 @@ func (suite *HandlerSuite) TestGetBulkAssignmentDataHandler() { suite.Len(payload.AvailableOfficeUsers, 1) suite.Len(payload.BulkAssignmentMoveIDs, 1) }) + + suite.Run("TIO - 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.RoleTypeTIO, + }, + }, + }, + }, + }, nil) + + request := httptest.NewRequest("GET", "/queues/bulk-assignment", nil) + request = suite.AuthenticateOfficeRequest(request, officeUser) + params := queues.GetBulkAssignmentDataParams{ + HTTPRequest: request, + QueueType: models.StringPointer("PAYMENT_REQUEST"), + } + handlerConfig := suite.HandlerConfig() + handler := GetBulkAssignmentDataHandler{ + handlerConfig, + officeusercreator.NewOfficeUserFetcherPop(), + movefetcher.NewMoveFetcherBulkAssignment(), + } + response := handler.Handle(params) + suite.IsNotErrResponse(response) + suite.IsType(&queues.GetBulkAssignmentDataUnauthorized{}, response) + }) + suite.Run("TIO - returns properly formatted 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.RoleTypeTIO, + }, + }, + }, + }, + }, nil) + + // payment request to appear in the return + move := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + }, + }, + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + }, nil) + factory.BuildPaymentRequest(suite.DB(), []factory.Customization{ + { + Model: models.PaymentRequest{ + ID: uuid.Must(uuid.NewV4()), + IsFinal: false, + Status: models.PaymentRequestStatusPending, + RejectionReason: nil, + }, + }, + { + Model: move, + LinkOnly: true, + }, + }, nil) + + request := httptest.NewRequest("GET", "/queues/bulk-assignment", nil) + request = suite.AuthenticateOfficeRequest(request, officeUser) + params := queues.GetBulkAssignmentDataParams{ + HTTPRequest: request, + QueueType: models.StringPointer("PAYMENT_REQUEST"), + } + 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/models/payment_request.go b/pkg/models/payment_request.go index 120f7a1d4fb..8a57e05e0ae 100644 --- a/pkg/models/payment_request.go +++ b/pkg/models/payment_request.go @@ -89,6 +89,11 @@ type PaymentRequest struct { TPPSPaidInvoiceReports TPPSPaidInvoiceReportEntrys `has_many:"tpps_paid_invoice_reports" fk_id:"payment_request_number"` } +type PaymentRequestWithEarliestRequestedDate struct { + ID uuid.UUID `json:"id" db:"id"` + EarliestRequestedDate time.Time `db:"requested_at"` +} + // TableName overrides the table name used by Pop. func (p PaymentRequest) TableName() string { return "payment_requests" diff --git a/pkg/services/move.go b/pkg/services/move.go index 0262e8da8ae..ff3abe681de 100644 --- a/pkg/services/move.go +++ b/pkg/services/move.go @@ -33,6 +33,7 @@ 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) + FetchMovesForBulkAssignmentPaymentRequest(appCtx appcontext.AppContext, gbloc string, officeId uuid.UUID) ([]models.MoveWithEarliestDate, error) } //go:generate mockery --name MoveSearcher diff --git a/pkg/services/move/move_fetcher.go b/pkg/services/move/move_fetcher.go index c0ca805f7c1..e3c4e0ad370 100644 --- a/pkg/services/move/move_fetcher.go +++ b/pkg/services/move/move_fetcher.go @@ -135,7 +135,8 @@ func (f moveFetcherBulkAssignment) FetchMovesForBulkAssignmentCounseling(appCtx INNER JOIN mto_shipments ON mto_shipments.move_id = moves.id LEFT JOIN ppm_shipments ON ppm_shipments.shipment_id = mto_shipments.id WHERE - moves.status = 'NEEDS SERVICE COUNSELING' + mto_shipments.deleted_at IS NULL + AND moves.status = 'NEEDS SERVICE COUNSELING' AND orders.gbloc = $1 AND moves.show = $2 AND moves.sc_assigned_id IS NULL @@ -223,37 +224,92 @@ func (f moveFetcherBulkAssignment) FetchMovesForBulkAssignmentCloseout(appCtx ap 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.MTOShipmentTypeHHGOutOfNTS, - gbloc, - models.MTOShipmentTypeHHGOutOfNTS, - gbloc). + sqlQuery := ` + 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 + INNER JOIN duty_locations as origin_dl ON orders.origin_duty_location_id = origin_dl.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 + mto_shipments.deleted_at IS NULL + AND (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 (moves.ppm_type IS NULL OR (moves.ppm_type = 'PARTIAL' or (moves.ppm_type = 'FULL' and origin_dl.provides_services_counseling = 'false'))) ` + if gbloc == "USMC" { + sqlQuery += ` + AND service_members.affiliation ILIKE 'MARINES' ` + } else { + sqlQuery += ` + AND service_members.affiliation != 'MARINES' + AND ((mto_shipments.shipment_type != 'HHG_OUTOF_NTS' AND move_to_gbloc.gbloc = '` + gbloc + `') + OR (mto_shipments.shipment_type = 'HHG_OUTOF_NTS' AND orders.gbloc = '` + gbloc + `')) ` + } + sqlQuery += ` + GROUP BY moves.id + ORDER BY earliest_date ASC` + + err := appCtx.DB().RawQuery(sqlQuery, + models.BoolPointer(true), + 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) FetchMovesForBulkAssignmentPaymentRequest(appCtx appcontext.AppContext, gbloc string, officeId uuid.UUID) ([]models.MoveWithEarliestDate, error) { + var moves []models.MoveWithEarliestDate + + sqlQuery := ` + SELECT + moves.id, + 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 + INNER JOIN payment_requests on payment_requests.move_id = moves.id + LEFT JOIN move_to_gbloc ON move_to_gbloc.move_id = moves.id + WHERE moves.show = $1 + AND (orders.orders_type NOT IN ($2, $3, $4)) + AND moves.tio_assigned_id IS NULL + AND payment_requests.status = 'PENDING' ` + if gbloc == "USMC" { + sqlQuery += ` + AND service_members.affiliation ILIKE 'MARINES' ` + } else { + sqlQuery += ` + AND service_members.affiliation != 'MARINES' + AND move_to_gbloc.gbloc = '` + gbloc + `' ` + } + sqlQuery += ` + GROUP BY moves.id + ORDER BY earliest_date ASC` + + err := appCtx.DB().RawQuery(sqlQuery, + models.BoolPointer(true), + internalmessages.OrdersTypeBLUEBARK, + internalmessages.OrdersTypeWOUNDEDWARRIOR, + internalmessages.OrdersTypeSAFETY). All(&moves) if err != nil { diff --git a/pkg/services/move/move_fetcher_test.go b/pkg/services/move/move_fetcher_test.go index e6144353c88..62b6551add5 100644 --- a/pkg/services/move/move_fetcher_test.go +++ b/pkg/services/move/move_fetcher_test.go @@ -3,6 +3,8 @@ package move import ( "time" + "github.com/gofrs/uuid" + "github.com/transcom/mymove/pkg/apperror" "github.com/transcom/mymove/pkg/factory" "github.com/transcom/mymove/pkg/gen/internalmessages" @@ -105,7 +107,7 @@ func (suite *MoveServiceSuite) TestMoveFetcher() { }) } -func (suite *MoveServiceSuite) TestMoveFetcherBulkAssignment() { +func (suite *MoveServiceSuite) TestMoveFetcherBulkAssignmentSC() { setupTestData := func() (services.MoveFetcherBulkAssignment, models.Move, models.TransportationOffice, models.OfficeUser) { moveFetcher := NewMoveFetcherBulkAssignment() transportationOffice := factory.BuildTransportationOffice(suite.DB(), nil, nil) @@ -149,7 +151,7 @@ func (suite *MoveServiceSuite) TestMoveFetcherBulkAssignment() { return moveFetcher, move, transportationOffice, officeUser } - suite.Run("Returns moves that fulfill the query's criteria", func() { + suite.Run("SC - Returns moves that fulfill the query's criteria", func() { moveFetcher, _, _, officeUser := setupTestData() moves, err := moveFetcher.FetchMovesForBulkAssignmentCounseling(suite.AppContextForTest(), "KKFA", officeUser.TransportationOffice.ID) suite.FatalNoError(err) @@ -517,10 +519,13 @@ func (suite *MoveServiceSuite) TestMoveFetcherBulkAssignment() { suite.Equal(1, len(moves)) suite.NotEqual(marinePPM.ID, moves[0].ID) }) +} - suite.Run("TOO: Returns moves that fulfill the query criteria", func() { +func (suite *MoveServiceSuite) TestMoveFetcherBulkAssignmentTOO() { + setupTestData := func() (services.MoveFetcherBulkAssignment, models.TransportationOffice, models.OfficeUser) { moveFetcher := NewMoveFetcherBulkAssignment() transportationOffice := factory.BuildTransportationOffice(suite.DB(), nil, nil) + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ { Model: transportationOffice, @@ -552,6 +557,128 @@ func (suite *MoveServiceSuite) TestMoveFetcherBulkAssignment() { Type: &factory.TransportationOffices.CounselingOffice, }, }, nil) + + return moveFetcher, transportationOffice, officeUser + } + + suite.Run("TOO: Returns moves that fulfill the query's criteria", func() { + moveFetcher, _, officeUser := setupTestData() + moves, err := moveFetcher.FetchMovesForBulkAssignmentTaskOrder(suite.AppContextForTest(), "KKFA", officeUser.TransportationOffice.ID) + suite.FatalNoError(err) + suite.Equal(2, len(moves)) + }) + + suite.Run("TOO: Does not return moves with safety, bluebark, or wounded warrior order types", func() { + moveFetcher, transportationOffice, officeUser := setupTestData() + factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.Order{ + OrdersType: internalmessages.OrdersTypeSAFETY, + }, + }, + { + Model: models.Move{ + Status: models.MoveStatusServiceCounselingCompleted, + }, + }, + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + }, nil) + factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.Order{ + OrdersType: internalmessages.OrdersTypeBLUEBARK, + }, + }, + { + Model: models.Move{ + Status: models.MoveStatusServiceCounselingCompleted, + }, + }, + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + }, nil) + factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.Order{ + OrdersType: internalmessages.OrdersTypeWOUNDEDWARRIOR, + }, + }, + { + Model: models.Move{ + Status: models.MoveStatusServiceCounselingCompleted, + }, + }, + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + }, nil) + + moves, err := moveFetcher.FetchMovesForBulkAssignmentTaskOrder(suite.AppContextForTest(), "KKFA", officeUser.TransportationOffice.ID) + suite.FatalNoError(err) + suite.Equal(2, len(moves)) + }) + + suite.Run("TOO: Does not return moves that are already assigned", func() { + moveFetcher := NewMoveFetcherBulkAssignment() + transportationOffice := factory.BuildTransportationOffice(suite.DB(), nil, nil) + + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + }, []roles.RoleType{roles.RoleTypeTOO}) + + assignedMove := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusServiceCounselingCompleted, + }, + }, + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + { + Model: officeUser, + LinkOnly: true, + Type: &factory.OfficeUsers.TOOAssignedUser, + }, + }, nil) + + moves, err := moveFetcher.FetchMovesForBulkAssignmentTaskOrder(suite.AppContextForTest(), "KKFA", officeUser.TransportationOffice.ID) + suite.FatalNoError(err) + + // confirm that the assigned move isn't returned + for _, move := range moves { + suite.NotEqual(move.ID, assignedMove.ID) + } + + // confirm that the rest of the details are correct + // move is SERVICE COUNSELING COMPLETED + suite.Equal(assignedMove.Status, models.MoveStatusServiceCounselingCompleted) + // GBLOC is the same + suite.Equal(*assignedMove.Orders.OriginDutyLocationGBLOC, officeUser.TransportationOffice.Gbloc) + // Show is true + suite.Equal(assignedMove.Show, models.BoolPointer(true)) + // Orders type isn't WW, BB, or Safety + suite.Equal(assignedMove.Orders.OrdersType, internalmessages.OrdersTypePERMANENTCHANGEOFSTATION) + }) + + suite.Run("TOO: Does not return payment requests with Marines if GBLOC not USMC", func() { + moveFetcher, transportationOffice, officeUser := setupTestData() + marine := models.AffiliationMARINES factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ { @@ -574,4 +701,360 @@ func (suite *MoveServiceSuite) TestMoveFetcherBulkAssignment() { suite.FatalNoError(err) suite.Equal(2, len(moves)) }) + + suite.Run("TOO: Only return payment requests with Marines if GBLOC is USMC", func() { + moveFetcher, transportationOffice, officeUser := setupTestData() + + marine := models.AffiliationMARINES + factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusServiceCounselingCompleted, + }, + }, + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + { + Model: models.ServiceMember{ + Affiliation: &marine, + }, + }, + }, nil) + + moves, err := moveFetcher.FetchMovesForBulkAssignmentTaskOrder(suite.AppContextForTest(), "USMC", officeUser.TransportationOffice.ID) + suite.FatalNoError(err) + suite.Equal(1, len(moves)) + }) + +} + +func (suite *MoveServiceSuite) TestMoveFetcherBulkAssignmentTIO() { + setupTestData := func() (services.MoveFetcherBulkAssignment, models.TransportationOffice, models.OfficeUser) { + moveFetcher := NewMoveFetcherBulkAssignment() + transportationOffice := factory.BuildTransportationOffice(suite.DB(), nil, nil) + + // this move has a transportation office associated with it that matches + // the TIO's transportation office and should be found + move := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + }, + }, + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + }, nil) + factory.BuildPaymentRequest(suite.DB(), []factory.Customization{ + { + Model: models.PaymentRequest{ + ID: uuid.Must(uuid.NewV4()), + IsFinal: false, + Status: models.PaymentRequestStatusPending, + RejectionReason: nil, + }, + }, + { + Model: move, + LinkOnly: true, + }, + }, nil) + + move2 := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + }, + }, + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + }, nil) + factory.BuildPaymentRequest(suite.DB(), []factory.Customization{ + { + Model: models.PaymentRequest{ + ID: uuid.Must(uuid.NewV4()), + IsFinal: false, + Status: models.PaymentRequestStatusPending, + RejectionReason: nil, + }, + }, + { + Model: move2, + LinkOnly: true, + }, + }, nil) + + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + }, []roles.RoleType{roles.RoleTypeTIO}) + + return moveFetcher, transportationOffice, officeUser + } + + suite.Run("TIO: Returns moves that fulfill the query criteria", func() { + moveFetcher, _, officeUser := setupTestData() + moves, err := moveFetcher.FetchMovesForBulkAssignmentPaymentRequest(suite.AppContextForTest(), "KKFA", officeUser.TransportationOffice.ID) + suite.FatalNoError(err) + suite.Equal(2, len(moves)) + }) + + suite.Run("Does not return moves that are already assigned", func() { + moveFetcher := NewMoveFetcherBulkAssignment() + transportationOffice := factory.BuildTransportationOffice(suite.DB(), nil, nil) + + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + }, []roles.RoleType{roles.RoleTypeTIO}) + + move := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + }, + }, + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + { + Model: officeUser, + LinkOnly: true, + Type: &factory.OfficeUsers.TIOAssignedUser, + }, + }, nil) + assignedPaymentRequest := factory.BuildPaymentRequest(suite.DB(), []factory.Customization{ + { + Model: models.PaymentRequest{ + ID: uuid.Must(uuid.NewV4()), + IsFinal: false, + Status: models.PaymentRequestStatusPending, + RejectionReason: nil, + }, + }, + { + Model: move, + LinkOnly: true, + }, + }, nil) + + moves, err := moveFetcher.FetchMovesForBulkAssignmentPaymentRequest(suite.AppContextForTest(), "KKFA", officeUser.TransportationOffice.ID) + suite.FatalNoError(err) + + // confirm that the assigned move isn't returned + for _, move := range moves { + suite.NotEqual(move.ID, assignedPaymentRequest.ID) + } + + // confirm that the rest of the details are correct + // move is APPROVALS REQUESTED STATUS + suite.Equal(assignedPaymentRequest.Status, models.PaymentRequestStatusPending) + // GBLOC is the same + suite.Equal(*move.Orders.OriginDutyLocationGBLOC, officeUser.TransportationOffice.Gbloc) + // Show is true + suite.Equal(move.Show, models.BoolPointer(true)) + // Orders type isn't WW, BB, or Safety + suite.Equal(move.Orders.OrdersType, internalmessages.OrdersTypePERMANENTCHANGEOFSTATION) + }) + + suite.Run("TIO: Does not return moves with safety, bluebark, or wounded warrior order types", func() { + moveFetcher, transportationOffice, officeUser := setupTestData() + moveSafety := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.Order{ + OrdersType: internalmessages.OrdersTypeSAFETY, + }, + }, + { + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + }, + }, + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + }, nil) + factory.BuildPaymentRequest(suite.DB(), []factory.Customization{ + { + Model: models.PaymentRequest{ + ID: uuid.Must(uuid.NewV4()), + IsFinal: false, + Status: models.PaymentRequestStatusPending, + RejectionReason: nil, + }, + }, + { + Model: moveSafety, + LinkOnly: true, + }, + }, nil) + + moveBB := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.Order{ + OrdersType: internalmessages.OrdersTypeBLUEBARK, + }, + }, + { + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + }, + }, + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + }, nil) + factory.BuildPaymentRequest(suite.DB(), []factory.Customization{ + { + Model: models.PaymentRequest{ + ID: uuid.Must(uuid.NewV4()), + IsFinal: false, + Status: models.PaymentRequestStatusPending, + RejectionReason: nil, + }, + }, + { + Model: moveBB, + LinkOnly: true, + }, + }, nil) + + moveWW := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.Order{ + OrdersType: internalmessages.OrdersTypeWOUNDEDWARRIOR, + }, + }, + { + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + }, + }, + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + }, nil) + factory.BuildPaymentRequest(suite.DB(), []factory.Customization{ + { + Model: models.PaymentRequest{ + ID: uuid.Must(uuid.NewV4()), + IsFinal: false, + Status: models.PaymentRequestStatusPending, + RejectionReason: nil, + }, + }, + { + Model: moveWW, + LinkOnly: true, + }, + }, nil) + + moves, err := moveFetcher.FetchMovesForBulkAssignmentPaymentRequest(suite.AppContextForTest(), "KKFA", officeUser.TransportationOffice.ID) + suite.FatalNoError(err) + suite.Equal(2, len(moves)) + }) + + suite.Run("TIO: Does not return payment requests with Marines if GBLOC not USMC", func() { + moveFetcher, transportationOffice, officeUser := setupTestData() + + marine := models.AffiliationMARINES + move := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + }, + }, + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + { + Model: models.ServiceMember{ + Affiliation: &marine, + }, + }, + }, nil) + factory.BuildPaymentRequest(suite.DB(), []factory.Customization{ + { + Model: models.PaymentRequest{ + ID: uuid.Must(uuid.NewV4()), + IsFinal: false, + Status: models.PaymentRequestStatusPending, + RejectionReason: nil, + }, + }, + { + Model: move, + LinkOnly: true, + }, + }, nil) + + moves, err := moveFetcher.FetchMovesForBulkAssignmentPaymentRequest(suite.AppContextForTest(), "KKFA", officeUser.TransportationOffice.ID) + suite.FatalNoError(err) + suite.Equal(2, len(moves)) + }) + + suite.Run("TIO: Only return payment requests with Marines if GBLOC is USMC", func() { + moveFetcher, transportationOffice, officeUser := setupTestData() + + marine := models.AffiliationMARINES + move := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusServiceCounselingCompleted, + }, + }, + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + { + Model: models.ServiceMember{ + Affiliation: &marine, + }, + }, + }, nil) + factory.BuildPaymentRequest(suite.DB(), []factory.Customization{ + { + Model: models.PaymentRequest{ + ID: uuid.Must(uuid.NewV4()), + IsFinal: false, + Status: models.PaymentRequestStatusPending, + RejectionReason: nil, + }, + }, + { + Model: move, + LinkOnly: true, + }, + }, nil) + + moves, err := moveFetcher.FetchMovesForBulkAssignmentPaymentRequest(suite.AppContextForTest(), "USMC", officeUser.TransportationOffice.ID) + suite.FatalNoError(err) + suite.Equal(1, len(moves)) + }) }