diff --git a/migrations/app/migrations_manifest.txt b/migrations/app/migrations_manifest.txt index 20fd4c31d51..8a92db1e557 100644 --- a/migrations/app/migrations_manifest.txt +++ b/migrations/app/migrations_manifest.txt @@ -1059,6 +1059,7 @@ 20241217163231_update_duty_locations_bad_zips.up.sql 20241217180136_add_AK_zips_to_zip3_distances.up.sql 20241218201833_add_PPPO_BASE_ELIZABETH.up.sql +20241218204620_add_international_nts_service_items.up.sql 20241220171035_add_additional_AK_zips_to_zip3_distances.up.sql 20241220213134_add_destination_gbloc_db_function.up.sql 20241224172258_add_and_update_po_box_zip.up.sql diff --git a/migrations/app/schema/20241218204620_add_international_nts_service_items.up.sql b/migrations/app/schema/20241218204620_add_international_nts_service_items.up.sql new file mode 100644 index 00000000000..207c5379080 --- /dev/null +++ b/migrations/app/schema/20241218204620_add_international_nts_service_items.up.sql @@ -0,0 +1,36 @@ +-- +-- Add service items for international NTS shipments. +-- +INSERT INTO re_service_items +(id, service_id, shipment_type, market_code, is_auto_approved, created_at, updated_at, sort) +VALUES + --ISLH International Shipping & Linehaul + ('2a560507-db09-4be1-b809-49c0f515b31b', '9f3d551a-0725-430e-897e-80ee9add3ae9' ,'HHG_INTO_NTS', 'i', true, now(), now(), 1), + --PODFSC International POD Fuel Surcharge + ('e702818f-defd-452c-81a3-865b902e7dd0', '388115e8-abe9-441d-96cf-a39f24baa0a3' ,'HHG_INTO_NTS', 'i', true, now(), now(), 2), + --INPK International NTS packing + ('366ee5a4-eb61-4ded-a68c-ddc29fe1a886', '874cb86a-bc39-4f57-a614-53ee3fcacf14' ,'HHG_INTO_NTS', 'i', true, now(), now(), 3), + --ICRT International crating + ('aac4e95e-27ed-4f09-9b6b-384d8542f410', '86203d72-7f7c-49ff-82f0-5b95e4958f60' ,'HHG_INTO_NTS', 'i', false, now(), now(), NULL), + --IDASIT International destination add'l day SIT + ('010f2f91-7381-4149-8d74-8eb5f593a864', '806c6d59-57ff-4a3f-9518-ebf29ba9cb10' ,'HHG_INTO_NTS', 'i', false, now(), now(), NULL), + --IDDSIT International destination SIT delivery + ('a41966b7-b83a-4eaf-8e68-d5e884777102', '28389ee1-56cf-400c-aa52-1501ecdd7c69' ,'HHG_INTO_NTS', 'i', false, now(), now(), NULL), + --IDFSIT International destination 1st day SIT + ('14c77957-3c76-465a-bb07-c98d36ef1e54', 'bd6064ca-e780-4ab4-a37b-0ae98eebb244' ,'HHG_INTO_NTS', 'i', false, now(), now(), NULL), + --IDSHUT International destination shuttle service + ('d52d2d03-100a-4ed9-b2de-16eac63a375f', '22fc07ed-be15-4f50-b941-cbd38153b378' ,'HHG_INTO_NTS', 'i', false, now(), now(), NULL), + --IOASIT International origin add'l day SIT + ('7fd91408-7d69-4375-b7e6-5b2ff714206b', 'bd424e45-397b-4766-9712-de4ae3a2da36' ,'HHG_INTO_NTS', 'i', false, now(), now(), NULL), + --IOFSIT International origin 1st day SIT + ('b3dc509d-d652-4300-a702-a1ddce6255b6', 'b488bf85-ea5e-49c8-ba5c-e2fa278ac806' ,'HHG_INTO_NTS', 'i', false, now(), now(), NULL), + --IOPSIT International origin SIT pickup + ('001eadb6-3526-45b9-96e0-0648bb481e86', '6f4f6e31-0675-4051-b659-89832259f390' ,'HHG_INTO_NTS', 'i', false, now(), now(), NULL), + --IOSHUT International origin shuttle service + ('b991c5c9-af2c-4146-b999-1d0bdf91de3f', '624a97c5-dfbf-4da9-a6e9-526b4f95af8d' ,'HHG_INTO_NTS', 'i', false, now(), now(), NULL), + --IUCRT International uncrating + ('5a89315a-257b-4ef0-92cb-4c06aa6f1332', '4132416b-b1aa-42e7-98f2-0ac0a03e8a31' ,'HHG_INTO_NTS', 'i', false, now(), now(), NULL), + --IOFSC International Origin SIT Fuel Surcharge + ('d4a98dea-a5f7-4b92-b5de-e6350ab07824', '81e29d0c-02a6-4a7a-be02-554deb3ee49e' ,'HHG_INTO_NTS', 'i', false, now(), now(), NULL), + --IDSFSC International Destination SIT Fuel Surcharge + ('eaea90c2-93d3-4db9-89cd-23ac57ec9ce1', '690a5fc1-0ea5-4554-8294-a367b5daefa9' ,'HHG_INTO_NTS', 'i', false, now(), now(), NULL); diff --git a/pkg/factory/mto_shipment_factory.go b/pkg/factory/mto_shipment_factory.go index 89efcc51e2f..0dc95a166e6 100644 --- a/pkg/factory/mto_shipment_factory.go +++ b/pkg/factory/mto_shipment_factory.go @@ -57,7 +57,7 @@ func buildMTOShipmentWithBuildType(db *pop.Connection, customs []Customization, defaultStatus = models.MTOShipmentStatusDraft buildStorageFacility = hasStorageFacilityCustom shipmentHasPickupDetails = true - shipmentHasDeliveryDetails = false + shipmentHasDeliveryDetails = true case mtoShipmentNTSR: defaultShipmentType = models.MTOShipmentTypeHHGOutOfNTS defaultStatus = models.MTOShipmentStatusDraft @@ -83,6 +83,10 @@ func buildMTOShipmentWithBuildType(db *pop.Connection, customs []Customization, MarketCode: defaultMarketCode, } + if newMTOShipment.ShipmentType == models.MTOShipmentTypeHHGIntoNTS && newMTOShipment.StorageFacility != nil { + newMTOShipment.DestinationAddress = &newMTOShipment.StorageFacility.Address + } + if cMtoShipment.Status == models.MTOShipmentStatusApproved { approvedDate := time.Date(GHCTestYear, time.March, 20, 0, 0, 0, 0, time.UTC) newMTOShipment.ApprovedDate = &approvedDate diff --git a/pkg/factory/mto_shipment_factory_test.go b/pkg/factory/mto_shipment_factory_test.go index 797164a616b..db0c04dd979 100644 --- a/pkg/factory/mto_shipment_factory_test.go +++ b/pkg/factory/mto_shipment_factory_test.go @@ -450,9 +450,9 @@ func (suite *FactorySuite) TestBuildMTOShipment() { suite.NotNil(ntsShipment.PrimeActualWeight) suite.Nil(ntsShipment.StorageFacility) suite.NotNil(ntsShipment.ScheduledPickupDate) - suite.Nil(ntsShipment.RequestedDeliveryDate) + suite.NotNil(ntsShipment.RequestedDeliveryDate) suite.Nil(ntsShipment.ActualDeliveryDate) - suite.Nil(ntsShipment.ScheduledDeliveryDate) + suite.NotNil(ntsShipment.ScheduledDeliveryDate) }) suite.Run("Successful creation of NTSShipment with storage facility", func() { diff --git a/pkg/services/mto_shipment/mto_shipment_updater.go b/pkg/services/mto_shipment/mto_shipment_updater.go index 280f223c551..e83e6a6e223 100644 --- a/pkg/services/mto_shipment/mto_shipment_updater.go +++ b/pkg/services/mto_shipment/mto_shipment_updater.go @@ -855,8 +855,8 @@ func (f *mtoShipmentUpdater) updateShipmentRecord(appCtx appcontext.AppContext, // we will compare data here to see if we even need to update the pricing if newShipment.MarketCode == models.MarketCodeInternational && (newShipment.PrimeEstimatedWeight != nil || - newShipment.PickupAddress != nil && newShipment.PickupAddress.PostalCode != dbShipment.PickupAddress.PostalCode || - newShipment.DestinationAddress != nil && newShipment.DestinationAddress.PostalCode != dbShipment.DestinationAddress.PostalCode || + newShipment.PickupAddress != nil && dbShipment.PickupAddress != nil && newShipment.PickupAddress.PostalCode != dbShipment.PickupAddress.PostalCode || + newShipment.DestinationAddress != nil && dbShipment.DestinationAddress != nil && newShipment.DestinationAddress.PostalCode != dbShipment.DestinationAddress.PostalCode || newShipment.RequestedPickupDate != nil && newShipment.RequestedPickupDate.Format("2006-01-02") != dbShipment.RequestedPickupDate.Format("2006-01-02")) { portZip, portType, err := models.GetPortLocationInfoForShipment(appCtx.DB(), newShipment.ID) @@ -1110,9 +1110,9 @@ func reServiceCodesForShipment(shipment models.MTOShipment) []models.ReServiceCo // More info in MB-1140: https://dp3.atlassian.net/browse/MB-1140 // international shipment service items are created in the shipment_approver - switch shipment.ShipmentType { - case models.MTOShipmentTypeHHG: - if shipment.MarketCode != models.MarketCodeInternational { + if shipment.MarketCode != models.MarketCodeInternational { + switch shipment.ShipmentType { + case models.MTOShipmentTypeHHG: originZIP3 := shipment.PickupAddress.PostalCode[0:3] destinationZIP3 := shipment.DestinationAddress.PostalCode[0:3] @@ -1136,51 +1136,51 @@ 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{ - models.ReServiceCodeDLH, - models.ReServiceCodeFSC, - models.ReServiceCodeDOP, - models.ReServiceCodeDDP, - models.ReServiceCodeDNPK, - } - case models.MTOShipmentTypeHHGOutOfNTS: - // Need to create: Dom Linehaul, Fuel Surcharge, Dom Origin Price, Dom Destination Price, Dom Unpacking - return []models.ReServiceCode{ - models.ReServiceCodeDLH, - models.ReServiceCodeFSC, - models.ReServiceCodeDOP, - models.ReServiceCodeDDP, - models.ReServiceCodeDUPK, - } - case models.MTOShipmentTypeMobileHome: - // Need to create: Dom Linehaul, Fuel Surcharge, Dom Origin Price, Dom Destination Price, Dom Mobile Home Factor - return []models.ReServiceCode{ - models.ReServiceCodeDLH, - models.ReServiceCodeFSC, - models.ReServiceCodeDOP, - models.ReServiceCodeDDP, - models.ReServiceCodeDMHF, - } - case models.MTOShipmentTypeBoatHaulAway: - // Need to create: Dom Linehaul, Fuel Surcharge, Dom Origin Price, Dom Destination Price, Dom Haul Away Boat Factor - return []models.ReServiceCode{ - models.ReServiceCodeDLH, - models.ReServiceCodeFSC, - models.ReServiceCodeDOP, - models.ReServiceCodeDDP, - models.ReServiceCodeDBHF, - } - case models.MTOShipmentTypeBoatTowAway: - // Need to create: Dom Linehaul, Fuel Surcharge, Dom Origin Price, Dom Destination Price, Dom Tow Away Boat Factor - return []models.ReServiceCode{ - models.ReServiceCodeDLH, - models.ReServiceCodeFSC, - models.ReServiceCodeDOP, - models.ReServiceCodeDDP, - models.ReServiceCodeDBTF, + case models.MTOShipmentTypeHHGIntoNTS: + // Need to create: Dom Linehaul, Fuel Surcharge, Dom Origin Price, Dom Destination Price, Dom NTS Packing + return []models.ReServiceCode{ + models.ReServiceCodeDLH, + models.ReServiceCodeFSC, + models.ReServiceCodeDOP, + models.ReServiceCodeDDP, + models.ReServiceCodeDNPK, + } + case models.MTOShipmentTypeHHGOutOfNTS: + // Need to create: Dom Linehaul, Fuel Surcharge, Dom Origin Price, Dom Destination Price, Dom Unpacking + return []models.ReServiceCode{ + models.ReServiceCodeDLH, + models.ReServiceCodeFSC, + models.ReServiceCodeDOP, + models.ReServiceCodeDDP, + models.ReServiceCodeDUPK, + } + case models.MTOShipmentTypeMobileHome: + // Need to create: Dom Linehaul, Fuel Surcharge, Dom Origin Price, Dom Destination Price, Dom Mobile Home Factor + return []models.ReServiceCode{ + models.ReServiceCodeDLH, + models.ReServiceCodeFSC, + models.ReServiceCodeDOP, + models.ReServiceCodeDDP, + models.ReServiceCodeDMHF, + } + case models.MTOShipmentTypeBoatHaulAway: + // Need to create: Dom Linehaul, Fuel Surcharge, Dom Origin Price, Dom Destination Price, Dom Haul Away Boat Factor + return []models.ReServiceCode{ + models.ReServiceCodeDLH, + models.ReServiceCodeFSC, + models.ReServiceCodeDOP, + models.ReServiceCodeDDP, + models.ReServiceCodeDBHF, + } + case models.MTOShipmentTypeBoatTowAway: + // Need to create: Dom Linehaul, Fuel Surcharge, Dom Origin Price, Dom Destination Price, Dom Tow Away Boat Factor + return []models.ReServiceCode{ + models.ReServiceCodeDLH, + models.ReServiceCodeFSC, + models.ReServiceCodeDOP, + models.ReServiceCodeDDP, + models.ReServiceCodeDBTF, + } } } diff --git a/pkg/services/mto_shipment/mto_shipment_updater_test.go b/pkg/services/mto_shipment/mto_shipment_updater_test.go index 673166ea591..63d4af96dc8 100644 --- a/pkg/services/mto_shipment/mto_shipment_updater_test.go +++ b/pkg/services/mto_shipment/mto_shipment_updater_test.go @@ -3445,3 +3445,104 @@ func (suite *MTOShipmentServiceSuite) TestUpdateStatusServiceItems() { suite.Equal(models.ReServiceCodeDSH, serviceItems[0].ReService.Code) }) } + +func (suite *MTOShipmentServiceSuite) TestUpdateDomesticServiceItems() { + + expectedReServiceCodes := []models.ReServiceCode{ + models.ReServiceCodeDLH, + models.ReServiceCodeFSC, + models.ReServiceCodeDOP, + models.ReServiceCodeDDP, + models.ReServiceCodeDNPK, + } + + var pickupAddress models.Address + var storageFacility models.StorageFacility + var mto models.Move + + setupTestData := func() { + pickupAddress = factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "Test Street 1", + City: "Des moines", + State: "IA", + PostalCode: "50309", + IsOconus: models.BoolPointer(false), + }, + }, + }, nil) + + storageFacility = factory.BuildStorageFacility(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "Test Street Adress 2", + City: "Des moines", + State: "IA", + PostalCode: "50314", + IsOconus: models.BoolPointer(false), + }, + }, + }, nil) + + mto = factory.BuildMove(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusAPPROVED, + }, + }, + }, nil) + } + + 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) + + suite.Run("Preapproved service items successfully added to domestic nts shipments", func() { + setupTestData() + + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: mto, + LinkOnly: true, + }, + { + Model: pickupAddress, + Type: &factory.Addresses.PickupAddress, + LinkOnly: true, + }, + { + Model: storageFacility, + Type: &factory.StorageFacility, + LinkOnly: true, + }, + { + Model: models.MTOShipment{ + ShipmentType: models.MTOShipmentTypeHHGIntoNTS, + Status: models.MTOShipmentStatusSubmitted, + }, + }, + }, nil) + + appCtx := suite.AppContextForTest() + eTag := etag.GenerateEtag(shipment.UpdatedAt) + + updatedShipment, err := updater.UpdateMTOShipmentStatus(appCtx, shipment.ID, models.MTOShipmentStatusApproved, nil, nil, eTag) + suite.NoError(err) + + serviceItems := models.MTOServiceItems{} + err = appCtx.DB().EagerPreload("ReService").Where("mto_shipment_id = ?", updatedShipment.ID).All(&serviceItems) + suite.NoError(err) + + for i := 0; i < len(expectedReServiceCodes); i++ { + suite.Equal(expectedReServiceCodes[i], serviceItems[i].ReService.Code) + } + }) +} diff --git a/pkg/services/mto_shipment/shipment_approver.go b/pkg/services/mto_shipment/shipment_approver.go index 2a4d5d97fb9..b285684a62b 100644 --- a/pkg/services/mto_shipment/shipment_approver.go +++ b/pkg/services/mto_shipment/shipment_approver.go @@ -2,6 +2,7 @@ package mtoshipment import ( "math" + "slices" "github.com/gofrs/uuid" "github.com/pkg/errors" @@ -80,7 +81,8 @@ func (f *shipmentApprover) ApproveShipment(appCtx appcontext.AppContext, shipmen transactionError := appCtx.NewTransaction(func(txnAppCtx appcontext.AppContext) error { // create international shipment service items before approving // we use a database proc to create the basic auto-approved service items - if shipment.ShipmentType == models.MTOShipmentTypeHHG && shipment.MarketCode == models.MarketCodeInternational { + internationalShipmentTypes := []models.MTOShipmentType{models.MTOShipmentTypeHHG, models.MTOShipmentTypeHHGIntoNTS, models.MTOShipmentTypeUnaccompaniedBaggage} + if slices.Contains(internationalShipmentTypes, shipment.ShipmentType) && shipment.MarketCode == models.MarketCodeInternational { err := models.CreateApprovedServiceItemsForShipment(appCtx.DB(), shipment) if err != nil { return err diff --git a/pkg/services/mto_shipment/shipment_approver_test.go b/pkg/services/mto_shipment/shipment_approver_test.go index 309a87c0015..167cffca439 100644 --- a/pkg/services/mto_shipment/shipment_approver_test.go +++ b/pkg/services/mto_shipment/shipment_approver_test.go @@ -14,6 +14,7 @@ import ( "github.com/transcom/mymove/pkg/etag" "github.com/transcom/mymove/pkg/factory" "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/route" "github.com/transcom/mymove/pkg/route/mocks" "github.com/transcom/mymove/pkg/services" "github.com/transcom/mymove/pkg/services/ghcrateengine" @@ -299,7 +300,7 @@ func (suite *MTOShipmentServiceSuite) TestApproveShipment() { models.ReServiceCodeIHUPK, } - suite.Equal(4, len(serviceItems)) + suite.Equal(len(expectedReserviceCodes), len(serviceItems)) for i := 0; i < len(serviceItems); i++ { actualReServiceCode := serviceItems[i].ReService.Code suite.True(slices.Contains(expectedReserviceCodes, actualReServiceCode)) @@ -312,6 +313,163 @@ func (suite *MTOShipmentServiceSuite) TestApproveShipment() { } }) + suite.Run("Given international mtoShipment is approved successfully pre-approved mtoServiceItems are created NTS CONUS to OCONUS", func() { + storageFacility := factory.BuildStorageFacility(suite.DB(), []factory.Customization{ + { + Model: models.StorageFacility{ + FacilityName: *models.StringPointer("Test Storage Name"), + Email: models.StringPointer("old@email.com"), + LotNumber: models.StringPointer("Test lot number"), + Phone: models.StringPointer("555-555-5555"), + }, + }, + { + Model: models.Address{ + StreetAddress1: "JBER", + City: "Anchorage", + State: "AK", + PostalCode: "99507", + IsOconus: models.BoolPointer(true), + }, + }, + }, nil) + + internationalShipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusAPPROVED, + }, + }, + { + Model: models.Address{ + StreetAddress1: "Tester Address", + City: "Des Moines", + State: "IA", + PostalCode: "50314", + IsOconus: models.BoolPointer(false), + }, + Type: &factory.Addresses.PickupAddress, + }, + { + Model: models.MTOShipment{ + MarketCode: models.MarketCodeInternational, + Status: models.MTOShipmentStatusSubmitted, + ShipmentType: models.MTOShipmentTypeHHGIntoNTS, + }, + }, + { + Model: storageFacility, + LinkOnly: true, + }, + }, nil) + internationalShipmentEtag := etag.GenerateEtag(internationalShipment.UpdatedAt) + + shipmentRouter := NewShipmentRouter() + var serviceItemCreator services.MTOServiceItemCreator + var planner route.Planner + var moveWeights services.MoveWeights + + // Approve international shipment + shipmentApprover := NewShipmentApprover(shipmentRouter, serviceItemCreator, planner, moveWeights) + _, err := shipmentApprover.ApproveShipment(suite.AppContextForTest(), internationalShipment.ID, internationalShipmentEtag) + suite.NoError(err) + + // Get created pre approved service items + var serviceItems []models.MTOServiceItem + err2 := suite.AppContextForTest().DB().EagerPreload("ReService").Where("mto_shipment_id = ?", internationalShipment.ID).Order("created_at asc").All(&serviceItems) + suite.NoError(err2) + + expectedReserviceCodes := []models.ReServiceCode{ + models.ReServiceCodeISLH, + models.ReServiceCodeINPK, + } + + suite.Equal(len(expectedReserviceCodes), len(serviceItems)) + for i := 0; i < len(serviceItems); i++ { + actualReServiceCode := serviceItems[i].ReService.Code + suite.True(slices.Contains(expectedReserviceCodes, actualReServiceCode)) + } + }) + + suite.Run("Given international mtoShipment is approved successfully pre-approved mtoServiceItems are created NTS OCONUS to CONUS", func() { + storageFacility := factory.BuildStorageFacility(suite.DB(), []factory.Customization{ + { + Model: models.StorageFacility{ + FacilityName: *models.StringPointer("Test Storage Name"), + Email: models.StringPointer("old@email.com"), + LotNumber: models.StringPointer("Test lot number"), + Phone: models.StringPointer("555-555-5555"), + }, + }, + { + Model: models.Address{ + StreetAddress1: "Tester Address", + City: "Des Moines", + State: "IA", + PostalCode: "50314", + IsOconus: models.BoolPointer(false), + }, + }, + }, nil) + + internationalShipment := factory.BuildNTSShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusAPPROVED, + }, + }, + { + Model: models.Address{ + StreetAddress1: "JBER", + City: "Anchorage", + State: "AK", + PostalCode: "99507", + IsOconus: models.BoolPointer(true), + }, + Type: &factory.Addresses.PickupAddress, + }, + { + Model: models.MTOShipment{ + MarketCode: models.MarketCodeInternational, + Status: models.MTOShipmentStatusSubmitted, + ShipmentType: models.MTOShipmentTypeHHGIntoNTS, + }, + }, + { + Model: storageFacility, + LinkOnly: true, + }, + }, nil) + internationalShipmentEtag := etag.GenerateEtag(internationalShipment.UpdatedAt) + + shipmentRouter := NewShipmentRouter() + var serviceItemCreator services.MTOServiceItemCreator + var planner route.Planner + var moveWeights services.MoveWeights + + // Approve international shipment + shipmentApprover := NewShipmentApprover(shipmentRouter, serviceItemCreator, planner, moveWeights) + _, err := shipmentApprover.ApproveShipment(suite.AppContextForTest(), internationalShipment.ID, internationalShipmentEtag) + suite.NoError(err) + + // Get created pre approved service items + var serviceItems []models.MTOServiceItem + err2 := suite.AppContextForTest().DB().EagerPreload("ReService").Where("mto_shipment_id = ?", internationalShipment.ID).Order("created_at asc").All(&serviceItems) + suite.NoError(err2) + + expectedReserviceCodes := []models.ReServiceCode{ + models.ReServiceCodeISLH, + models.ReServiceCodePODFSC, + models.ReServiceCodeINPK, + } + + suite.Equal(len(expectedReserviceCodes), len(serviceItems)) + for i := 0; i < len(serviceItems); i++ { + actualReServiceCode := serviceItems[i].ReService.Code + suite.True(slices.Contains(expectedReserviceCodes, actualReServiceCode)) + } + }) + suite.Run("If the mtoShipment is approved successfully it should create approved mtoServiceItems", func() { subtestData := suite.createApproveShipmentSubtestData() appCtx := subtestData.appCtx @@ -854,4 +1012,25 @@ func (suite *MTOShipmentServiceSuite) TestApproveShipment() { suite.NotNil(shipment.MoveTaskOrder.ExcessWeightQualifiedAt) }) + + suite.Run("Given invalid shipment error returned", func() { + invalidShipment := factory.BuildMTOShipment(suite.AppContextForTest().DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + ShipmentType: models.MTOShipmentTypePPM, + }, + }, + }, nil) + invalidShipmentEtag := etag.GenerateEtag(invalidShipment.UpdatedAt) + + shipmentRouter := NewShipmentRouter() + var serviceItemCreator services.MTOServiceItemCreator + var planner route.Planner + var moveWeights services.MoveWeights + + // Approve international shipment + shipmentApprover := NewShipmentApprover(shipmentRouter, serviceItemCreator, planner, moveWeights) + _, err := shipmentApprover.ApproveShipment(suite.AppContextForTest(), invalidShipment.ID, invalidShipmentEtag) + suite.Error(err) + }) }