Skip to content

Commit

Permalink
refactor bulk assignment save to distribute move assignment by priori…
Browse files Browse the repository at this point in the history
…ty evenly
  • Loading branch information
paulstonebraker committed Feb 26, 2025
1 parent 7e46906 commit b202bfa
Show file tree
Hide file tree
Showing 3 changed files with 197 additions and 28 deletions.
75 changes: 50 additions & 25 deletions pkg/services/move/move_assignment.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package move

import (
"container/list"

"github.com/gofrs/uuid"

"github.com/transcom/mymove/pkg/appcontext"
Expand All @@ -22,32 +24,55 @@ func (a moveAssigner) BulkMoveAssignment(appCtx appcontext.AppContext, queueType
return nil, apperror.NewBadDataError("No moves to assign")
}

// make a map to track users and their assignment counts
// and a queue of userIDs
moveAssignments := make(map[uuid.UUID]int)
queue := list.New()
for _, user := range officeUserData {
if user != nil && user.MoveAssignments > 0 {
userID := uuid.FromStringOrNil(user.ID.String())
moveAssignments[userID] = int(user.MoveAssignments)
queue.PushBack(userID)
}
}

// point at the index in the movesToAssign set
moveIndex := 0

transactionErr := appCtx.NewTransaction(func(txnAppCtx appcontext.AppContext) error {
for _, move := range movesToAssign {
for _, officeUser := range officeUserData {
if officeUser != nil && officeUser.MoveAssignments > 0 {
officeUserId := uuid.FromStringOrNil(officeUser.ID.String())

switch queueType {
case string(models.QueueTypeCounseling):
move.SCAssignedID = &officeUserId
case string(models.QueueTypeCloseout):
move.SCAssignedID = &officeUserId
case string(models.QueueTypeTaskOrder):
move.TOOAssignedID = &officeUserId
case string(models.QueueTypePaymentRequest):
move.TIOAssignedID = &officeUserId
}

officeUser.MoveAssignments -= 1

verrs, err := appCtx.DB().ValidateAndUpdate(&move)
if err != nil || verrs.HasAny() {
return apperror.NewInvalidInputError(move.ID, err, verrs, "")
}

break
}
// while we have a queue...
for moveIndex < len(movesToAssign) && queue.Len() > 0 {
// grab that ID off the front
user := queue.Front()
userID := user.Value.(uuid.UUID)
queue.Remove(user)

// do our assignment logic
move := movesToAssign[moveIndex]
switch queueType {
case string(models.QueueTypeCounseling):
move.SCAssignedID = &userID
case string(models.QueueTypeCloseout):
move.SCAssignedID = &userID
case string(models.QueueTypeTaskOrder):
move.TOOAssignedID = &userID
case string(models.QueueTypePaymentRequest):
move.TIOAssignedID = &userID
}

verrs, err := appCtx.DB().ValidateAndUpdate(&move)
if err != nil || verrs.HasAny() {
return apperror.NewInvalidInputError(move.ID, err, verrs, "")
}

// decrement the users assignment count
moveAssignments[userID]--
// increment our index
moveIndex++

// If user still has remaining assignments, re-queue them
if moveAssignments[userID] > 0 {
queue.PushBack(userID)
}
}

Expand Down
137 changes: 137 additions & 0 deletions pkg/services/move/move_assignment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,143 @@ func (suite *MoveServiceSuite) TestBulkMoveAssignment() {
return transportationOffice, move1, move2, move3
}

suite.Run("properly distributes moves", func() {
transportationOffice, move1, move2, move3 := setupTestData()
move4 := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{
{
Model: models.Move{
Status: models.MoveStatusNeedsServiceCounseling,
},
},
{
Model: transportationOffice,
LinkOnly: true,
Type: &factory.TransportationOffices.CounselingOffice,
},
}, nil)
move5 := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{
{
Model: models.Move{
Status: models.MoveStatusNeedsServiceCounseling,
},
},
{
Model: transportationOffice,
LinkOnly: true,
Type: &factory.TransportationOffices.CounselingOffice,
},
}, nil)
move6 := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{
{
Model: models.Move{
Status: models.MoveStatusNeedsServiceCounseling,
},
},
{
Model: transportationOffice,
LinkOnly: true,
Type: &factory.TransportationOffices.CounselingOffice,
},
}, nil)

officeUser1 := 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)
officeUser2 := factory.BuildOfficeUserWithPrivileges(suite.DB(), []factory.Customization{
{
Model: models.OfficeUser{
Email: "officeuser2@example.com",
Active: true,
},
},
{
Model: transportationOffice,
LinkOnly: true,
Type: &factory.TransportationOffices.CounselingOffice,
},
{
Model: models.User{
Roles: []roles.Role{
{
RoleType: roles.RoleTypeServicesCounselor,
},
},
},
},
}, nil)
officeUser3 := factory.BuildOfficeUserWithPrivileges(suite.DB(), []factory.Customization{
{
Model: models.OfficeUser{
Email: "officeuser3@example.com",
Active: true,
},
},
{
Model: transportationOffice,
LinkOnly: true,
Type: &factory.TransportationOffices.CounselingOffice,
},
{
Model: models.User{
Roles: []roles.Role{
{
RoleType: roles.RoleTypeServicesCounselor,
},
},
},
},
}, nil)

moves := []models.Move{move1, move2, move3, move4, move5, move6}
userData := []*ghcmessages.BulkAssignmentForUser{
{ID: strfmt.UUID(officeUser1.ID.String()), MoveAssignments: 1},
{ID: strfmt.UUID(officeUser2.ID.String()), MoveAssignments: 2},
{ID: strfmt.UUID(officeUser3.ID.String()), MoveAssignments: 3},
}

_, err := moveAssigner.BulkMoveAssignment(suite.AppContextForTest(), string(models.QueueTypeCounseling), userData, moves)
suite.NoError(err)

// reload move data to check assigned
suite.NoError(suite.DB().Reload(&move1))
suite.NoError(suite.DB().Reload(&move2))
suite.NoError(suite.DB().Reload(&move3))
suite.NoError(suite.DB().Reload(&move4))
suite.NoError(suite.DB().Reload(&move5))
suite.NoError(suite.DB().Reload(&move6))

suite.Equal(officeUser1.ID, *move1.SCAssignedID)
suite.Equal(officeUser2.ID, *move2.SCAssignedID)
suite.Equal(officeUser3.ID, *move3.SCAssignedID)
suite.Equal(officeUser2.ID, *move4.SCAssignedID)
suite.Equal(officeUser3.ID, *move5.SCAssignedID)
suite.Equal(officeUser3.ID, *move6.SCAssignedID)
})

suite.Run("successfully assigns multiple counseling moves to a SC user", func() {
transportationOffice, move1, move2, move3 := setupTestData()

Expand Down
13 changes: 10 additions & 3 deletions pkg/services/move/move_fetcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,17 @@ func (f moveFetcher) FetchMove(appCtx appcontext.AppContext, locator string, sea
func (f moveFetcher) FetchMovesByIdArray(appCtx appcontext.AppContext, moveIds []ghcmessages.BulkAssignmentMoveData) (models.Moves, error) {
moves := models.Moves{}

err := appCtx.DB().Q().
Where("id in (?)", moveIds).
caseExpr := "CASE "
for i, moveId := range moveIds {
caseExpr += "WHEN id = '" + fmt.Sprintf("%v", moveId) + "' THEN " + fmt.Sprintf("%d", i) + " "
}
caseExpr += "ELSE " + fmt.Sprintf("%d", len(moveIds)) + " END"

query := appCtx.DB().Q().
Where("show = TRUE").
All(&moves)
Where("id in (?)", moveIds).
Order(caseExpr)
err := query.All(&moves)

if err != nil {
return nil, err
Expand Down

0 comments on commit b202bfa

Please sign in to comment.