diff --git a/pkg/factory/mto_shipment_factory.go b/pkg/factory/mto_shipment_factory.go index 1ac468cf681..c017a2f54cd 100644 --- a/pkg/factory/mto_shipment_factory.go +++ b/pkg/factory/mto_shipment_factory.go @@ -111,7 +111,11 @@ func buildMTOShipmentWithBuildType(db *pop.Connection, customs []Customization, if shipmentHasPickupDetails { newMTOShipment.RequestedPickupDate = models.TimePointer(time.Date(GHCTestYear, time.March, 15, 0, 0, 0, 0, time.UTC)) - newMTOShipment.ScheduledPickupDate = models.TimePointer(time.Date(GHCTestYear, time.March, 16, 0, 0, 0, 0, time.UTC)) + if cMtoShipment.ScheduledPickupDate == nil { + newMTOShipment.ScheduledPickupDate = models.TimePointer(time.Date(GHCTestYear, time.March, 16, 0, 0, 0, 0, time.UTC)) + } else { + newMTOShipment.ScheduledPickupDate = cMtoShipment.ScheduledPickupDate + } newMTOShipment.ActualPickupDate = models.TimePointer(time.Date(GHCTestYear, time.March, 16, 0, 0, 0, 0, time.UTC)) } @@ -208,8 +212,12 @@ func buildMTOShipmentWithBuildType(db *pop.Connection, customs []Customization, } if cMtoShipment.ScheduledPickupDate != nil { - requiredDeliveryDate := time.Date(GHCTestYear, time.April, 15, 0, 0, 0, 0, time.UTC) - newMTOShipment.RequiredDeliveryDate = &requiredDeliveryDate + if cMtoShipment.RequiredDeliveryDate != nil { + newMTOShipment.RequiredDeliveryDate = cMtoShipment.RequiredDeliveryDate + } else { + requiredDeliveryDate := time.Date(GHCTestYear, time.April, 15, 0, 0, 0, 0, time.UTC) + newMTOShipment.RequiredDeliveryDate = &requiredDeliveryDate + } } } diff --git a/pkg/handlers/primeapiv3/mto_shipment.go b/pkg/handlers/primeapiv3/mto_shipment.go index d2f6221ac9f..20f3b4733e8 100644 --- a/pkg/handlers/primeapiv3/mto_shipment.go +++ b/pkg/handlers/primeapiv3/mto_shipment.go @@ -225,7 +225,7 @@ func (h UpdateMTOShipmentHandler) Handle(params mtoshipmentops.UpdateMTOShipment payloads.ClientError(handlers.PreconditionErrMessage, err.Error(), h.GetTraceIDFromRequest(params.HTTPRequest))), err default: return mtoshipmentops.NewUpdateMTOShipmentInternalServerError().WithPayload( - payloads.InternalServerError(nil, h.GetTraceIDFromRequest(params.HTTPRequest))), err + payloads.InternalServerError(handlers.FmtString(err.Error()), h.GetTraceIDFromRequest(params.HTTPRequest))), err } } mtoShipmentPayload := payloads.MTOShipment(mtoShipment) diff --git a/pkg/models/re_contract_year.go b/pkg/models/re_contract_year.go index e24801e44a3..211f8012532 100644 --- a/pkg/models/re_contract_year.go +++ b/pkg/models/re_contract_year.go @@ -71,6 +71,20 @@ func (r *ReContractYear) Validate(_ *pop.Connection) (*validate.Errors, error) { ), nil } +// This function uses a raw query that calls db function get_contract to get the reContractYearId in respects to the requestedPickupDate +func FetchContractId(db *pop.Connection, requestedPickupDate time.Time) (uuid.UUID, error) { + if !requestedPickupDate.IsZero() { + var reContractYearId uuid.UUID + err := db.RawQuery("SELECT get_contract_id($1)", requestedPickupDate).First(&reContractYearId) + if err != nil { + return uuid.Nil, fmt.Errorf("error fetching contract year id for requested pickup date %s", requestedPickupDate) + } + + return reContractYearId, nil + } + return uuid.Nil, fmt.Errorf("error fetching contract ID - required parameters not provided") +} + func GetExpectedEscalationPriceContractsCount(contractYearName string) (ExpectedEscalationPriceContractsCount, error) { switch contractYearName { case BasePeriodYear1: diff --git a/pkg/models/re_contract_year_test.go b/pkg/models/re_contract_year_test.go index ad7b61403cc..5a29320d76f 100644 --- a/pkg/models/re_contract_year_test.go +++ b/pkg/models/re_contract_year_test.go @@ -6,6 +6,7 @@ import ( "github.com/gofrs/uuid" "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/testdatagen" ) func (suite *ModelSuite) TestReContractYearValidations() { @@ -52,3 +53,62 @@ func (suite *ModelSuite) TestReContractYearValidations() { suite.verifyValidationErrors(&badDatesReContractYear, expErrors) }) } + +func (suite *ModelSuite) TestReContractYearModel() { + suite.Run("test that FetchContractId returns the contractId given a requestedPickupDate", func() { + validReContractYear := testdatagen.MakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + Name: "Base Period Year 1", + StartDate: time.Date(2019, time.October, 1, 0, 0, 0, 0, time.UTC), + EndDate: time.Date(2020, time.September, 30, 0, 0, 0, 0, time.UTC), + Escalation: 1.03, + EscalationCompounded: 1.74, + }, + }) + + requestedPickupDate := time.Date(2019, time.October, 25, 0, 0, 0, 0, time.UTC) + contractYearId, err := models.FetchContractId(suite.DB(), requestedPickupDate) + + suite.Nil(err) + suite.NotNil(contractYearId) + suite.Equal(contractYearId, validReContractYear.ContractID) + }) + + suite.Run("test that FetchContractId returns error when no requestedPickupDate is given", func() { + testdatagen.MakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + Name: "Base Period Year 1", + StartDate: time.Date(2019, time.October, 1, 0, 0, 0, 0, time.UTC), + EndDate: time.Date(2020, time.September, 30, 0, 0, 0, 0, time.UTC), + Escalation: 1.03, + EscalationCompounded: 1.74, + }, + }) + + var time time.Time + contractYearId, err := models.FetchContractId(suite.DB(), time) + + suite.NotNil(err) + suite.Contains(err.Error(), "error fetching contract ID - required parameters not provided") + suite.Equal(contractYearId, uuid.Nil) + }) + + suite.Run("test that FetchContractId returns error when no contract is found for given requestedPickupDate", func() { + testdatagen.MakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + Name: "Base Period Year 1", + StartDate: time.Date(2019, time.October, 1, 0, 0, 0, 0, time.UTC), + EndDate: time.Date(2020, time.September, 30, 0, 0, 0, 0, time.UTC), + Escalation: 1.03, + EscalationCompounded: 1.74, + }, + }) + requestedPickupDate := time.Date(2019, time.September, 1, 0, 0, 0, 0, time.UTC) + + contractYearId, err := models.FetchContractId(suite.DB(), requestedPickupDate) + + suite.NotNil(err) + suite.Contains(err.Error(), "error fetching contract year id") + suite.Equal(contractYearId, uuid.Nil) + }) +} diff --git a/pkg/models/re_intl_transit_times.go b/pkg/models/re_intl_transit_times.go index b5652894efa..de44bb9c30a 100644 --- a/pkg/models/re_intl_transit_times.go +++ b/pkg/models/re_intl_transit_times.go @@ -3,7 +3,10 @@ package models import ( "time" + "github.com/gobuffalo/pop/v6" "github.com/gofrs/uuid" + + "github.com/transcom/mymove/pkg/apperror" ) type InternationalTransitTime struct { @@ -20,3 +23,17 @@ type InternationalTransitTime struct { func (InternationalTransitTime) TableName() string { return "re_intl_transit_times" } + +// fetch the re_intl_transit_time record from the db +func FetchInternationalTransitTime(db *pop.Connection, originRateAreaId uuid.UUID, destinationRateAreaId uuid.UUID) (InternationalTransitTime, error) { + var internationalTransitTime InternationalTransitTime + err := db. + Where("origin_rate_area_id = $1 and destination_rate_area_id = $2", originRateAreaId, destinationRateAreaId). + First(&internationalTransitTime) + + if err != nil { + return internationalTransitTime, apperror.NewQueryError("InternationalTransitTime", err, "could not look up intl transit time") + } + + return internationalTransitTime, nil +} diff --git a/pkg/models/re_intl_transit_times_test.go b/pkg/models/re_intl_transit_times_test.go new file mode 100644 index 00000000000..13e0ec45019 --- /dev/null +++ b/pkg/models/re_intl_transit_times_test.go @@ -0,0 +1,21 @@ +package models_test + +import ( + "github.com/gofrs/uuid" + + "github.com/transcom/mymove/pkg/models" +) + +func (suite *ModelSuite) TestIntlTransitTimesModel() { + suite.Run("test that FetchInternationalTransitTime returns the re_intl_transit_times given an originRateAreaId and a destinationRateAreaId", func() { + originRateAreaId := uuid.FromStringOrNil("6e802149-7e46-4d7a-ab57-6c4df832085d") + destinationRateAreaId := uuid.FromStringOrNil("c18e25f9-ec34-41ca-8c1b-05558c8d6364") + + fetchedIntlTransitTime, err := models.FetchInternationalTransitTime(suite.DB(), originRateAreaId, destinationRateAreaId) + + suite.Nil(err) + suite.NotNil(fetchedIntlTransitTime) + suite.Equal(originRateAreaId, fetchedIntlTransitTime.OriginRateAreaId) + suite.Equal(destinationRateAreaId, fetchedIntlTransitTime.DestinationRateAreaId) + }) +} diff --git a/pkg/models/re_rate_area_test.go b/pkg/models/re_rate_area_test.go index ab279418976..eaafcf6cbbd 100644 --- a/pkg/models/re_rate_area_test.go +++ b/pkg/models/re_rate_area_test.go @@ -42,8 +42,11 @@ func (suite *ModelSuite) TestFetchRateAreaID() { }) suite.Run("fail - receive error when not all values are provided", func() { - address := factory.BuildAddress(suite.DB(), nil, nil) - rateAreaId, err := models.FetchRateAreaID(suite.DB(), address.ID, nil, uuid.Nil) + var nilUuid uuid.UUID + nonNilUuid := uuid.Must(uuid.NewV4()) + contract := testdatagen.FetchOrMakeReContract(suite.DB(), testdatagen.Assertions{}) + rateAreaId, err := models.FetchRateAreaID(suite.DB(), nilUuid, &nonNilUuid, contract.ID) + suite.Equal(uuid.Nil, rateAreaId) suite.Error(err) }) diff --git a/pkg/services/mto_shipment/mto_shipment_updater.go b/pkg/services/mto_shipment/mto_shipment_updater.go index 7788c05d731..a9735170435 100644 --- a/pkg/services/mto_shipment/mto_shipment_updater.go +++ b/pkg/services/mto_shipment/mto_shipment_updater.go @@ -848,6 +848,16 @@ func (f *mtoShipmentUpdater) updateShipmentRecord(appCtx appcontext.AppContext, newShipment = models.DetermineShipmentMarketCode(newShipment) } + // RDD for UB shipments only need the pick up date, shipment origin address and destination address to determine required delivery date + if newShipment.ScheduledPickupDate != nil && !newShipment.ScheduledPickupDate.IsZero() && newShipment.ShipmentType == models.MTOShipmentTypeUnaccompaniedBaggage { + calculatedRDD, err := CalculateRequiredDeliveryDateForInternationalShipment(appCtx, *newShipment.PickupAddress, *newShipment.DestinationAddress, *newShipment.ScheduledPickupDate, newShipment.ShipmentType) + if err != nil { + return err + } + + newShipment.RequiredDeliveryDate = &calculatedRDD + } + if err := txnAppCtx.DB().Update(newShipment); err != nil { return err } @@ -1042,6 +1052,7 @@ func (o *mtoShipmentStatusUpdater) createShipmentServiceItems(appCtx appcontext. func (o *mtoShipmentStatusUpdater) setRequiredDeliveryDate(appCtx appcontext.AppContext, shipment *models.MTOShipment) error { if shipment.ScheduledPickupDate != nil && shipment.RequiredDeliveryDate == nil && + shipment.ShipmentType != models.MTOShipmentTypeUnaccompaniedBaggage && (shipment.PrimeEstimatedWeight != nil || shipment.NTSRecordedWeight != nil) { var pickupLocation *models.Address @@ -1081,6 +1092,13 @@ func (o *mtoShipmentStatusUpdater) setRequiredDeliveryDate(appCtx appcontext.App } shipment.RequiredDeliveryDate = requiredDeliveryDate + } else if shipment.ShipmentType == models.MTOShipmentTypeUnaccompaniedBaggage && shipment.ScheduledPickupDate != nil && !shipment.ScheduledDeliveryDate.IsZero() { + requiredDeliveryDate, calcErr := CalculateRequiredDeliveryDateForInternationalShipment(appCtx, *shipment.PickupAddress, *shipment.DestinationAddress, *shipment.ScheduledPickupDate, shipment.ShipmentType) + if calcErr != nil { + return calcErr + } + + shipment.RequiredDeliveryDate = &requiredDeliveryDate } return nil @@ -1273,6 +1291,41 @@ func CalculateRequiredDeliveryDate(appCtx appcontext.AppContext, planner route.P return &requiredDeliveryDate, nil } +// CalculateRequiredDeliveryDateForInternationalShipment function is used to get the Required delivery Date of a UB shipment by finding the re_intl_transit_time using the origin and destination address rate areas. +// The transit time is then added to the day after the pickup date then that date is used as the required delivery date for the UB shipment. +func CalculateRequiredDeliveryDateForInternationalShipment(appCtx appcontext.AppContext, pickupAddress models.Address, destinationAddress models.Address, pickupDate time.Time, shipmentType models.MTOShipmentType) (time.Time, error) { + + // Transit times does not include the pickup date. Setting the required delivery date to the day after pickup date + rdd := pickupDate.AddDate(0, 0, 1) + + // get the contract id + contractID, err := models.FetchContractId(appCtx.DB(), pickupDate) + if err != nil { + return rdd, err + } + + // get the rate area id for the origin address + originRateAreaID, err := models.FetchRateAreaID(appCtx.DB(), pickupAddress.ID, nil, contractID) + if err != nil { + return rdd, err + } + + // get the rate area id for the destination address + destRateAreaID, err := models.FetchRateAreaID(appCtx.DB(), destinationAddress.ID, nil, contractID) + if err != nil { + return rdd, err + } + + // lookup the intl transit time + internationalTransitTime, err := models.FetchInternationalTransitTime(appCtx.DB(), originRateAreaID, destRateAreaID) + if err != nil { + return rdd, err + } + + // rdd plus the intl ub transit time + return rdd.AddDate(0, 0, *internationalTransitTime.UbTransitTime), nil +} + // This private function is used to generically construct service items when shipments are approved. func constructMTOServiceItemModels(shipmentID uuid.UUID, mtoID uuid.UUID, reServiceCodes []models.ReServiceCode) models.MTOServiceItems { serviceItems := make(models.MTOServiceItems, len(reServiceCodes)) diff --git a/pkg/services/mto_shipment/mto_shipment_updater_test.go b/pkg/services/mto_shipment/mto_shipment_updater_test.go index e408c246cd0..44ab1d6a386 100644 --- a/pkg/services/mto_shipment/mto_shipment_updater_test.go +++ b/pkg/services/mto_shipment/mto_shipment_updater_test.go @@ -156,6 +156,9 @@ func (suite *MTOShipmentServiceSuite) TestMTOShipmentUpdater() { eTag := etag.GenerateEtag(time.Now()) + var testScheduledPickupDate time.Time + mtoShipment.ScheduledPickupDate = &testScheduledPickupDate + session := auth.Session{} _, err := mtoShipmentUpdaterCustomer.UpdateMTOShipment(suite.AppContextWithSessionForTest(&session), &mtoShipment, eTag, "test") suite.Error(err) @@ -191,6 +194,8 @@ func (suite *MTOShipmentServiceSuite) TestMTOShipmentUpdater() { setupTestData() eTag := etag.GenerateEtag(oldMTOShipment.UpdatedAt) + var testScheduledPickupDate time.Time + mtoShipment.ScheduledPickupDate = &testScheduledPickupDate session := auth.Session{} updatedMTOShipment, err := mtoShipmentUpdaterCustomer.UpdateMTOShipment(suite.AppContextWithSessionForTest(&session), &mtoShipment, eTag, "test") @@ -211,11 +216,13 @@ func (suite *MTOShipmentServiceSuite) TestMTOShipmentUpdater() { suite.Run("Updater can handle optional queries set as nil", func() { setupTestData() + var testScheduledPickupDate time.Time oldMTOShipment2 := factory.BuildMTOShipment(suite.DB(), nil, nil) mtoShipment2 := models.MTOShipment{ - ID: oldMTOShipment2.ID, - ShipmentType: "UNACCOMPANIED_BAGGAGE", + ID: oldMTOShipment2.ID, + ShipmentType: "UNACCOMPANIED_BAGGAGE", + ScheduledPickupDate: &testScheduledPickupDate, } eTag := etag.GenerateEtag(oldMTOShipment2.UpdatedAt) @@ -3678,3 +3685,262 @@ func (suite *MTOShipmentServiceSuite) TestUpdateDomesticServiceItems() { } }) } + +func (suite *MTOShipmentServiceSuite) TestUpdateRequiredDeliveryDateUpdate() { + + builder := query.NewQueryBuilder() + fetcher := fetch.NewFetcher(builder) + planner := &mocks.Planner{} + planner.On("ZipTransitDistance", + mock.AnythingOfType("*appcontext.appContext"), + mock.Anything, + mock.Anything, + false, + ).Return(1000, nil) + moveRouter := moveservices.NewMoveRouter() + waf := entitlements.NewWeightAllotmentFetcher() + moveWeights := moveservices.NewMoveWeights(NewShipmentReweighRequester(), waf) + mockShipmentRecalculator := mockservices.PaymentRequestShipmentRecalculator{} + mockShipmentRecalculator.On("ShipmentRecalculatePaymentRequest", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("uuid.UUID"), + ).Return(&models.PaymentRequests{}, nil) + mockSender := setUpMockNotificationSender() + addressCreator := address.NewAddressCreator() + addressUpdater := address.NewAddressUpdater() + + mtoShipmentUpdaterPrime := NewPrimeMTOShipmentUpdater(builder, fetcher, planner, moveRouter, moveWeights, mockSender, &mockShipmentRecalculator, addressUpdater, addressCreator) + + suite.Run("should error when unable to find IntlTransit time when updating UB Shipment", func() { + testdatagen.FetchOrMakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + StartDate: time.Now().AddDate(-1, 0, 0), + EndDate: time.Now().AddDate(1, 0, 0), + }, + }) + + moveWithUb := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) + + testScheduledPickupDate := time.Now().AddDate(0, -5, 0) + testScheduledPickupDate2 := time.Now().AddDate(0, -2, 0) + primeEstimatedWeight := unit.Pound(9000) + + oldUBMTOShipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + MarketCode: models.MarketCodeInternational, + Status: models.MTOShipmentStatusApproved, + ShipmentType: models.MTOShipmentTypeUnaccompaniedBaggage, + PrimeEstimatedWeight: &primeEstimatedWeight, + ScheduledPickupDate: &testScheduledPickupDate, + }, + }, + { + Model: moveWithUb, + LinkOnly: true, + }, + }, nil) + oldUBMTOShipment.ScheduledPickupDate = &testScheduledPickupDate + mtoShipment := models.MTOShipment{ + ID: oldUBMTOShipment.ID, + ShipmentType: models.MTOShipmentTypeUnaccompaniedBaggage, + ScheduledPickupDate: &testScheduledPickupDate2, + } + + eTag := etag.GenerateEtag(oldUBMTOShipment.UpdatedAt) + session := auth.Session{} + + updatedMTOShipment, err := mtoShipmentUpdaterPrime.UpdateMTOShipment(suite.AppContextWithSessionForTest(&session), &mtoShipment, eTag, "test") + + suite.Error(err) + suite.Nil(updatedMTOShipment) + suite.IsType(apperror.QueryError{}, err) + queryErr := err.(apperror.QueryError) + wrappedErr := queryErr.Unwrap() + suite.IsType(apperror.QueryError{}, wrappedErr) + suite.Equal("could not look up intl transit time", wrappedErr.Error()) + }) + + suite.Run("should update requiredDeliveryDate when scheduledPickupDate exist and shipment status is approved", func() { + + testdatagen.FetchOrMakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + StartDate: time.Now().AddDate(-1, 0, 0), + EndDate: time.Now().AddDate(1, 0, 0), + }, + }) + + moveWithUb := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) + + testScheduledPickupDate := time.Now().AddDate(0, -5, 0) + primeEstimatedWeight := unit.Pound(9000) + + usPostRegionPickup, err := uuid.FromString("d47d5da4-10d2-41f9-9985-d7dd0f05d98c") + suite.NoError(err) + + usPostRegionDest, err := uuid.FromString("4cb599d1-c12a-43e3-988c-d43d5641f291") + suite.NoError(err) + + // Create the addresses + pickupAddress := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "987 Other Avenue", + StreetAddress2: models.StringPointer("P.O. Box 1234"), + StreetAddress3: models.StringPointer("c/o Another Person"), + City: "Columbia", + State: "SC", + PostalCode: "29228", + IsOconus: models.BoolPointer(false), + UsPostRegionCityID: &usPostRegionPickup, + }, + }, + }, nil) + + destinationAddress := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "987 Other Avenue", + StreetAddress2: models.StringPointer("P.O. Box 12345"), + StreetAddress3: models.StringPointer("c/o Another Person"), + City: "Eielson AFB", + State: "AK", + PostalCode: "99702", + IsOconus: models.BoolPointer(true), + UsPostRegionCityID: &usPostRegionDest, + }, + }, + }, nil) + + var requiredDeliveryDate time.Time + oldUBMTOShipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + MarketCode: models.MarketCodeInternational, + Status: models.MTOShipmentStatusSubmitted, + ShipmentType: models.MTOShipmentTypeUnaccompaniedBaggage, + PrimeEstimatedWeight: &primeEstimatedWeight, + ScheduledPickupDate: &testScheduledPickupDate, + PickupAddressID: &pickupAddress.ID, + DestinationAddressID: &destinationAddress.ID, + RequiredDeliveryDate: &requiredDeliveryDate, + }, + }, + { + Model: moveWithUb, + LinkOnly: true, + }, + }, nil) + + suite.True(oldUBMTOShipment.RequiredDeliveryDate.IsZero()) + + eTag := etag.GenerateEtag(oldUBMTOShipment.UpdatedAt) + + session := auth.Session{} + builder := query.NewQueryBuilder() + moveRouter := moveservices.NewMoveRouter() + planner := &mocks.Planner{} + planner.On("ZipTransitDistance", + mock.AnythingOfType("*appcontext.appContext"), + mock.Anything, + mock.Anything, + ).Return(400, nil) + siCreator := mtoserviceitem.NewMTOServiceItemCreator(planner, builder, moveRouter, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticPackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticShorthaulPricer(), ghcrateengine.NewDomesticOriginPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) + updater := NewMTOShipmentStatusUpdater(builder, siCreator, planner) + + updatedShipment, err := updater.UpdateMTOShipmentStatus(suite.AppContextWithSessionForTest(&session), oldUBMTOShipment.ID, models.MTOShipmentStatusApproved, nil, nil, eTag) + + suite.Nil(err) + suite.NotNil(updatedShipment) + suite.NotNil(updatedShipment.RequiredDeliveryDate) + }) + + suite.Run("should update requiredDeliveryDate when scheduledPickupDate is updated", func() { + testdatagen.FetchOrMakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + StartDate: time.Now().AddDate(-1, 0, 0), + EndDate: time.Now().AddDate(1, 0, 0), + }, + }) + + moveWithUb := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) + + testScheduledPickupDate := time.Now().AddDate(0, -5, 0) + testScheduledPickupDate2 := time.Now().AddDate(0, -2, 0) + primeEstimatedWeight := unit.Pound(9000) + + usPostRegionPickup, err := uuid.FromString("d47d5da4-10d2-41f9-9985-d7dd0f05d98c") + suite.NoError(err) + + usPostRegionDest, err := uuid.FromString("4cb599d1-c12a-43e3-988c-d43d5641f291") + suite.NoError(err) + + // Create the addresses + pickupAddress := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "987 Other Avenue", + StreetAddress2: models.StringPointer("P.O. Box 1234"), + StreetAddress3: models.StringPointer("c/o Another Person"), + City: "Columbia", + State: "SC", + PostalCode: "29228", + IsOconus: models.BoolPointer(false), + UsPostRegionCityID: &usPostRegionPickup, + }, + }, + }, nil) + + destinationAddress := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "987 Other Avenue", + StreetAddress2: models.StringPointer("P.O. Box 12345"), + StreetAddress3: models.StringPointer("c/o Another Person"), + City: "Eielson AFB", + State: "AK", + PostalCode: "99702", + IsOconus: models.BoolPointer(true), + UsPostRegionCityID: &usPostRegionDest, + }, + }, + }, nil) + + var requiredDeliveryDate time.Time + oldUBMTOShipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + MarketCode: models.MarketCodeInternational, + Status: models.MTOShipmentStatusApproved, + ShipmentType: models.MTOShipmentTypeUnaccompaniedBaggage, + PrimeEstimatedWeight: &primeEstimatedWeight, + ScheduledPickupDate: &testScheduledPickupDate, + PickupAddressID: &pickupAddress.ID, + DestinationAddressID: &destinationAddress.ID, + RequiredDeliveryDate: &requiredDeliveryDate, + }, + }, + { + Model: moveWithUb, + LinkOnly: true, + }, + }, nil) + + suite.True(oldUBMTOShipment.RequiredDeliveryDate.IsZero()) + + mtoShipment := models.MTOShipment{ + ID: oldUBMTOShipment.ID, + ShipmentType: models.MTOShipmentTypeUnaccompaniedBaggage, + ScheduledPickupDate: &testScheduledPickupDate2, + } + + eTag := etag.GenerateEtag(oldUBMTOShipment.UpdatedAt) + session := auth.Session{} + + updatedMTOShipment, err := mtoShipmentUpdaterPrime.UpdateMTOShipment(suite.AppContextWithSessionForTest(&session), &mtoShipment, eTag, "test") + + suite.Nil(err) + suite.NotNil(updatedMTOShipment) + suite.NotNil(updatedMTOShipment.RequiredDeliveryDate) + }) +} diff --git a/pkg/services/mto_shipment/rules.go b/pkg/services/mto_shipment/rules.go index 604da6a12f0..63749cc54be 100644 --- a/pkg/services/mto_shipment/rules.go +++ b/pkg/services/mto_shipment/rules.go @@ -337,7 +337,7 @@ func checkPrimeValidationsOnModel(planner route.Planner) validator { // If we have all the data, calculate RDD if latestSchedPickupDate != nil && (latestEstimatedWeight != nil || (older.ShipmentType == models.MTOShipmentTypeHHGOutOfNTS && - older.NTSRecordedWeight != nil)) && latestPickupAddress != nil && latestDestinationAddress != nil { + older.NTSRecordedWeight != nil)) && latestPickupAddress != nil && latestDestinationAddress != nil && older.ShipmentType != models.MTOShipmentTypeUnaccompaniedBaggage { weight := latestEstimatedWeight if older.ShipmentType == models.MTOShipmentTypeHHGOutOfNTS && older.NTSRecordedWeight != nil { weight = older.NTSRecordedWeight diff --git a/pkg/services/mto_shipment/shipment_approver.go b/pkg/services/mto_shipment/shipment_approver.go index 1ed37f55165..1072bdf7a24 100644 --- a/pkg/services/mto_shipment/shipment_approver.go +++ b/pkg/services/mto_shipment/shipment_approver.go @@ -186,6 +186,7 @@ func (f *shipmentApprover) findShipment(appCtx appcontext.AppContext, shipmentID func (f *shipmentApprover) setRequiredDeliveryDate(appCtx appcontext.AppContext, shipment *models.MTOShipment) error { if shipment.ScheduledPickupDate != nil && shipment.RequiredDeliveryDate == nil && + shipment.ShipmentType != models.MTOShipmentTypeUnaccompaniedBaggage && (shipment.PrimeEstimatedWeight != nil || (shipment.ShipmentType == models.MTOShipmentTypeHHGOutOfNTS && shipment.NTSRecordedWeight != nil)) { @@ -219,6 +220,13 @@ func (f *shipmentApprover) setRequiredDeliveryDate(appCtx appcontext.AppContext, } shipment.RequiredDeliveryDate = requiredDeliveryDate + } else if shipment.ScheduledPickupDate != nil && !shipment.ScheduledPickupDate.IsZero() && shipment.ShipmentType == models.MTOShipmentTypeUnaccompaniedBaggage { + requiredDeliveryDate, calcErr := CalculateRequiredDeliveryDateForInternationalShipment(appCtx, *shipment.PickupAddress, *shipment.DestinationAddress, *shipment.ScheduledPickupDate, shipment.ShipmentType) + if calcErr != nil { + return calcErr + } + + shipment.RequiredDeliveryDate = &requiredDeliveryDate } return nil diff --git a/pkg/services/mto_shipment/shipment_approver_test.go b/pkg/services/mto_shipment/shipment_approver_test.go index 4e2a45f68ee..b40d2612591 100644 --- a/pkg/services/mto_shipment/shipment_approver_test.go +++ b/pkg/services/mto_shipment/shipment_approver_test.go @@ -1078,6 +1078,7 @@ func (suite *MTOShipmentServiceSuite) TestApproveShipment() { }) suite.Run("If the OCONUS to CONUS UB mtoShipment is approved successfully it should create pre approved mtoServiceItems", func() { + var scheduledPickupDate time.Time internationalShipment := factory.BuildMTOShipment(suite.AppContextForTest().DB(), []factory.Customization{ { Model: models.Move{ @@ -1096,9 +1097,10 @@ func (suite *MTOShipmentServiceSuite) TestApproveShipment() { }, { Model: models.MTOShipment{ - MarketCode: models.MarketCodeInternational, - Status: models.MTOShipmentStatusSubmitted, - ShipmentType: models.MTOShipmentTypeUnaccompaniedBaggage, + MarketCode: models.MarketCodeInternational, + Status: models.MTOShipmentStatusSubmitted, + ShipmentType: models.MTOShipmentTypeUnaccompaniedBaggage, + ScheduledPickupDate: &scheduledPickupDate, }, }, { @@ -1146,6 +1148,7 @@ func (suite *MTOShipmentServiceSuite) TestApproveShipment() { }) suite.Run("If the OCONUS to OCONUS UB mtoShipment is approved successfully it should create pre approved mtoServiceItems", func() { + var scheduledPickupDate time.Time internationalShipment := factory.BuildMTOShipment(suite.AppContextForTest().DB(), []factory.Customization{ { Model: models.Move{ @@ -1164,9 +1167,10 @@ func (suite *MTOShipmentServiceSuite) TestApproveShipment() { }, { Model: models.MTOShipment{ - MarketCode: models.MarketCodeInternational, - Status: models.MTOShipmentStatusSubmitted, - ShipmentType: models.MTOShipmentTypeUnaccompaniedBaggage, + MarketCode: models.MarketCodeInternational, + Status: models.MTOShipmentStatusSubmitted, + ShipmentType: models.MTOShipmentTypeUnaccompaniedBaggage, + ScheduledPickupDate: &scheduledPickupDate, }, }, {