Skip to content

Commit

Permalink
Merge pull request #14039 from transcom/INT-B-21527-counselor-actual-…
Browse files Browse the repository at this point in the history
…expense-reimbursement

INT - B-21527 counselor actual expense reimbursement
  • Loading branch information
taeJungCaci authored Oct 30, 2024
2 parents c76a0c1 + 6311d25 commit 5e0b062
Show file tree
Hide file tree
Showing 35 changed files with 628 additions and 24 deletions.
1 change: 1 addition & 0 deletions pkg/factory/move_factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ func BuildMoveWithPPMShipment(db *pop.Connection, customs []Customization, trait
ppmShipment.ShipmentID = mtoShipment.ID

mtoShipment.PPMShipment = &ppmShipment
mtoShipment.ShipmentType = models.MTOShipmentTypePPM
move.MTOShipments = append(move.MTOShipments, mtoShipment)

if db != nil {
Expand Down
28 changes: 28 additions & 0 deletions pkg/handlers/internalapi/orders.go
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,34 @@ func (h UpdateOrdersHandler) Handle(params ordersop.UpdateOrdersParams) middlewa

order.EntitlementID = &entitlement.ID
order.Entitlement = &entitlement

// change actual expense reimbursement to 'true' for all PPM shipments if pay grade is civilian
if payload.Grade != nil && *payload.Grade == models.ServiceMemberGradeCIVILIANEMPLOYEE {
moves, fetchErr := models.FetchMovesByOrderID(appCtx.DB(), order.ID)
if fetchErr != nil {
appCtx.Logger().Error("failure encountered querying for move associated with the order", zap.Error(fetchErr))
} else {
move := moves[0]
for i := range move.MTOShipments {
shipment := &move.MTOShipments[i]

if shipment.ShipmentType == models.MTOShipmentTypePPM {
if shipment.PPMShipment == nil {
appCtx.Logger().Warn("PPM shipment not found for MTO shipment", zap.String("shipmentID", shipment.ID.String()))
continue
}
// actual expense reimbursement is always true for civilian moves
shipment.PPMShipment.IsActualExpenseReimbursement = models.BoolPointer(true)

if verrs, err := appCtx.DB().ValidateAndUpdate(shipment.PPMShipment); verrs.HasAny() || err != nil {
msg := "failure saving PPM shipment when updating orders"
appCtx.Logger().Error(msg, zap.Error(err))
}
}
}
}
}

}
order.Grade = payload.Grade

Expand Down
29 changes: 19 additions & 10 deletions pkg/services/move/move_router.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,22 @@ func (router moveRouter) needsServiceCounseling(appCtx appcontext.AppContext, mo

// sendToServiceCounselor makes the move available for a Service Counselor to review
func (router moveRouter) sendToServiceCounselor(appCtx appcontext.AppContext, move *models.Move) error {
var orders models.Order
err := appCtx.DB().Q().
Where("orders.id = ?", move.OrdersID).
First(&orders)

if err != nil {
switch err {
case sql.ErrNoRows:
appCtx.Logger().Error("failure finding move", zap.Error(err))
return apperror.NewNotFoundError(move.OrdersID, "looking for move.OrdersID")
default:
appCtx.Logger().Error("failure encountered querying for orders associated with the move", zap.Error(err))
return apperror.NewQueryError("Order", err, fmt.Sprintf("failure encountered querying for orders associated with the move, %s, id: %s", err.Error(), move.ID))
}
}

if move.Status == models.MoveStatusNeedsServiceCounseling {
return nil
}
Expand All @@ -186,6 +202,7 @@ func (router moveRouter) sendToServiceCounselor(appCtx appcontext.AppContext, mo
)
}

isCivilian := orders.Grade != nil && *orders.Grade == models.ServiceMemberGradeCIVILIANEMPLOYEE
move.Status = models.MoveStatusNeedsServiceCounseling
now := time.Now()
move.SubmittedAt = &now
Expand All @@ -195,6 +212,8 @@ func (router moveRouter) sendToServiceCounselor(appCtx appcontext.AppContext, mo
if move.MTOShipments[i].ShipmentType == models.MTOShipmentTypePPM {
move.MTOShipments[i].Status = models.MTOShipmentStatusSubmitted
move.MTOShipments[i].PPMShipment.Status = models.PPMShipmentStatusSubmitted
// actual expense reimbursement is always true for civilian moves
move.MTOShipments[i].PPMShipment.IsActualExpenseReimbursement = models.BoolPointer(isCivilian)

if verrs, err := appCtx.DB().ValidateAndUpdate(&move.MTOShipments[i]); verrs.HasAny() || err != nil {
msg := "failure saving shipment when routing move submission"
Expand Down Expand Up @@ -235,16 +254,6 @@ func (router moveRouter) sendToServiceCounselor(appCtx appcontext.AppContext, mo
}
}
}
// update status for mobile home shipment
if move.MTOShipments[i].ShipmentType == models.MTOShipmentTypeMobileHome {
move.MTOShipments[i].Status = models.MTOShipmentStatusSubmitted

if verrs, err := appCtx.DB().ValidateAndUpdate(&move.MTOShipments[i]); verrs.HasAny() || err != nil {
msg := "failure saving shipment when routing move submission"
appCtx.Logger().Error(msg, zap.Error(err))
return apperror.NewInvalidInputError(move.MTOShipments[i].ID, err, verrs, msg)
}
}
}

if verrs, err := appCtx.DB().ValidateAndSave(move); verrs.HasAny() || err != nil {
Expand Down
118 changes: 118 additions & 0 deletions pkg/services/move/move_router_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,124 @@ func (suite *MoveServiceSuite) TestMoveSubmission() {
suite.Contains(err.Error(), expError)
suite.Equal(models.MoveStatusNeedsServiceCounseling, move.Status, "expected move to still be in NEEDS_SERVICE_COUNSELING status when routing has failed")
})

suite.Run("PPM Actual Expense Reimbursement is true for Civilian Employee on submit", func() {
move := factory.BuildMove(suite.DB(), []factory.Customization{
{
Model: models.DutyLocation{
ProvidesServicesCounseling: true,
},
Type: &factory.DutyLocations.OriginDutyLocation,
},
{
Model: models.Move{
Status: models.MoveStatusDRAFT,
},
},
{
Model: models.Order{
Grade: models.ServiceMemberGradeCIVILIANEMPLOYEE.Pointer(),
},
},
}, nil)

shipment := factory.BuildMTOShipmentMinimal(suite.DB(), []factory.Customization{
{
Model: models.MTOShipment{
Status: models.MTOShipmentStatusDraft,
ShipmentType: models.MTOShipmentTypePPM,
},
},
{
Model: move,
LinkOnly: true,
},
}, nil)

ppmShipment := factory.BuildPPMShipment(suite.DB(), []factory.Customization{
{
Model: models.PPMShipment{
Status: models.PPMShipmentStatusDraft,
IsActualExpenseReimbursement: models.BoolPointer(false),
},
},
}, nil)
move.MTOShipments = models.MTOShipments{shipment}
move.MTOShipments[0].PPMShipment = &ppmShipment

newSignedCertification := factory.BuildSignedCertification(nil, []factory.Customization{
{
Model: move,
LinkOnly: true,
},
}, nil)
err := moveRouter.Submit(suite.AppContextForTest(), &move, &newSignedCertification)
suite.NoError(err)
suite.NotNil(newSignedCertification)

err = suite.DB().Find(&move, move.ID)
suite.NoError(err)
suite.True(*move.MTOShipments[0].PPMShipment.IsActualExpenseReimbursement)
})

suite.Run("PPM Actual Expense Reimbursement is false for non-civilian on submit", func() {
move := factory.BuildMove(suite.DB(), []factory.Customization{
{
Model: models.DutyLocation{
ProvidesServicesCounseling: true,
},
Type: &factory.DutyLocations.OriginDutyLocation,
},
{
Model: models.Move{
Status: models.MoveStatusDRAFT,
},
},
{
Model: models.Order{
Grade: models.ServiceMemberGradeE1.Pointer(),
},
},
}, nil)

shipment := factory.BuildMTOShipmentMinimal(suite.DB(), []factory.Customization{
{
Model: models.MTOShipment{
Status: models.MTOShipmentStatusDraft,
ShipmentType: models.MTOShipmentTypePPM,
},
},
{
Model: move,
LinkOnly: true,
},
}, nil)

ppmShipment := factory.BuildPPMShipment(suite.DB(), []factory.Customization{
{
Model: models.PPMShipment{
Status: models.PPMShipmentStatusDraft,
IsActualExpenseReimbursement: models.BoolPointer(true),
},
},
}, nil)
move.MTOShipments = models.MTOShipments{shipment}
move.MTOShipments[0].PPMShipment = &ppmShipment

newSignedCertification := factory.BuildSignedCertification(nil, []factory.Customization{
{
Model: move,
LinkOnly: true,
},
}, nil)
err := moveRouter.Submit(suite.AppContextForTest(), &move, &newSignedCertification)
suite.NoError(err)
suite.NotNil(newSignedCertification)

err = suite.DB().Find(&move, move.ID)
suite.NoError(err)
suite.False(*move.MTOShipments[0].PPMShipment.IsActualExpenseReimbursement)
})
}

func (suite *MoveServiceSuite) TestMoveCancellation() {
Expand Down
27 changes: 27 additions & 0 deletions pkg/services/order/order_updater.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/gobuffalo/validate/v3"
"github.com/gofrs/uuid"
"go.uber.org/zap"

"github.com/transcom/mymove/pkg/appcontext"
"github.com/transcom/mymove/pkg/apperror"
Expand Down Expand Up @@ -672,5 +673,31 @@ func updateOrderInTx(appCtx appcontext.AppContext, order models.Order, checks ..
return nil, e
}

// change actual expense reimbursement to 'true' for all PPM shipments if pay grade is civilian
if order.Grade != nil && *order.Grade == models.ServiceMemberGradeCIVILIANEMPLOYEE {
moves, fetchErr := models.FetchMovesByOrderID(appCtx.DB(), order.ID)
if fetchErr != nil || len(moves) == 0 {
appCtx.Logger().Error("failure encountered querying for move associated with the order", zap.Error(err))
} else {
move := moves[0]
for i := range move.MTOShipments {
shipment := &move.MTOShipments[i]

if shipment.ShipmentType == models.MTOShipmentTypePPM {
if shipment.PPMShipment == nil {
appCtx.Logger().Warn("PPM shipment not found for MTO shipment", zap.String("shipmentID", shipment.ID.String()))
continue
}
shipment.PPMShipment.IsActualExpenseReimbursement = models.BoolPointer(true)

if verrs, err := appCtx.DB().ValidateAndUpdate(shipment.PPMShipment); verrs.HasAny() || err != nil {
msg := "failure saving PPM shipment when updating orders"
appCtx.Logger().Error(msg, zap.Error(err))
}
}
}
}
}

return &order, nil
}
28 changes: 28 additions & 0 deletions pkg/services/order/order_updater_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,34 @@ func (suite *OrderServiceSuite) TestUpdateOrderAsCounselor() {
suite.Equal(*updatedOrder.Entitlement.DBAuthorizedWeight, 16000)
})

