diff --git a/pkg/handlers/ghcapi/mto_service_items.go b/pkg/handlers/ghcapi/mto_service_items.go index f2e727e8418..e5f5572580d 100644 --- a/pkg/handlers/ghcapi/mto_service_items.go +++ b/pkg/handlers/ghcapi/mto_service_items.go @@ -153,7 +153,7 @@ func (h UpdateServiceItemSitEntryDateHandler) Handle(params mtoserviceitemop.Upd existingETag := etag.GenerateEtag(shipment.UpdatedAt) - shipment, err = h.UpdateShipment(appCtx, &shipmentWithSITInfo, existingETag, "ghc", nil) + shipment, err = h.UpdateShipment(appCtx, &shipmentWithSITInfo, existingETag, "ghc") if err != nil { appCtx.Logger().Error(fmt.Sprintf("Could not update the shipment SIT auth end date for shipment ID: %s: %s", shipment.ID, err)) } @@ -266,7 +266,7 @@ func (h UpdateMTOServiceItemStatusHandler) Handle(params mtoserviceitemop.Update existingETag := etag.GenerateEtag(shipment.UpdatedAt) - shipment, err = h.UpdateShipment(appCtx, &shipmentWithSITInfo, existingETag, "ghc", nil) + shipment, err = h.UpdateShipment(appCtx, &shipmentWithSITInfo, existingETag, "ghc") if err != nil { appCtx.Logger().Error(fmt.Sprintf("Could not update the shipment SIT auth end date for shipment ID: %s: %s", shipment.ID, err)) } diff --git a/pkg/handlers/ghcapi/mto_shipment.go b/pkg/handlers/ghcapi/mto_shipment.go index 08bb344bdd2..53011950b49 100644 --- a/pkg/handlers/ghcapi/mto_shipment.go +++ b/pkg/handlers/ghcapi/mto_shipment.go @@ -409,7 +409,7 @@ func (h UpdateShipmentHandler) Handle(params mtoshipmentops.UpdateMTOShipmentPar mtoShipment.PrimeEstimatedWeight = &previouslyRecordedWeight } - updatedMtoShipment, err := h.ShipmentUpdater.UpdateShipment(appCtx, mtoShipment, params.IfMatch, "ghc", nil) + updatedMtoShipment, err := h.ShipmentUpdater.UpdateShipment(appCtx, mtoShipment, params.IfMatch, "ghc") if err != nil { return handleError(err) } @@ -1124,7 +1124,7 @@ func (h ApproveSITExtensionHandler) Handle(params shipmentops.ApproveSITExtensio existingETag := etag.GenerateEtag(updatedShipment.UpdatedAt) - updatedShipment, err = h.UpdateShipment(appCtx, &shipmentWithSITInfo, existingETag, "ghc", nil) + updatedShipment, err = h.UpdateShipment(appCtx, &shipmentWithSITInfo, existingETag, "ghc") if err != nil { return handleError(err) } @@ -1371,7 +1371,7 @@ func (h CreateApprovedSITDurationUpdateHandler) Handle(params shipmentops.Create existingETag := etag.GenerateEtag(shipment.UpdatedAt) - shipment, err = h.UpdateShipment(appCtx, &shipmentWithSITInfo, existingETag, "ghc", nil) + shipment, err = h.UpdateShipment(appCtx, &shipmentWithSITInfo, existingETag, "ghc") if err != nil { return handleError(err) } diff --git a/pkg/handlers/ghcapi/mto_shipment_test.go b/pkg/handlers/ghcapi/mto_shipment_test.go index 6e313b2527b..904cfb69ea0 100644 --- a/pkg/handlers/ghcapi/mto_shipment_test.go +++ b/pkg/handlers/ghcapi/mto_shipment_test.go @@ -4655,7 +4655,6 @@ func (suite *HandlerSuite) TestUpdateShipmentHandler() { mock.Anything, mock.Anything, mock.Anything, - nil, ).Return(nil, err) oldShipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ diff --git a/pkg/handlers/internalapi/mto_shipment.go b/pkg/handlers/internalapi/mto_shipment.go index 5b401190232..dc23374a6af 100644 --- a/pkg/handlers/internalapi/mto_shipment.go +++ b/pkg/handlers/internalapi/mto_shipment.go @@ -169,7 +169,7 @@ func (h UpdateMTOShipmentHandler) Handle(params mtoshipmentops.UpdateMTOShipment h.GetTraceIDFromRequest(params.HTTPRequest))), invalidShipmentStatusErr } - updatedMTOShipment, err := h.shipmentUpdater.UpdateShipment(appCtx, mtoShipment, params.IfMatch, "internal", nil) + updatedMTOShipment, err := h.shipmentUpdater.UpdateShipment(appCtx, mtoShipment, params.IfMatch, "internal") if err != nil { appCtx.Logger().Error("internalapi.UpdateMTOShipmentHandler", zap.Error(err)) diff --git a/pkg/handlers/internalapi/mto_shipment_test.go b/pkg/handlers/internalapi/mto_shipment_test.go index d69494d9df0..2a06643c691 100644 --- a/pkg/handlers/internalapi/mto_shipment_test.go +++ b/pkg/handlers/internalapi/mto_shipment_test.go @@ -1487,7 +1487,6 @@ func (suite *HandlerSuite) TestUpdateMTOShipmentHandler() { mock.AnythingOfType("*models.MTOShipment"), mock.AnythingOfType("string"), mock.AnythingOfType("string"), - nil, ).Return(nil, err) subtestData := getDefaultMTOShipmentAndParams(&mockUpdater) diff --git a/pkg/handlers/primeapiv2/mto_shipment.go b/pkg/handlers/primeapiv2/mto_shipment.go index e4f2c41748a..d4a5e5012da 100644 --- a/pkg/handlers/primeapiv2/mto_shipment.go +++ b/pkg/handlers/primeapiv2/mto_shipment.go @@ -204,7 +204,7 @@ func (h UpdateMTOShipmentHandler) Handle(params mtoshipmentops.UpdateMTOShipment mtoShipment.ShipmentType = dbShipment.ShipmentType appCtx.Logger().Info("primeapi.UpdateMTOShipmentHandler info", zap.String("pointOfContact", params.Body.PointOfContact)) - mtoShipment, err = h.ShipmentUpdater.UpdateShipment(appCtx, mtoShipment, params.IfMatch, "prime-v2", h.planner) + mtoShipment, err = h.ShipmentUpdater.UpdateShipment(appCtx, mtoShipment, params.IfMatch, "prime-v2") if err != nil { appCtx.Logger().Error("primeapi.UpdateMTOShipmentHandler error", zap.Error(err)) switch e := err.(type) { diff --git a/pkg/handlers/primeapiv3/mto_shipment.go b/pkg/handlers/primeapiv3/mto_shipment.go index aa8476f6267..d2f6221ac9f 100644 --- a/pkg/handlers/primeapiv3/mto_shipment.go +++ b/pkg/handlers/primeapiv3/mto_shipment.go @@ -207,7 +207,7 @@ func (h UpdateMTOShipmentHandler) Handle(params mtoshipmentops.UpdateMTOShipment mtoShipment.ShipmentType = dbShipment.ShipmentType appCtx.Logger().Info("primeapi.UpdateMTOShipmentHandler info", zap.String("pointOfContact", params.Body.PointOfContact)) - mtoShipment, err = h.ShipmentUpdater.UpdateShipment(appCtx, mtoShipment, params.IfMatch, "prime-v3", h.planner) + mtoShipment, err = h.ShipmentUpdater.UpdateShipment(appCtx, mtoShipment, params.IfMatch, "prime-v3") if err != nil { appCtx.Logger().Error("primeapi.UpdateMTOShipmentHandler error", zap.Error(err)) switch e := err.(type) { diff --git a/pkg/services/mocks/ShipmentUpdater.go b/pkg/services/mocks/ShipmentUpdater.go index 83e0f17d197..94bc241b543 100644 --- a/pkg/services/mocks/ShipmentUpdater.go +++ b/pkg/services/mocks/ShipmentUpdater.go @@ -7,8 +7,6 @@ import ( appcontext "github.com/transcom/mymove/pkg/appcontext" models "github.com/transcom/mymove/pkg/models" - - route "github.com/transcom/mymove/pkg/route" ) // ShipmentUpdater is an autogenerated mock type for the ShipmentUpdater type @@ -16,9 +14,9 @@ type ShipmentUpdater struct { mock.Mock } -// UpdateShipment provides a mock function with given fields: appCtx, shipment, eTag, api, planner -func (_m *ShipmentUpdater) UpdateShipment(appCtx appcontext.AppContext, shipment *models.MTOShipment, eTag string, api string, planner route.Planner) (*models.MTOShipment, error) { - ret := _m.Called(appCtx, shipment, eTag, api, planner) +// UpdateShipment provides a mock function with given fields: appCtx, shipment, eTag, api +func (_m *ShipmentUpdater) UpdateShipment(appCtx appcontext.AppContext, shipment *models.MTOShipment, eTag string, api string) (*models.MTOShipment, error) { + ret := _m.Called(appCtx, shipment, eTag, api) if len(ret) == 0 { panic("no return value specified for UpdateShipment") @@ -26,19 +24,19 @@ func (_m *ShipmentUpdater) UpdateShipment(appCtx appcontext.AppContext, shipment var r0 *models.MTOShipment var r1 error - if rf, ok := ret.Get(0).(func(appcontext.AppContext, *models.MTOShipment, string, string, route.Planner) (*models.MTOShipment, error)); ok { - return rf(appCtx, shipment, eTag, api, planner) + if rf, ok := ret.Get(0).(func(appcontext.AppContext, *models.MTOShipment, string, string) (*models.MTOShipment, error)); ok { + return rf(appCtx, shipment, eTag, api) } - if rf, ok := ret.Get(0).(func(appcontext.AppContext, *models.MTOShipment, string, string, route.Planner) *models.MTOShipment); ok { - r0 = rf(appCtx, shipment, eTag, api, planner) + if rf, ok := ret.Get(0).(func(appcontext.AppContext, *models.MTOShipment, string, string) *models.MTOShipment); ok { + r0 = rf(appCtx, shipment, eTag, api) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*models.MTOShipment) } } - if rf, ok := ret.Get(1).(func(appcontext.AppContext, *models.MTOShipment, string, string, route.Planner) error); ok { - r1 = rf(appCtx, shipment, eTag, api, planner) + if rf, ok := ret.Get(1).(func(appcontext.AppContext, *models.MTOShipment, string, string) error); ok { + r1 = rf(appCtx, shipment, eTag, api) } else { r1 = ret.Error(1) } diff --git a/pkg/services/mto_service_item/mto_service_item_creator.go b/pkg/services/mto_service_item/mto_service_item_creator.go index 66d5b16595b..05ccd8c7929 100644 --- a/pkg/services/mto_service_item/mto_service_item_creator.go +++ b/pkg/services/mto_service_item/mto_service_item_creator.go @@ -39,6 +39,7 @@ type mtoServiceItemCreator struct { fuelSurchargePricer services.FuelSurchargePricer } +// FindEstimatedPrice finds the estimated price for a service item func (o *mtoServiceItemCreator) FindEstimatedPrice(appCtx appcontext.AppContext, serviceItem *models.MTOServiceItem, mtoShipment models.MTOShipment) (unit.Cents, error) { if serviceItem.ReService.Code == models.ReServiceCodeDOP || serviceItem.ReService.Code == models.ReServiceCodeDPK || @@ -55,11 +56,8 @@ func (o *mtoServiceItemCreator) FindEstimatedPrice(appCtx appcontext.AppContext, requestedPickupDate := *mtoShipment.RequestedPickupDate currTime := time.Now() var distance int - primeEstimatedWeight := mtoShipment.PrimeEstimatedWeight - if mtoShipment.ShipmentType == models.MTOShipmentTypeHHGOutOfNTS { - newWeight := int(primeEstimatedWeight.Float64() * 1.1) - primeEstimatedWeight = (*unit.Pound)(&newWeight) - } + + adjustedWeight := GetAdjustedWeight(*mtoShipment.PrimeEstimatedWeight, mtoShipment.ShipmentType == models.MTOShipmentTypeUnaccompaniedBaggage) contractCode, err := FetchContractCode(appCtx, currTime) if err != nil { @@ -78,7 +76,7 @@ func (o *mtoServiceItemCreator) FindEstimatedPrice(appCtx appcontext.AppContext, return 0, err } - price, _, err = o.originPricer.Price(appCtx, contractCode, requestedPickupDate, *primeEstimatedWeight, domesticServiceArea.ServiceArea, isPPM) + price, _, err = o.originPricer.Price(appCtx, contractCode, requestedPickupDate, *adjustedWeight, domesticServiceArea.ServiceArea, isPPM) if err != nil { return 0, err } @@ -91,7 +89,7 @@ func (o *mtoServiceItemCreator) FindEstimatedPrice(appCtx appcontext.AppContext, servicesScheduleOrigin := domesticServiceArea.ServicesSchedule - price, _, err = o.packPricer.Price(appCtx, contractCode, requestedPickupDate, *primeEstimatedWeight, servicesScheduleOrigin, isPPM) + price, _, err = o.packPricer.Price(appCtx, contractCode, requestedPickupDate, *adjustedWeight, servicesScheduleOrigin, isPPM) if err != nil { return 0, err } @@ -106,7 +104,7 @@ func (o *mtoServiceItemCreator) FindEstimatedPrice(appCtx appcontext.AppContext, } } - price, _, err = o.destinationPricer.Price(appCtx, contractCode, requestedPickupDate, *primeEstimatedWeight, domesticServiceArea.ServiceArea, isPPM) + price, _, err = o.destinationPricer.Price(appCtx, contractCode, requestedPickupDate, *adjustedWeight, domesticServiceArea.ServiceArea, isPPM) if err != nil { return 0, err } @@ -119,7 +117,7 @@ func (o *mtoServiceItemCreator) FindEstimatedPrice(appCtx appcontext.AppContext, serviceScheduleDestination := domesticServiceArea.ServicesSchedule - price, _, err = o.unpackPricer.Price(appCtx, contractCode, requestedPickupDate, *primeEstimatedWeight, serviceScheduleDestination, isPPM) + price, _, err = o.unpackPricer.Price(appCtx, contractCode, requestedPickupDate, *adjustedWeight, serviceScheduleDestination, isPPM) if err != nil { return 0, err } @@ -137,7 +135,7 @@ func (o *mtoServiceItemCreator) FindEstimatedPrice(appCtx appcontext.AppContext, return 0, err } } - price, _, err = o.linehaulPricer.Price(appCtx, contractCode, requestedPickupDate, unit.Miles(distance), *primeEstimatedWeight, domesticServiceArea.ServiceArea, isPPM) + price, _, err = o.linehaulPricer.Price(appCtx, contractCode, requestedPickupDate, unit.Miles(distance), *adjustedWeight, domesticServiceArea.ServiceArea, isPPM) if err != nil { return 0, err } @@ -153,7 +151,7 @@ func (o *mtoServiceItemCreator) FindEstimatedPrice(appCtx appcontext.AppContext, return 0, err } } - price, _, err = o.shorthaulPricer.Price(appCtx, contractCode, requestedPickupDate, unit.Miles(distance), *primeEstimatedWeight, domesticServiceArea.ServiceArea) + price, _, err = o.shorthaulPricer.Price(appCtx, contractCode, requestedPickupDate, unit.Miles(distance), *adjustedWeight, domesticServiceArea.ServiceArea) if err != nil { return 0, err } @@ -177,7 +175,7 @@ func (o *mtoServiceItemCreator) FindEstimatedPrice(appCtx appcontext.AppContext, } } - fscWeightBasedDistanceMultiplier, err := LookupFSCWeightBasedDistanceMultiplier(appCtx, *primeEstimatedWeight) + fscWeightBasedDistanceMultiplier, err := LookupFSCWeightBasedDistanceMultiplier(appCtx, *adjustedWeight) if err != nil { return 0, err } @@ -189,7 +187,7 @@ func (o *mtoServiceItemCreator) FindEstimatedPrice(appCtx appcontext.AppContext, if err != nil { return 0, err } - price, _, err = o.fuelSurchargePricer.Price(appCtx, pickupDateForFSC, unit.Miles(distance), *primeEstimatedWeight, fscWeightBasedDistanceMultiplierFloat, eiaFuelPrice, isPPM) + price, _, err = o.fuelSurchargePricer.Price(appCtx, pickupDateForFSC, unit.Miles(distance), *adjustedWeight, fscWeightBasedDistanceMultiplierFloat, eiaFuelPrice, isPPM) if err != nil { return 0, err } @@ -608,6 +606,7 @@ func (o *mtoServiceItemCreator) CreateMTOServiceItem(appCtx appcontext.AppContex // if estimated weight for shipment provided by the prime, calculate the estimated prices for // DLH, DPK, DOP, DDP, DUPK + // NTS-release requested pickup dates are for handle out, their pricing is handled differently as their locations are based on storage facilities, not pickup locations if mtoShipment.PrimeEstimatedWeight != nil && mtoShipment.RequestedPickupDate != nil { serviceItemEstimatedPrice, err := o.FindEstimatedPrice(appCtx, serviceItem, mtoShipment) if serviceItemEstimatedPrice != 0 && err == nil { @@ -940,3 +939,22 @@ func (o *mtoServiceItemCreator) validateFirstDaySITServiceItem(appCtx appcontext return &extraServiceItems, nil } + +// Get Adjusted weight for pricing. Returns the weight at 110% or the minimum billable weight whichever is higher, unless it's 0 +func GetAdjustedWeight(incomingWeight unit.Pound, isUB bool) *unit.Pound { + // minimum weight billed by GHC is 500 lbs unless it's Unaccompanied Baggage (UB) + minimumBilledWeight := unit.Pound(500) + if isUB { + minimumBilledWeight = unit.Pound(300) + } + + // add 110% modifier to billable weight + newWeight := (int(incomingWeight.Float64() * 1.1)) + adjustedWeight := (*unit.Pound)(&newWeight) + + // if the adjusted weight is less than the minimum billable weight but is nonzero, set it to the minimum weight billed + if *adjustedWeight < minimumBilledWeight && *adjustedWeight > 0 { + *adjustedWeight = minimumBilledWeight + } + return adjustedWeight +} diff --git a/pkg/services/mto_service_item/mto_service_item_creator_test.go b/pkg/services/mto_service_item/mto_service_item_creator_test.go index 16a141a9693..a939b438d9e 100644 --- a/pkg/services/mto_service_item/mto_service_item_creator_test.go +++ b/pkg/services/mto_service_item/mto_service_item_creator_test.go @@ -2108,22 +2108,22 @@ func (suite *MTOServiceItemServiceSuite) TestPriceEstimator() { creator := NewMTOServiceItemCreator(planner, builder, moveRouter, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticPackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticShorthaulPricer(), ghcrateengine.NewDomesticOriginPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) dopEstimatedPriceInCents, _ := creator.FindEstimatedPrice(suite.AppContextForTest(), &serviceItemDOP, shipment) - suite.Equal(unit.Cents(61080), dopEstimatedPriceInCents) + suite.Equal(unit.Cents(67188), dopEstimatedPriceInCents) dpkEstimatedPriceInCents, _ := creator.FindEstimatedPrice(suite.AppContextForTest(), &serviceItemDPK, shipment) - suite.Equal(unit.Cents(540000), dpkEstimatedPriceInCents) + suite.Equal(unit.Cents(594000), dpkEstimatedPriceInCents) ddpEstimatedPriceInCents, _ := creator.FindEstimatedPrice(suite.AppContextForTest(), &serviceItemDDP, shipment) - suite.Equal(unit.Cents(42240), ddpEstimatedPriceInCents) + suite.Equal(unit.Cents(46464), ddpEstimatedPriceInCents) dupkEstimatedPriceInCents, _ := creator.FindEstimatedPrice(suite.AppContextForTest(), &serviceItemDUPK, shipment) - suite.Equal(unit.Cents(43860), dupkEstimatedPriceInCents) + suite.Equal(unit.Cents(48246), dupkEstimatedPriceInCents) dlhEstimatedPriceInCents, _ := creator.FindEstimatedPrice(suite.AppContextForTest(), &serviceItemDLH, shipment) - suite.Equal(unit.Cents(12381600), dlhEstimatedPriceInCents) + suite.Equal(unit.Cents(13619760), dlhEstimatedPriceInCents) dshEstimatedPriceInCents, _ := creator.FindEstimatedPrice(suite.AppContextForTest(), &serviceItemDSH, shipment) - suite.Equal(unit.Cents(10080000), dshEstimatedPriceInCents) + suite.Equal(unit.Cents(11088000), dshEstimatedPriceInCents) fscEstimatedPriceInCents, _ := creator.FindEstimatedPrice(suite.AppContextForTest(), &serviceItemFSC, shipment) suite.Equal(unit.Cents(-168), fscEstimatedPriceInCents) @@ -2429,3 +2429,66 @@ func (suite *MTOServiceItemServiceSuite) TestPriceEstimator() { }) } +func (suite *MTOServiceItemServiceSuite) TestGetAdjustedWeight() { + suite.Run("If no weight is provided", func() { + var incomingWeight unit.Pound + adjustedWeight := GetAdjustedWeight(incomingWeight, false) + suite.Equal(unit.Pound(0), *adjustedWeight) + }) + suite.Run("If a weight of 0 is provided", func() { + incomingWeight := unit.Pound(0) + adjustedWeight := GetAdjustedWeight(incomingWeight, false) + suite.Equal(unit.Pound(0), *adjustedWeight) + }) + suite.Run("If weight of 100 is provided", func() { + incomingWeight := unit.Pound(100) + adjustedWeight := GetAdjustedWeight(incomingWeight, false) + suite.Equal(unit.Pound(500), *adjustedWeight) + }) + suite.Run("If weight of 454 is provided", func() { + incomingWeight := unit.Pound(454) + adjustedWeight := GetAdjustedWeight(incomingWeight, false) + suite.Equal(unit.Pound(500), *adjustedWeight) + }) + suite.Run("If weight of 456 is provided", func() { + incomingWeight := unit.Pound(456) + adjustedWeight := GetAdjustedWeight(incomingWeight, false) + suite.Equal(unit.Pound(501), *adjustedWeight) + }) + suite.Run("If weight of 1000 is provided", func() { + incomingWeight := unit.Pound(1000) + adjustedWeight := GetAdjustedWeight(incomingWeight, false) + suite.Equal(unit.Pound(1100), *adjustedWeight) + }) + + suite.Run("If no weight is provided UB", func() { + var incomingWeight unit.Pound + adjustedWeight := GetAdjustedWeight(incomingWeight, true) + suite.Equal(unit.Pound(0), *adjustedWeight) + }) + suite.Run("If a weight of 0 is provided UB", func() { + incomingWeight := unit.Pound(0) + adjustedWeight := GetAdjustedWeight(incomingWeight, true) + suite.Equal(unit.Pound(0), *adjustedWeight) + }) + suite.Run("If weight of 100 is provided UB", func() { + incomingWeight := unit.Pound(100) + adjustedWeight := GetAdjustedWeight(incomingWeight, true) + suite.Equal(unit.Pound(300), *adjustedWeight) + }) + suite.Run("If weight of 272 is provided UB", func() { + incomingWeight := unit.Pound(272) + adjustedWeight := GetAdjustedWeight(incomingWeight, true) + suite.Equal(unit.Pound(300), *adjustedWeight) + }) + suite.Run("If weight of 274 is provided UB", func() { + incomingWeight := unit.Pound(274) + adjustedWeight := GetAdjustedWeight(incomingWeight, true) + suite.Equal(unit.Pound(301), *adjustedWeight) + }) + suite.Run("If weight of 1000 is provided UB", func() { + incomingWeight := unit.Pound(1000) + adjustedWeight := GetAdjustedWeight(incomingWeight, true) + suite.Equal(unit.Pound(1100), *adjustedWeight) + }) +} diff --git a/pkg/services/mto_shipment/mto_shipment_updater.go b/pkg/services/mto_shipment/mto_shipment_updater.go index a4ec081df2a..1f3696da161 100644 --- a/pkg/services/mto_shipment/mto_shipment_updater.go +++ b/pkg/services/mto_shipment/mto_shipment_updater.go @@ -1136,7 +1136,6 @@ func reServiceCodesForShipment(shipment models.MTOShipment) []models.ReServiceCo models.ReServiceCodeDPK, models.ReServiceCodeDUPK, } - case models.MTOShipmentTypeHHGIntoNTS: // Need to create: Dom Linehaul, Fuel Surcharge, Dom Origin Price, Dom Destination Price, Dom NTS Packing return []models.ReServiceCode{ diff --git a/pkg/services/orchestrators/shipment/shipment_updater.go b/pkg/services/orchestrators/shipment/shipment_updater.go index 53a20840e5e..d8d82af8e90 100644 --- a/pkg/services/orchestrators/shipment/shipment_updater.go +++ b/pkg/services/orchestrators/shipment/shipment_updater.go @@ -5,7 +5,6 @@ import ( "github.com/transcom/mymove/pkg/appcontext" "github.com/transcom/mymove/pkg/models" - "github.com/transcom/mymove/pkg/route" "github.com/transcom/mymove/pkg/services" "github.com/transcom/mymove/pkg/unit" ) @@ -33,7 +32,7 @@ func NewShipmentUpdater(mtoShipmentUpdater services.MTOShipmentUpdater, ppmShipm } // UpdateShipment updates a shipment, taking into account different shipment types and their needs. -func (s *shipmentUpdater) UpdateShipment(appCtx appcontext.AppContext, shipment *models.MTOShipment, eTag string, api string, planner route.Planner) (*models.MTOShipment, error) { +func (s *shipmentUpdater) UpdateShipment(appCtx appcontext.AppContext, shipment *models.MTOShipment, eTag string, api string) (*models.MTOShipment, error) { if err := validateShipment(appCtx, *shipment, s.checks...); err != nil { return nil, err } @@ -47,24 +46,10 @@ func (s *shipmentUpdater) UpdateShipment(appCtx appcontext.AppContext, shipment return err } - if mtoShipment != nil && planner != nil { - if mtoShipment.ShipmentType != models.MTOShipmentTypePPM && (shipment.PrimeEstimatedWeight != nil || mtoShipment.PrimeEstimatedWeight != nil) && mtoShipment.Status == models.MTOShipmentStatusApproved { - for index, serviceItem := range mtoShipment.MTOServiceItems { - var estimatedWeightToUse unit.Pound - if shipment.PrimeEstimatedWeight != nil { - estimatedWeightToUse = *shipment.PrimeEstimatedWeight - } else { - estimatedWeightToUse = *mtoShipment.PrimeEstimatedWeight - } - mtoShipment.MTOServiceItems[index].EstimatedWeight = &estimatedWeightToUse - serviceItemEstimatedPrice, err := s.mtoServiceItemCreator.FindEstimatedPrice(appCtx, &serviceItem, *mtoShipment) - if serviceItemEstimatedPrice != 0 && err == nil { - mtoShipment.MTOServiceItems[index].PricingEstimate = &serviceItemEstimatedPrice - } - if err != nil { - return err - } - } + if mtoShipment != nil && (mtoShipment.ShipmentType != models.MTOShipmentTypePPM) && (shipment.PrimeEstimatedWeight != nil || mtoShipment.PrimeEstimatedWeight != nil) && mtoShipment.Status == models.MTOShipmentStatusApproved { + mtoShipment, err = AddPricingEstimatesToMTOServiceItems(appCtx, *s, mtoShipment, shipment) + if err != nil { + return err } } @@ -154,3 +139,30 @@ func (s *shipmentUpdater) UpdateShipment(appCtx appcontext.AppContext, shipment return mtoShipment, nil } + +func AddPricingEstimatesToMTOServiceItems(appCtx appcontext.AppContext, shipmentUpdater shipmentUpdater, mtoShipment *models.MTOShipment, shipmentDelta *models.MTOShipment) (*models.MTOShipment, error) { + mtoShipmentCopy := mtoShipment + + for index, serviceItem := range mtoShipmentCopy.MTOServiceItems { + var estimatedWeightToUse unit.Pound + if shipmentDelta.PrimeEstimatedWeight != nil { + estimatedWeightToUse = *shipmentDelta.PrimeEstimatedWeight + } else { + estimatedWeightToUse = *mtoShipmentCopy.PrimeEstimatedWeight + } + + serviceItemEstimatedPrice, err := shipmentUpdater.mtoServiceItemCreator.FindEstimatedPrice(appCtx, &serviceItem, *mtoShipment) + + // store actual captured weight + mtoShipmentCopy.MTOServiceItems[index].EstimatedWeight = &estimatedWeightToUse + mtoShipmentCopy.PrimeEstimatedWeight = &estimatedWeightToUse + + if serviceItemEstimatedPrice != 0 && err == nil { + mtoShipmentCopy.MTOServiceItems[index].PricingEstimate = &serviceItemEstimatedPrice + } + if err != nil { + return mtoShipmentCopy, err + } + } + return mtoShipmentCopy, nil +} diff --git a/pkg/services/orchestrators/shipment/shipment_updater_test.go b/pkg/services/orchestrators/shipment/shipment_updater_test.go index 34874136525..3b9fec24cac 100644 --- a/pkg/services/orchestrators/shipment/shipment_updater_test.go +++ b/pkg/services/orchestrators/shipment/shipment_updater_test.go @@ -2,6 +2,7 @@ package shipment import ( "fmt" + "time" "github.com/gofrs/uuid" "github.com/stretchr/testify/mock" @@ -18,6 +19,7 @@ import ( moveservices "github.com/transcom/mymove/pkg/services/move" mtoserviceitem "github.com/transcom/mymove/pkg/services/mto_service_item" "github.com/transcom/mymove/pkg/services/query" + "github.com/transcom/mymove/pkg/testdatagen" "github.com/transcom/mymove/pkg/unit" ) @@ -33,6 +35,7 @@ func (suite *ShipmentSuite) TestUpdateShipment() { type subtestDataObjects struct { mockMTOShipmentUpdater *mocks.MTOShipmentUpdater + mockMtoServiceItemCreator *mocks.MTOServiceItemCreator mockPPMShipmentUpdater *mocks.PPMShipmentUpdater mockBoatShipmentUpdater *mocks.BoatShipmentUpdater mockMobileHomeShipmentUpdater *mocks.MobileHomeShipmentUpdater @@ -50,6 +53,9 @@ func (suite *ShipmentSuite) TestUpdateShipment() { mockMTOShipmentUpdater := mocks.MTOShipmentUpdater{} subtestData.mockMTOShipmentUpdater = &mockMTOShipmentUpdater + mockMtoServiceItemCreator := mocks.MTOServiceItemCreator{} + subtestData.mockMtoServiceItemCreator = &mockMtoServiceItemCreator + mockPPMShipmentUpdater := mocks.PPMShipmentUpdater{} subtestData.mockPPMShipmentUpdater = &mockPPMShipmentUpdater @@ -186,6 +192,26 @@ func (suite *ShipmentSuite) TestUpdateShipment() { return subtestData } + makeServiceItemSubtestData := func() (subtestData subtestDataObjects) { + mockMTOShipmentUpdater := mocks.MTOShipmentUpdater{} + subtestData.mockMTOShipmentUpdater = &mockMTOShipmentUpdater + + mockMtoServiceItemCreator := mocks.MTOServiceItemCreator{} + subtestData.mockMtoServiceItemCreator = &mockMtoServiceItemCreator + + mockPPMShipmentUpdater := mocks.PPMShipmentUpdater{} + subtestData.mockPPMShipmentUpdater = &mockPPMShipmentUpdater + + mockBoatShipmentUpdater := mocks.BoatShipmentUpdater{} + subtestData.mockBoatShipmentUpdater = &mockBoatShipmentUpdater + + mockMobileHomeShipmentUpdater := mocks.MobileHomeShipmentUpdater{} + subtestData.mockMobileHomeShipmentUpdater = &mockMobileHomeShipmentUpdater + + subtestData.shipmentUpdaterOrchestrator = NewShipmentUpdater(subtestData.mockMTOShipmentUpdater, subtestData.mockPPMShipmentUpdater, subtestData.mockBoatShipmentUpdater, subtestData.mockMobileHomeShipmentUpdater, subtestData.mockMtoServiceItemCreator) + + return subtestData + } suite.Run("Returns an InvalidInputError if there is an error with the shipment info that was input", func() { appCtx := suite.AppContextForTest() @@ -197,7 +223,7 @@ func (suite *ShipmentSuite) TestUpdateShipment() { // Set invalid data, can't pass in blank to the generator above (it'll default to HHG if blank) so we're setting it afterward. shipment.ShipmentType = "" - updatedShipment, err := subtestData.shipmentUpdaterOrchestrator.UpdateShipment(appCtx, &shipment, etag.GenerateEtag(shipment.UpdatedAt), "test", nil) + updatedShipment, err := subtestData.shipmentUpdaterOrchestrator.UpdateShipment(appCtx, &shipment, etag.GenerateEtag(shipment.UpdatedAt), "test") suite.Nil(updatedShipment) @@ -245,7 +271,7 @@ func (suite *ShipmentSuite) TestUpdateShipment() { // Need to start a transaction so we can assert the call with the correct appCtx err := appCtx.NewTransaction(func(txAppCtx appcontext.AppContext) error { - mtoShipment, err := subtestData.shipmentUpdaterOrchestrator.UpdateShipment(txAppCtx, &shipment, eTag, "test", nil) + mtoShipment, err := subtestData.shipmentUpdaterOrchestrator.UpdateShipment(txAppCtx, &shipment, eTag, "test") suite.NoError(err) suite.NotNil(mtoShipment) @@ -308,7 +334,7 @@ func (suite *ShipmentSuite) TestUpdateShipment() { shipment.PPMShipment.AdvanceAmountReceived = models.CentPointer(unit.Cents(55000)) shipment.PPMShipment.HasReceivedAdvance = models.BoolPointer(true) - mtoShipment, err := subtestData.shipmentUpdaterOrchestrator.UpdateShipment(appCtx, &shipment, etag.GenerateEtag(shipment.UpdatedAt), "test", nil) + mtoShipment, err := subtestData.shipmentUpdaterOrchestrator.UpdateShipment(appCtx, &shipment, etag.GenerateEtag(shipment.UpdatedAt), "test") suite.NoError(err) suite.NotNil(mtoShipment) @@ -347,7 +373,7 @@ func (suite *ShipmentSuite) TestUpdateShipment() { shipment.BoatShipment.LengthInInches = models.IntPointer(20) shipment.BoatShipment.HasTrailer = models.BoolPointer(false) - mtoShipment, err := subtestData.shipmentUpdaterOrchestrator.UpdateShipment(appCtx, &shipment, etag.GenerateEtag(shipment.UpdatedAt), "test", nil) + mtoShipment, err := subtestData.shipmentUpdaterOrchestrator.UpdateShipment(appCtx, &shipment, etag.GenerateEtag(shipment.UpdatedAt), "test") suite.NoError(err) @@ -426,7 +452,7 @@ func (suite *ShipmentSuite) TestUpdateShipment() { }, nil) } - mtoShipment, err := subtestData.shipmentUpdaterOrchestrator.UpdateShipment(appCtx, &shipment, etag.GenerateEtag(shipment.UpdatedAt), "test", nil) + mtoShipment, err := subtestData.shipmentUpdaterOrchestrator.UpdateShipment(appCtx, &shipment, etag.GenerateEtag(shipment.UpdatedAt), "test") suite.Nil(mtoShipment) @@ -450,7 +476,7 @@ func (suite *ShipmentSuite) TestUpdateShipment() { eTag := etag.GenerateEtag(shipment.UpdatedAt) - mtoShipment, err := subtestData.shipmentUpdaterOrchestrator.UpdateShipment(appCtx, &shipment, eTag, "test", nil) + mtoShipment, err := subtestData.shipmentUpdaterOrchestrator.UpdateShipment(appCtx, &shipment, eTag, "test") suite.Nil(mtoShipment) @@ -474,4 +500,133 @@ func (suite *ShipmentSuite) TestUpdateShipment() { mock.AnythingOfType("uuid.UUID"), ) }) + + suite.Run("Updating weight will update the estimated price of service items", func() { + appCtx := suite.AppContextForTest() + + subtestData := makeServiceItemSubtestData() + + estimatedWeight := unit.Pound(2000) + pickupAddress := factory.BuildAddress(suite.DB(), nil, []factory.Trait{factory.GetTraitAddress2}) + deliveryAddress := factory.BuildAddress(suite.DB(), nil, []factory.Trait{factory.GetTraitAddress3}) + + shipment := factory.BuildMTOShipment(appCtx.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + ID: uuid.Must(uuid.FromString("a5e95c1d-97c3-4f79-8097-c12dd2557ac7")), + Status: models.MTOShipmentStatusApproved, + ShipmentType: models.MTOShipmentTypeHHG, + PrimeEstimatedWeight: &estimatedWeight, + }, + }, + { + Model: pickupAddress, + LinkOnly: true, + Type: &factory.Addresses.PickupAddress, + }, + { + Model: deliveryAddress, + LinkOnly: true, + Type: &factory.Addresses.DeliveryAddress, + }, + }, nil) + + reServiceCodeFSC := factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeFSC) + + startDate := time.Now().AddDate(-1, 0, 0) + endDate := startDate.AddDate(1, 1, 1) + reason := "lorem ipsum" + + testdatagen.FetchOrMakeReContract(suite.DB(), testdatagen.Assertions{}) + testdatagen.FetchOrMakeReContractYear(suite.DB(), + testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + Name: "Test Contract Year", + EscalationCompounded: 1.125, + StartDate: startDate, + EndDate: endDate, + }, + }) + + testdatagen.FetchOrMakeGHCDieselFuelPrice(suite.DB(), testdatagen.Assertions{ + GHCDieselFuelPrice: models.GHCDieselFuelPrice{ + FuelPriceInMillicents: unit.Millicents(281400), + PublicationDate: time.Date(2020, time.March, 9, 0, 0, 0, 0, time.UTC), + EffectiveDate: time.Date(2020, time.March, 10, 0, 0, 0, 0, time.UTC), + EndDate: time.Date(2025, time.March, 17, 0, 0, 0, 0, time.UTC), + }, + }) + + actualPickupAddress := factory.BuildAddress(suite.DB(), nil, []factory.Trait{factory.GetTraitAddress2}) + requestedPickupDate := time.Date(2020, time.October, 24, 0, 0, 0, 0, time.UTC) + + serviceItemFSC := models.MTOServiceItem{ + MoveTaskOrder: shipment.MoveTaskOrder, + MoveTaskOrderID: shipment.MoveTaskOrderID, + MTOShipment: shipment, + MTOShipmentID: &shipment.ID, + ReService: reServiceCodeFSC, + Reason: &reason, + SITOriginHHGActualAddress: &actualPickupAddress, + Status: models.MTOServiceItemStatusSubmitted, + } + + shipment.MTOServiceItems = append(shipment.MTOServiceItems, serviceItemFSC) + suite.MustSave(&shipment) + + eTag := etag.GenerateEtag(shipment.UpdatedAt) + + subtestData.mockMtoServiceItemCreator.On("ZipTransitDistance", + mock.AnythingOfType("*appcontext.appContext"), + mock.Anything, + mock.Anything, + false, + false, + ).Return(800, nil) + + returnCents := unit.Cents(123) + + subtestData.mockMtoServiceItemCreator.On("FindEstimatedPrice", + mock.AnythingOfType("*appcontext.appContext"), + mock.Anything, + mock.Anything, + mock.Anything, + ).Return(returnCents, nil) + + subtestData.mockMTOShipmentUpdater. + On( + updateMTOShipmentMethodName, + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("*models.MTOShipment"), + mock.AnythingOfType("string"), + mock.AnythingOfType("string")). + Return( + &models.MTOShipment{ + ID: uuid.Must(uuid.FromString("a5e95c1d-97c3-4f79-8097-c12dd2557ac7")), + Status: models.MTOShipmentStatusApproved, + ShipmentType: models.MTOShipmentTypeHHG, + PrimeEstimatedWeight: &estimatedWeight, + RequestedPickupDate: &requestedPickupDate, + MTOServiceItems: models.MTOServiceItems{serviceItemFSC}, + PickupAddress: &pickupAddress, + DestinationAddress: &deliveryAddress, + }, nil) + + // Need to start a transaction so we can assert the call with the correct appCtx + err := appCtx.NewTransaction(func(txAppCtx appcontext.AppContext) error { + mtoShipment, err := subtestData.shipmentUpdaterOrchestrator.UpdateShipment(txAppCtx, &shipment, eTag, "test") + + suite.NoError(err) + suite.NotNil(mtoShipment) + + expectedPrice := unit.Cents(123) + expectedWeight := unit.Pound(2000) + suite.Equal(expectedWeight, *mtoShipment.MTOServiceItems[0].EstimatedWeight) + suite.Equal(expectedPrice, *mtoShipment.MTOServiceItems[0].PricingEstimate) + + return nil + }) + + suite.NoError(err) // just making golangci-lint happy + }) } diff --git a/pkg/services/shipment_orchestrator.go b/pkg/services/shipment_orchestrator.go index cef3a0c405a..69168e8f997 100644 --- a/pkg/services/shipment_orchestrator.go +++ b/pkg/services/shipment_orchestrator.go @@ -3,7 +3,6 @@ package services import ( "github.com/transcom/mymove/pkg/appcontext" "github.com/transcom/mymove/pkg/models" - "github.com/transcom/mymove/pkg/route" ) // ShipmentCreator creates a shipment, taking into account different shipment types and their needs. @@ -17,5 +16,5 @@ type ShipmentCreator interface { // //go:generate mockery --name ShipmentUpdater type ShipmentUpdater interface { - UpdateShipment(appCtx appcontext.AppContext, shipment *models.MTOShipment, eTag string, api string, planner route.Planner) (*models.MTOShipment, error) + UpdateShipment(appCtx appcontext.AppContext, shipment *models.MTOShipment, eTag string, api string) (*models.MTOShipment, error) }