suite.Run("Updates the PPM actual expense reimbursement when pay grade is civilian", func() {
moveRouter := move.NewMoveRouter()
orderUpdater := NewOrderUpdater(moveRouter)

ppmShipment := factory.BuildPPMShipmentThatNeedsCloseout(suite.DB(), nil, nil)
move := ppmShipment.Shipment.MoveTaskOrder

order := move.Orders
grade := ghcmessages.GradeCIVILIANEMPLOYEE
body := ghcmessages.CounselingUpdateOrderPayload{
Grade: &grade,
}
eTag := etag.GenerateEtag(order.UpdatedAt)

var moved models.Move
err := suite.DB().Find(&moved, move.ID)
suite.NoError(err)

_, _, errs := orderUpdater.UpdateOrderAsCounselor(suite.AppContextForTest(), order.ID, body, eTag)
suite.NoError(errs)

var updatedPPMShipment models.PPMShipment
err = suite.DB().Find(&updatedPPMShipment, ppmShipment.ID)

suite.NoError(err)
suite.EqualValues(true, *updatedPPMShipment.IsActualExpenseReimbursement)
})

suite.Run("Rolls back transaction if Order is invalid", func() {
moveRouter := move.NewMoveRouter()
orderUpdater := NewOrderUpdater(moveRouter)
Expand Down
3 changes: 3 additions & 0 deletions pkg/testdatagen/testharness/dispatch.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,9 @@ var actionDispatcher = map[string]actionFunc{
"ApprovedMoveWithPPMProgearWeightTicketOffice": func(appCtx appcontext.AppContext) testHarnessResponse {
return MakeApprovedMoveWithPPMProgearWeightTicketOffice(appCtx)
},
"ApprovedMoveWithPPMProgearWeightTicketOfficeCivilian": func(appCtx appcontext.AppContext) testHarnessResponse {
return MakeApprovedMoveWithPPMProgearWeightTicketOfficeCivilian(appCtx)
},
"ApprovedMoveWithPPMWeightTicketOffice": func(appCtx appcontext.AppContext) testHarnessResponse {
return MakeApprovedMoveWithPPMWeightTicketOffice(appCtx)
},
Expand Down
Loading

0 comments on commit 5e0b062

Please sign in to comment.