diff --git a/migrations/app/migrations_manifest.txt b/migrations/app/migrations_manifest.txt index c08d3d87e09..8c2120f0dd7 100644 --- a/migrations/app/migrations_manifest.txt +++ b/migrations/app/migrations_manifest.txt @@ -1070,3 +1070,4 @@ 20241230190647_add_missing_AK_zips_to_zip3_distances.up.sql 20250103130619_revert_data_change_for_gbloc_for_ak.up.sql 20250103180420_update_pricing_proc_to_use_local_price_variable.up.sql +20250110153428_add_shipment_address_updates_to_move_history.up.sql diff --git a/migrations/app/schema/20250110153428_add_shipment_address_updates_to_move_history.up.sql b/migrations/app/schema/20250110153428_add_shipment_address_updates_to_move_history.up.sql new file mode 100644 index 00000000000..ec30212f12c --- /dev/null +++ b/migrations/app/schema/20250110153428_add_shipment_address_updates_to_move_history.up.sql @@ -0,0 +1,9 @@ +-- adding shipment_address_updates table to move history so we can track the activity +SELECT add_audit_history_table( + target_table := 'shipment_address_updates', + audit_rows := BOOLEAN 't', + audit_query_text := BOOLEAN 't', + ignored_cols := ARRAY[ + 'created_at' + ] +); \ No newline at end of file diff --git a/pkg/assets/sql_scripts/move_history_fetcher.sql b/pkg/assets/sql_scripts/move_history_fetcher.sql index 305c682ec4f..75f961fa881 100644 --- a/pkg/assets/sql_scripts/move_history_fetcher.sql +++ b/pkg/assets/sql_scripts/move_history_fetcher.sql @@ -646,6 +646,25 @@ WITH move AS ( JOIN gsr_appeals ON gsr_appeals.id = audit_history.object_id WHERE audit_history.table_name = 'gsr_appeals' ), + shipment_address_updates AS ( + SELECT shipment_address_updates.*, + jsonb_agg(jsonb_build_object( + 'status', shipment_address_updates.status + ) + )::TEXT AS context + FROM shipment_address_updates + JOIN move_shipments ON shipment_address_updates.shipment_id = move_shipments.id + GROUP BY shipment_address_updates.id + ), + shipment_address_updates_logs as ( + SELECT audit_history.*, + shipment_address_updates.context AS context, + NULL AS context_id + FROM + audit_history + JOIN shipment_address_updates ON shipment_address_updates.id = audit_history.object_id + WHERE audit_history.table_name = 'shipment_address_updates' + ), combined_logs AS ( SELECT * @@ -736,6 +755,11 @@ WITH move AS ( * FROM gsr_appeals_logs + UNION + SELECT + * + FROM + shipment_address_updates_logs ) diff --git a/pkg/gen/ghcapi/embedded_spec.go b/pkg/gen/ghcapi/embedded_spec.go index 61bd52e4f5c..fd865c3c891 100644 --- a/pkg/gen/ghcapi/embedded_spec.go +++ b/pkg/gen/ghcapi/embedded_spec.go @@ -5935,7 +5935,8 @@ func init() { "application/json" ], "tags": [ - "shipment" + "shipment", + "shipment_address_updates" ], "summary": "Allows TOO to review a shipment address update", "operationId": "reviewShipmentAddressUpdate", @@ -22989,7 +22990,8 @@ func init() { "application/json" ], "tags": [ - "shipment" + "shipment", + "shipment_address_updates" ], "summary": "Allows TOO to review a shipment address update", "operationId": "reviewShipmentAddressUpdate", diff --git a/pkg/gen/ghcapi/ghcoperations/shipment/review_shipment_address_update.go b/pkg/gen/ghcapi/ghcoperations/shipment/review_shipment_address_update.go index d4532a282ce..61dafe8bc53 100644 --- a/pkg/gen/ghcapi/ghcoperations/shipment/review_shipment_address_update.go +++ b/pkg/gen/ghcapi/ghcoperations/shipment/review_shipment_address_update.go @@ -36,7 +36,7 @@ func NewReviewShipmentAddressUpdate(ctx *middleware.Context, handler ReviewShipm } /* - ReviewShipmentAddressUpdate swagger:route PATCH /shipments/{shipmentID}/review-shipment-address-update shipment reviewShipmentAddressUpdate + ReviewShipmentAddressUpdate swagger:route PATCH /shipments/{shipmentID}/review-shipment-address-update shipment shipment_address_updates reviewShipmentAddressUpdate # Allows TOO to review a shipment address update diff --git a/pkg/models/re_contract.go b/pkg/models/re_contract.go index 2c4b4a28e35..c0576ce403b 100644 --- a/pkg/models/re_contract.go +++ b/pkg/models/re_contract.go @@ -1,12 +1,17 @@ package models import ( + "database/sql" + "fmt" "time" "github.com/gobuffalo/pop/v6" "github.com/gobuffalo/validate/v3" "github.com/gobuffalo/validate/v3/validators" "github.com/gofrs/uuid" + + "github.com/transcom/mymove/pkg/appcontext" + "github.com/transcom/mymove/pkg/apperror" ) // ReContract represents a contract with pricing information @@ -32,3 +37,30 @@ func (r *ReContract) Validate(_ *pop.Connection) (*validate.Errors, error) { &validators.StringIsPresent{Field: r.Name, Name: "Name"}, ), nil } + +func FetchContractForMove(appCtx appcontext.AppContext, moveID uuid.UUID) (ReContract, error) { + var move Move + err := appCtx.DB().Find(&move, moveID) + if err != nil { + if err == sql.ErrNoRows { + return ReContract{}, apperror.NewNotFoundError(moveID, "looking for Move") + } + return ReContract{}, err + } + + if move.AvailableToPrimeAt == nil { + return ReContract{}, apperror.NewConflictError(moveID, "unable to pick contract because move is not available to prime") + } + + var contractYear ReContractYear + err = appCtx.DB().EagerPreload("Contract").Where("? between start_date and end_date", move.AvailableToPrimeAt). + First(&contractYear) + if err != nil { + if err == sql.ErrNoRows { + return ReContract{}, apperror.NewNotFoundError(uuid.Nil, fmt.Sprintf("no contract year found for %s", move.AvailableToPrimeAt.String())) + } + return ReContract{}, err + } + + return contractYear.Contract, nil +} diff --git a/pkg/models/re_contract_test.go b/pkg/models/re_contract_test.go index c2148951ede..9fca3401f22 100644 --- a/pkg/models/re_contract_test.go +++ b/pkg/models/re_contract_test.go @@ -1,7 +1,11 @@ package models_test import ( + "time" + + "github.com/transcom/mymove/pkg/factory" "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/testdatagen" ) func (suite *ModelSuite) TestReContractValidations() { @@ -23,3 +27,30 @@ func (suite *ModelSuite) TestReContractValidations() { suite.verifyValidationErrors(&emptyReContract, expErrors) }) } + +func (suite *ModelSuite) TestFetchContractForMove() { + suite.Run("finds valid contract", func() { + reContract := testdatagen.FetchOrMakeReContract(suite.DB(), testdatagen.Assertions{}) + testdatagen.FetchOrMakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + Contract: reContract, + ContractID: reContract.ID, + StartDate: time.Now(), + EndDate: time.Now().Add(time.Hour * 12), + Escalation: 1.0, + EscalationCompounded: 1.0, + }, + }) + move := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) + contract, err := models.FetchContractForMove(suite.AppContextForTest(), move.ID) + suite.NoError(err) + suite.Equal(contract.ID, reContract.ID) + }) + + suite.Run("returns error if no contract found", func() { + move := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) + contract, err := models.FetchContractForMove(suite.AppContextForTest(), move.ID) + suite.Error(err) + suite.Equal(contract, models.ReContract{}) + }) +} diff --git a/pkg/models/re_oconus_rate_areas.go b/pkg/models/re_oconus_rate_areas.go index 72b85773159..84def705f95 100644 --- a/pkg/models/re_oconus_rate_areas.go +++ b/pkg/models/re_oconus_rate_areas.go @@ -3,6 +3,7 @@ package models import ( "time" + "github.com/gobuffalo/pop/v6" "github.com/gofrs/uuid" ) @@ -19,3 +20,16 @@ type OconusRateArea struct { func (o OconusRateArea) TableName() string { return "re_oconus_rate_areas" } + +func FetchOconusRateArea(db *pop.Connection, zip string) (*OconusRateArea, error) { + var reOconusRateArea OconusRateArea + err := db.Q(). + InnerJoin("re_rate_areas ra", "re_oconus_rate_areas.rate_area_id = ra.id"). + InnerJoin("us_post_region_cities upc", "upc.id = re_oconus_rate_areas.us_post_region_cities_id"). + Where("upc.uspr_zip_id = ?", zip). + First(&reOconusRateArea) + if err != nil { + return nil, err + } + return &reOconusRateArea, nil +} diff --git a/pkg/models/re_rate_area.go b/pkg/models/re_rate_area.go index 7b613b42a28..8eb7c56328f 100644 --- a/pkg/models/re_rate_area.go +++ b/pkg/models/re_rate_area.go @@ -56,8 +56,8 @@ func FetchReRateAreaItem(tx *pop.Connection, contractID uuid.UUID, code string) } // a db stored proc that takes in an address id & a service code to get the rate area id for an address -func FetchRateAreaID(db *pop.Connection, addressID uuid.UUID, serviceID uuid.UUID, contractID uuid.UUID) (uuid.UUID, error) { - if addressID != uuid.Nil && serviceID != uuid.Nil && contractID != uuid.Nil { +func FetchRateAreaID(db *pop.Connection, addressID uuid.UUID, serviceID *uuid.UUID, contractID uuid.UUID) (uuid.UUID, error) { + if addressID != uuid.Nil && contractID != uuid.Nil { var rateAreaID uuid.UUID err := db.RawQuery("SELECT get_rate_area_id($1, $2, $3)", addressID, serviceID, contractID).First(&rateAreaID) if err != nil { @@ -67,3 +67,17 @@ func FetchRateAreaID(db *pop.Connection, addressID uuid.UUID, serviceID uuid.UUI } return uuid.Nil, fmt.Errorf("error fetching rate area ID - required parameters not provided") } + +func FetchConusRateAreaByPostalCode(db *pop.Connection, zip string, contractID uuid.UUID) (*ReRateArea, error) { + var reRateArea ReRateArea + postalCode := zip[0:3] + err := db.Q(). + InnerJoin("re_zip3s rz", "rz.rate_area_id = re_rate_areas.id"). + Where("zip3 = ?", postalCode). + Where("re_rate_areas.contract_id = ?", contractID). + First(&reRateArea) + if err != nil { + return nil, err + } + return &reRateArea, nil +} diff --git a/pkg/models/re_rate_area_test.go b/pkg/models/re_rate_area_test.go index 87f310c2088..ab279418976 100644 --- a/pkg/models/re_rate_area_test.go +++ b/pkg/models/re_rate_area_test.go @@ -36,16 +36,14 @@ func (suite *ModelSuite) TestFetchRateAreaID() { service := factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeIHPK) contract := testdatagen.FetchOrMakeReContract(suite.DB(), testdatagen.Assertions{}) address := factory.BuildAddress(suite.DB(), nil, nil) - rateAreaId, err := models.FetchRateAreaID(suite.DB(), address.ID, service.ID, contract.ID) + rateAreaId, err := models.FetchRateAreaID(suite.DB(), address.ID, &service.ID, contract.ID) suite.NotNil(rateAreaId) suite.NoError(err) }) suite.Run("fail - receive error when not all values are provided", func() { - var nilUuid uuid.UUID - contract := testdatagen.FetchOrMakeReContract(suite.DB(), testdatagen.Assertions{}) address := factory.BuildAddress(suite.DB(), nil, nil) - rateAreaId, err := models.FetchRateAreaID(suite.DB(), address.ID, nilUuid, contract.ID) + rateAreaId, err := models.FetchRateAreaID(suite.DB(), address.ID, nil, uuid.Nil) suite.Equal(uuid.Nil, rateAreaId) suite.Error(err) }) diff --git a/pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup.go b/pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup.go index b339fbf43dd..68d59f13d27 100644 --- a/pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup.go +++ b/pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup.go @@ -24,7 +24,7 @@ func (p PerUnitCentsLookup) lookup(appCtx appcontext.AppContext, s *ServiceItemP switch p.ServiceItem.ReService.Code { case models.ReServiceCodeIHPK: // IHPK: Need rate area ID for the pickup address - rateAreaID, err := models.FetchRateAreaID(appCtx.DB(), *p.MTOShipment.PickupAddressID, serviceID, contractID) + rateAreaID, err := models.FetchRateAreaID(appCtx.DB(), *p.MTOShipment.PickupAddressID, &serviceID, contractID) if err != nil { return "", fmt.Errorf("error fetching rate area id for shipment ID: %s and service ID %s: %s", p.MTOShipment.ID, serviceID, err) } @@ -43,7 +43,7 @@ func (p PerUnitCentsLookup) lookup(appCtx appcontext.AppContext, s *ServiceItemP case models.ReServiceCodeIHUPK: // IHUPK: Need rate area ID for the destination address - rateAreaID, err := models.FetchRateAreaID(appCtx.DB(), *p.MTOShipment.PickupAddressID, serviceID, contractID) + rateAreaID, err := models.FetchRateAreaID(appCtx.DB(), *p.MTOShipment.PickupAddressID, &serviceID, contractID) if err != nil { return "", fmt.Errorf("error fetching rate area id for shipment ID: %s and service ID %s: %s", p.MTOShipment.ID, serviceID, err) } @@ -62,11 +62,11 @@ func (p PerUnitCentsLookup) lookup(appCtx appcontext.AppContext, s *ServiceItemP case models.ReServiceCodeISLH: // ISLH: Need rate area IDs for origin and destination - originRateAreaID, err := models.FetchRateAreaID(appCtx.DB(), *p.MTOShipment.PickupAddressID, serviceID, contractID) + originRateAreaID, err := models.FetchRateAreaID(appCtx.DB(), *p.MTOShipment.PickupAddressID, &serviceID, contractID) if err != nil { return "", fmt.Errorf("error fetching rate area id for origin address for shipment ID: %s and service ID %s: %s", p.MTOShipment.ID, serviceID, err) } - destRateAreaID, err := models.FetchRateAreaID(appCtx.DB(), *p.MTOShipment.DestinationAddressID, serviceID, contractID) + destRateAreaID, err := models.FetchRateAreaID(appCtx.DB(), *p.MTOShipment.DestinationAddressID, &serviceID, contractID) if err != nil { return "", fmt.Errorf("error fetching rate area id for destination address for shipment ID: %s and service ID %s: %s", p.MTOShipment.ID, serviceID, err) } diff --git a/pkg/services/move_history/move_history_fetcher_test.go b/pkg/services/move_history/move_history_fetcher_test.go index b64c2bd3f4a..1aabbab0a2b 100644 --- a/pkg/services/move_history/move_history_fetcher_test.go +++ b/pkg/services/move_history/move_history_fetcher_test.go @@ -252,8 +252,10 @@ func (suite *MoveHistoryServiceSuite) TestMoveHistoryFetcherFunctionality() { auditHistoryContains := func(auditHistories models.AuditHistories, keyword string) func() (success bool) { return func() (success bool) { for _, record := range auditHistories { - if strings.Contains(*record.ChangedData, keyword) { - return true + if record.ChangedData != nil { + if strings.Contains(*record.ChangedData, keyword) { + return true + } } } return false diff --git a/pkg/services/shipment_address_update/shipment_address_update_requester.go b/pkg/services/shipment_address_update/shipment_address_update_requester.go index 6d99f05a3a1..53d845af020 100644 --- a/pkg/services/shipment_address_update/shipment_address_update_requester.go +++ b/pkg/services/shipment_address_update/shipment_address_update_requester.go @@ -38,10 +38,10 @@ func NewShipmentAddressUpdateRequester(planner route.Planner, addressCreator ser } } -func (f *shipmentAddressUpdateRequester) isAddressChangeDistanceOver50(appCtx appcontext.AppContext, addressUpdate models.ShipmentAddressUpdate) (bool, error) { +func (f *shipmentAddressUpdateRequester) isAddressChangeDistanceOver50(appCtx appcontext.AppContext, addressUpdate models.ShipmentAddressUpdate, isInternationalShipment bool) (bool, error) { - //We calculate and set the distance between the old and new address - distance, err := f.planner.ZipTransitDistance(appCtx, addressUpdate.OriginalAddress.PostalCode, addressUpdate.NewAddress.PostalCode, false, false) + // We calculate and set the distance between the old and new address + distance, err := f.planner.ZipTransitDistance(appCtx, addressUpdate.OriginalAddress.PostalCode, addressUpdate.NewAddress.PostalCode, false, isInternationalShipment) if err != nil { return false, err } @@ -52,32 +52,62 @@ func (f *shipmentAddressUpdateRequester) isAddressChangeDistanceOver50(appCtx ap return true, nil } -func (f *shipmentAddressUpdateRequester) doesDeliveryAddressUpdateChangeServiceArea(appCtx appcontext.AppContext, contractID uuid.UUID, originalDeliveryAddress models.Address, newDeliveryAddress models.Address) (bool, error) { - var existingServiceArea models.ReZip3 - var actualServiceArea models.ReZip3 +func (f *shipmentAddressUpdateRequester) doesDeliveryAddressUpdateChangeServiceOrRateArea(appCtx appcontext.AppContext, contractID uuid.UUID, originalDeliveryAddress models.Address, newDeliveryAddress models.Address, shipment models.MTOShipment) (bool, error) { + // international shipments find their rate areas differently than domestic + if shipment.MarketCode == models.MarketCodeInternational { + // we already have the origin address in the db so we can check the rate area using the db func + originalRateArea, err := models.FetchRateAreaID(appCtx.DB(), originalDeliveryAddress.ID, nil, contractID) + if err != nil || originalRateArea == uuid.Nil { + return false, err + } + // since the new address isn't created yet we can't use the db func since it doesn't have an id, + // we need to manually find the rate area using the postal code + var updateRateArea uuid.UUID + newRateArea, err := models.FetchOconusRateArea(appCtx.DB(), newDeliveryAddress.PostalCode) + if err != nil && err != sql.ErrNoRows { + return false, err + } else if err == sql.ErrNoRows { // if we got no rows then the new address is likely CONUS + newRateArea, err := models.FetchConusRateAreaByPostalCode(appCtx.DB(), newDeliveryAddress.PostalCode, contractID) + if err != nil && err != sql.ErrNoRows { + return false, err + } + updateRateArea = newRateArea.ID + } else { + updateRateArea = newRateArea.RateAreaId + } + // if these are different, we need the TOO to approve this request since it will change ISLH pricing + if originalRateArea != updateRateArea { + return true, nil + } else { + return false, nil + } + } else { + var existingServiceArea models.ReZip3 + var actualServiceArea models.ReZip3 - originalZip := originalDeliveryAddress.PostalCode[0:3] - destinationZip := newDeliveryAddress.PostalCode[0:3] + originalZip := originalDeliveryAddress.PostalCode[0:3] + destinationZip := newDeliveryAddress.PostalCode[0:3] - if originalZip == destinationZip { - // If the ZIP hasn't changed, we must be in the same service area - return false, nil - } + if originalZip == destinationZip { + // If the ZIP hasn't changed, we must be in the same service area + return false, nil + } - err := appCtx.DB().Where("zip3 = ?", originalZip).Where("contract_id = ?", contractID).First(&existingServiceArea) - if err != nil { - return false, err - } + err := appCtx.DB().Where("zip3 = ?", originalZip).Where("contract_id = ?", contractID).First(&existingServiceArea) + if err != nil { + return false, err + } - err = appCtx.DB().Where("zip3 = ?", destinationZip).Where("contract_id = ?", contractID).First(&actualServiceArea) - if err != nil { - return false, err - } + err = appCtx.DB().Where("zip3 = ?", destinationZip).Where("contract_id = ?", contractID).First(&actualServiceArea) + if err != nil { + return false, err + } - if existingServiceArea.DomesticServiceAreaID != actualServiceArea.DomesticServiceAreaID { - return true, nil + if existingServiceArea.DomesticServiceAreaID != actualServiceArea.DomesticServiceAreaID { + return true, nil + } + return false, nil } - return false, nil } func (f *shipmentAddressUpdateRequester) doesDeliveryAddressUpdateChangeMileageBracket(appCtx appcontext.AppContext, originalPickupAddress models.Address, originalDeliveryAddress, newDeliveryAddress models.Address) (bool, error) { @@ -251,6 +281,7 @@ func (f *shipmentAddressUpdateRequester) RequestShipmentDeliveryAddressUpdate(ap if eTag != etag.GenerateEtag(shipment.UpdatedAt) { return nil, apperror.NewPreconditionFailedError(shipmentID, nil) } + isInternationalShipment := shipment.MarketCode == models.MarketCodeInternational shipmentHasApprovedDestSIT := f.doesShipmentContainApprovedDestinationSIT(shipment) @@ -333,12 +364,13 @@ func (f *shipmentAddressUpdateRequester) RequestShipmentDeliveryAddressUpdate(ap return nil, err } - updateNeedsTOOReview, err := f.doesDeliveryAddressUpdateChangeServiceArea(appCtx, contract.ID, addressUpdate.OriginalAddress, newAddress) + updateNeedsTOOReview, err := f.doesDeliveryAddressUpdateChangeServiceOrRateArea(appCtx, contract.ID, addressUpdate.OriginalAddress, newAddress, shipment) if err != nil { return nil, err } - if !updateNeedsTOOReview { + // international shipments don't need to be concerned with shorthaul/linehaul + if !updateNeedsTOOReview && !isInternationalShipment { if shipment.ShipmentType == models.MTOShipmentTypeHHG { updateNeedsTOOReview, err = f.doesDeliveryAddressUpdateChangeShipmentPricingType(*shipment.PickupAddress, addressUpdate.OriginalAddress, newAddress) if err != nil { @@ -354,7 +386,7 @@ func (f *shipmentAddressUpdateRequester) RequestShipmentDeliveryAddressUpdate(ap } } - if !updateNeedsTOOReview { + if !updateNeedsTOOReview && !isInternationalShipment { if shipment.ShipmentType == models.MTOShipmentTypeHHG { updateNeedsTOOReview, err = f.doesDeliveryAddressUpdateChangeMileageBracket(appCtx, *shipment.PickupAddress, addressUpdate.OriginalAddress, newAddress) if err != nil { @@ -371,7 +403,7 @@ func (f *shipmentAddressUpdateRequester) RequestShipmentDeliveryAddressUpdate(ap } if !updateNeedsTOOReview { - updateNeedsTOOReview, err = f.isAddressChangeDistanceOver50(appCtx, addressUpdate) + updateNeedsTOOReview, err = f.isAddressChangeDistanceOver50(appCtx, addressUpdate, isInternationalShipment) if err != nil { return nil, err } @@ -390,7 +422,7 @@ func (f *shipmentAddressUpdateRequester) RequestShipmentDeliveryAddressUpdate(ap return apperror.NewQueryError("ShipmentAddressUpdate", txnErr, "error saving shipment address update request") } - //Get the move + // Get the move var move models.Move err := txnAppCtx.DB().Find(&move, shipment.MoveTaskOrderID) if err != nil { @@ -463,6 +495,7 @@ func (f *shipmentAddressUpdateRequester) ReviewShipmentAddressChange(appCtx appc } shipment = addressUpdate.Shipment + isInternationalShipment := shipment.MarketCode == models.MarketCodeInternational if tooApprovalStatus == models.ShipmentAddressUpdateStatusApproved { queryBuilder := query.NewQueryBuilder() @@ -472,6 +505,7 @@ func (f *shipmentAddressUpdateRequester) ReviewShipmentAddressChange(appCtx appc addressUpdate.Status = models.ShipmentAddressUpdateStatusApproved addressUpdate.OfficeRemarks = &tooRemarks shipment.DestinationAddress = &addressUpdate.NewAddress + shipment.DestinationAddressID = &addressUpdate.NewAddressID var haulPricingTypeHasChanged bool if shipment.ShipmentType == models.MTOShipmentTypeHHG { @@ -526,7 +560,7 @@ func (f *shipmentAddressUpdateRequester) ReviewShipmentAddressChange(appCtx appc // If the pricing type has changed then we automatically reject the DLH or DSH service item on the shipment since it is now inaccurate var approvedPaymentRequestsExistsForServiceItem bool - if haulPricingTypeHasChanged && len(shipment.MTOServiceItems) > 0 { + if haulPricingTypeHasChanged && len(shipment.MTOServiceItems) > 0 && !isInternationalShipment { serviceItems := shipment.MTOServiceItems autoRejectionRemark := "Automatically rejected due to change in destination address affecting the ZIP code qualification for short haul / line haul." var regeneratedServiceItems models.MTOServiceItems @@ -630,7 +664,7 @@ func (f *shipmentAddressUpdateRequester) ReviewShipmentAddressChange(appCtx appc // if the shipment has an estimated weight, we need to update the service item pricing since we know the distances have changed // this only applies to international shipments that the TOO is approving the address change for if shipment.PrimeEstimatedWeight != nil && - shipment.MarketCode == models.MarketCodeInternational && + isInternationalShipment && tooApprovalStatus == models.ShipmentAddressUpdateStatusApproved { portZip, portType, err := models.GetPortLocationInfoForShipment(appCtx.DB(), shipment.ID) if err != nil { diff --git a/pkg/services/shipment_address_update/shipment_address_update_requester_test.go b/pkg/services/shipment_address_update/shipment_address_update_requester_test.go index e209730dee7..7a7e12a3733 100644 --- a/pkg/services/shipment_address_update/shipment_address_update_requester_test.go +++ b/pkg/services/shipment_address_update/shipment_address_update_requester_test.go @@ -102,7 +102,7 @@ func (suite *ShipmentAddressUpdateServiceSuite) TestCreateApprovedShipmentAddres moveRouter := moveservices.NewMoveRouter() addressUpdateRequester := NewShipmentAddressUpdateRequester(mockPlanner, addressCreator, moveRouter) - suite.Run("Successfully create ShipmentAddressUpdate", func() { + suite.Run("Successfully create ShipmentAddressUpdate for a domestic shipment", func() { mockPlanner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), "90210", @@ -144,6 +144,142 @@ func (suite *ShipmentAddressUpdateServiceSuite) TestCreateApprovedShipmentAddres suite.Equal(newAddress.City, updatedShipment.DestinationAddress.City) }) + suite.Run("Successfully create ShipmentAddressUpdate for an international shipment that requires approval", func() { + mockPlanner.On("ZipTransitDistance", + mock.AnythingOfType("*appcontext.appContext"), + "90210", + "94535", + false, + false, + ).Return(2500, nil).Twice() + mockPlanner.On("ZipTransitDistance", + mock.AnythingOfType("*appcontext.appContext"), + "94535", + "94535", + false, + false, + ).Return(2500, nil).Once() + move := setupTestData() + + pickupAddress := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "Tester Address", + City: "Des Moines", + State: "IA", + PostalCode: "50314", + IsOconus: models.BoolPointer(false), + }, + }, + }, nil) + destinationAddress := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "JBER", + City: "Anchorage", + State: "AK", + PostalCode: "99505", + IsOconus: models.BoolPointer(true), + }, + }, + }, nil) + shipment := factory.BuildMTOShipmentWithMove(&move, suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + MarketCode: models.MarketCodeInternational, + PickupAddressID: &pickupAddress.ID, + DestinationAddressID: &destinationAddress.ID, + }, + }, + }, nil) + + newAddress := models.Address{ + StreetAddress1: "Colder Ave.", + City: "Klawock", + State: "AK", + PostalCode: "99925", + } + suite.NotEmpty(move.MTOShipments) + update, err := addressUpdateRequester.RequestShipmentDeliveryAddressUpdate(suite.AppContextForTest(), shipment.ID, newAddress, "we really need to change the address", etag.GenerateEtag(shipment.UpdatedAt)) + suite.NoError(err) + suite.NotNil(update) + suite.Equal(models.ShipmentAddressUpdateStatusRequested, update.Status) + + // Make sure the destination address on the shipment was NOT updated + var updatedShipment models.MTOShipment + err = suite.DB().EagerPreload("DestinationAddress").Find(&updatedShipment, shipment.ID) + suite.NoError(err) + + suite.NotEqual(newAddress.StreetAddress1, updatedShipment.DestinationAddress.StreetAddress1) + suite.NotEqual(newAddress.PostalCode, updatedShipment.DestinationAddress.PostalCode) + suite.NotEqual(newAddress.City, updatedShipment.DestinationAddress.City) + }) + + suite.Run("Successfully create ShipmentAddressUpdate for an international shipment that requires approval", func() { + mockPlanner.On("ZipTransitDistance", + mock.AnythingOfType("*appcontext.appContext"), + "99505", + "99506", + false, + true, + ).Return(49, nil) + move := setupTestData() + + pickupAddress := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "Tester Address", + City: "Des Moines", + State: "IA", + PostalCode: "50314", + IsOconus: models.BoolPointer(false), + }, + }, + }, nil) + destinationAddress := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "JBER", + City: "Anchorage", + State: "AK", + PostalCode: "99505", + IsOconus: models.BoolPointer(true), + }, + }, + }, nil) + shipment := factory.BuildMTOShipmentWithMove(&move, suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + MarketCode: models.MarketCodeInternational, + PickupAddressID: &pickupAddress.ID, + DestinationAddressID: &destinationAddress.ID, + }, + }, + }, nil) + + // this shouldn't change the rate area + newAddress := models.Address{ + StreetAddress1: "Elsewhere Ave.", + City: "Anchorage", + State: "AK", + PostalCode: "99506", + } + suite.NotEmpty(move.MTOShipments) + update, err := addressUpdateRequester.RequestShipmentDeliveryAddressUpdate(suite.AppContextForTest(), shipment.ID, newAddress, "we really need to change the address", etag.GenerateEtag(shipment.UpdatedAt)) + suite.NoError(err) + suite.NotNil(update) + suite.Equal(models.ShipmentAddressUpdateStatusApproved, update.Status) + + // Make sure the destination address on the shipment was updated + var updatedShipment models.MTOShipment + err = suite.DB().EagerPreload("DestinationAddress").Find(&updatedShipment, shipment.ID) + suite.NoError(err) + + suite.Equal(newAddress.StreetAddress1, updatedShipment.DestinationAddress.StreetAddress1) + suite.Equal(newAddress.PostalCode, updatedShipment.DestinationAddress.PostalCode) + suite.Equal(newAddress.City, updatedShipment.DestinationAddress.City) + }) + suite.Run("Update with invalid etag should fail", func() { move := setupTestData() shipment := factory.BuildMTOShipmentWithMove(&move, suite.DB(), nil, nil) diff --git a/src/components/Office/AddressUpdatePreview/AddressUpdatePreview.jsx b/src/components/Office/AddressUpdatePreview/AddressUpdatePreview.jsx index 74a2ac1e06b..dc7e9d3a9d4 100644 --- a/src/components/Office/AddressUpdatePreview/AddressUpdatePreview.jsx +++ b/src/components/Office/AddressUpdatePreview/AddressUpdatePreview.jsx @@ -9,23 +9,35 @@ import DataTable from 'components/DataTable/index'; import { formatTwoLineAddress } from 'utils/shipmentDisplay'; import DataTableWrapper from 'components/DataTableWrapper'; import { ShipmentAddressUpdateShape } from 'types'; +import { MARKET_CODES } from 'shared/constants'; -const AddressUpdatePreview = ({ deliveryAddressUpdate }) => { +const AddressUpdatePreview = ({ deliveryAddressUpdate, shipment }) => { const { originalAddress, newAddress, contractorRemarks } = deliveryAddressUpdate; const newSitMileage = deliveryAddressUpdate.newSitDistanceBetween; + const { marketCode } = shipment; return (

Delivery Address

- - If approved, the requested update to the delivery address will change one or all of the following: - Service area. - Mileage bracket for direct delivery. - - ZIP3 resulting in Domestic Shorthaul (DSH) changing to Domestic Linehaul (DLH) or vice versa. + {marketCode === MARKET_CODES.DOMESTIC ? ( + + If approved, the requested update to the delivery address will change one or all of the following: + Service area. + Mileage bracket for direct delivery. + + ZIP3 resulting in Domestic Shorthaul (DSH) changing to Domestic Linehaul (DLH) or vice versa. + + Approvals will result in updated pricing for this shipment. Customer may be subject to excess costs. + + ) : ( + + If approved, the requested update to the delivery address will change one or all of the following: + The rate area for the international shipment destination address. + Pricing for international shipping & linehaul. + Pricing for POD Fuel Surcharge (if applicable). + Approvals will result in updated pricing for this shipment. Customer may be subject to excess costs. - Approvals will result in updated pricing for this shipment. Customer may be subject to excess costs. - + )} {newSitMileage > 50 ? ( diff --git a/src/components/Office/AddressUpdatePreview/AddressUpdatePreview.stories.jsx b/src/components/Office/AddressUpdatePreview/AddressUpdatePreview.stories.jsx index a77d9fcc672..46273140dca 100644 --- a/src/components/Office/AddressUpdatePreview/AddressUpdatePreview.stories.jsx +++ b/src/components/Office/AddressUpdatePreview/AddressUpdatePreview.stories.jsx @@ -2,6 +2,8 @@ import React from 'react'; import AddressUpdatePreview from './AddressUpdatePreview'; +import { MARKET_CODES } from 'shared/constants'; + const mockDeliveryAddressUpdate = { contractorRemarks: 'Test Contractor Remark', id: 'c49f7921-5a6e-46b4-bb39-022583574453', @@ -26,6 +28,10 @@ const mockDeliveryAddressUpdate = { status: 'REQUESTED', }; +const mockShipment = { + marketCode: MARKET_CODES.DOMESTIC, +}; + const destinationSITServiceItems = ['DDDSIT', 'DDFSIT', 'DDASIT', 'DDSFSC']; export default { @@ -39,6 +45,7 @@ export const preview = { ,
diff --git a/src/components/Office/AddressUpdatePreview/AddressUpdatePreview.test.jsx b/src/components/Office/AddressUpdatePreview/AddressUpdatePreview.test.jsx index c8578937e06..d95d3d0fef3 100644 --- a/src/components/Office/AddressUpdatePreview/AddressUpdatePreview.test.jsx +++ b/src/components/Office/AddressUpdatePreview/AddressUpdatePreview.test.jsx @@ -3,6 +3,8 @@ import { render, screen, waitFor } from '@testing-library/react'; import AddressUpdatePreview from './AddressUpdatePreview'; +import { MARKET_CODES } from 'shared/constants'; + const mockDeliveryAddressUpdateWithoutSIT = { contractorRemarks: 'Test Contractor Remark', id: 'c49f7921-5a6e-46b4-bb39-022583574453', @@ -66,9 +68,53 @@ const mockDeliveryAddressUpdateWithSIT = { }, status: 'REQUESTED', }; + +const domesticShipment = { + marketCode: MARKET_CODES.DOMESTIC, +}; + +const internationalShipment = { + marketCode: MARKET_CODES.INTERNATIONAL, +}; describe('AddressUpdatePreview', () => { - it('renders all of the address preview information', async () => { - render(); + it('renders all of the address preview information for an international shipment', async () => { + render( + , + ); + // Heading and alert present + expect(screen.getByRole('heading', { name: 'Delivery Address' })).toBeInTheDocument(); + expect(screen.getByTestId('alert')).toBeInTheDocument(); + expect(screen.getByTestId('alert')).toHaveTextContent( + 'If approved, the requested update to the delivery address will change one or all of the following:' + + 'The rate area for the international shipment destination address.' + + 'Pricing for international shipping & linehaul.' + + 'Pricing for POD Fuel Surcharge (if applicable).' + + 'Approvals will result in updated pricing for this shipment. Customer may be subject to excess costs.', + ); + const addressChangePreview = screen.getByTestId('address-change-preview'); + expect(addressChangePreview).toBeInTheDocument(); + const addresses = screen.getAllByTestId('two-line-address'); + expect(addresses).toHaveLength(2); + // Original Address + expect(addressChangePreview).toHaveTextContent('Original Delivery Address'); + expect(addresses[0]).toHaveTextContent('987 Any Avenue'); + expect(addresses[0]).toHaveTextContent('Fairfield, CA 94535'); + // New Address + expect(addressChangePreview).toHaveTextContent('Requested Delivery Address'); + expect(addresses[1]).toHaveTextContent('123 Any Street'); + expect(addresses[1]).toHaveTextContent('Beverly Hills, CA 90210'); + // Request details (contractor remarks) + expect(addressChangePreview).toHaveTextContent('Update request details'); + expect(addressChangePreview).toHaveTextContent('Contractor remarks: Test Contractor Remark'); + }); + + it('renders all of the address preview information for a domestic shipment', async () => { + render( + , + ); // Heading and alert present expect(screen.getByRole('heading', { name: 'Delivery Address' })).toBeInTheDocument(); expect(screen.getByTestId('alert')).toBeInTheDocument(); @@ -102,8 +148,11 @@ describe('AddressUpdatePreview', () => { expect(screen.queryByTestId('destSitAlert')).not.toBeInTheDocument(); }); }); + it('renders the destination SIT alert when shipment contains dest SIT service items', () => { - render(); + render( + , + ); // Heading and alert present expect(screen.getByRole('heading', { name: 'Delivery Address' })).toBeInTheDocument(); expect(screen.getByTestId('destSitAlert')).toBeInTheDocument(); diff --git a/src/components/Office/ShipmentAddressUpdateReviewRequestModal/ShipmentAddressUpdateReviewRequestModal.jsx b/src/components/Office/ShipmentAddressUpdateReviewRequestModal/ShipmentAddressUpdateReviewRequestModal.jsx index 4325db1565f..cf1575395de 100644 --- a/src/components/Office/ShipmentAddressUpdateReviewRequestModal/ShipmentAddressUpdateReviewRequestModal.jsx +++ b/src/components/Office/ShipmentAddressUpdateReviewRequestModal/ShipmentAddressUpdateReviewRequestModal.jsx @@ -60,10 +60,7 @@ export const ShipmentAddressUpdateReviewRequestModal = ({ return (
- +

Review Request

diff --git a/src/components/Office/ShipmentForm/ShipmentForm.jsx b/src/components/Office/ShipmentForm/ShipmentForm.jsx index bb352f02bf6..96485a69be5 100644 --- a/src/components/Office/ShipmentForm/ShipmentForm.jsx +++ b/src/components/Office/ShipmentForm/ShipmentForm.jsx @@ -788,6 +788,7 @@ const ShipmentForm = (props) => { if (status === ADDRESS_UPDATE_STATUS.APPROVED) { setValues({ ...values, + hasDeliveryAddress: 'yes', delivery: { ...values.delivery, address: mtoShipment.deliveryAddressUpdate.newAddress, diff --git a/src/constants/MoveHistory/Database/Tables.js b/src/constants/MoveHistory/Database/Tables.js index e097f569adf..f860416dda7 100644 --- a/src/constants/MoveHistory/Database/Tables.js +++ b/src/constants/MoveHistory/Database/Tables.js @@ -19,4 +19,5 @@ export default { moving_expenses: 'moving_expenses', progear_weight_tickets: 'progear_weight_tickets', gsr_appeals: 'gsr_appeals', + shipment_address_updates: 'shipment_address_updates', }; diff --git a/src/constants/MoveHistory/EventTemplates/ReviewShipmentAddressUpdate/reviewShipmentAddressUpdate.jsx b/src/constants/MoveHistory/EventTemplates/ReviewShipmentAddressUpdate/reviewShipmentAddressUpdate.jsx new file mode 100644 index 00000000000..3597791218a --- /dev/null +++ b/src/constants/MoveHistory/EventTemplates/ReviewShipmentAddressUpdate/reviewShipmentAddressUpdate.jsx @@ -0,0 +1,31 @@ +import React from 'react'; + +import o from 'constants/MoveHistory/UIDisplay/Operations'; +import a from 'constants/MoveHistory/Database/Actions'; +import t from 'constants/MoveHistory/Database/Tables'; + +export default { + action: a.UPDATE, + eventName: o.reviewShipmentAddressUpdate, + tableName: t.shipment_address_updates, + getEventNameDisplay: () => { + return 'Shipment Destination Address Request'; + }, + getDetails: ({ changedValues }) => { + if (changedValues.status === 'APPROVED') { + return ( +
+ Status: Approved +
+ ); + } + if (changedValues.status === 'REJECTED') { + return ( +
+ Status: Rejected +
+ ); + } + return null; + }, +}; diff --git a/src/constants/MoveHistory/EventTemplates/ReviewShipmentAddressUpdate/reviewShipmentAddressUpdate.test.jsx b/src/constants/MoveHistory/EventTemplates/ReviewShipmentAddressUpdate/reviewShipmentAddressUpdate.test.jsx new file mode 100644 index 00000000000..8077e4f324e --- /dev/null +++ b/src/constants/MoveHistory/EventTemplates/ReviewShipmentAddressUpdate/reviewShipmentAddressUpdate.test.jsx @@ -0,0 +1,82 @@ +import { render, screen } from '@testing-library/react'; + +import getTemplate from 'constants/MoveHistory/TemplateManager'; +import o from 'constants/MoveHistory/UIDisplay/Operations'; +import a from 'constants/MoveHistory/Database/Actions'; +import t from 'constants/MoveHistory/Database/Tables'; +import e from 'constants/MoveHistory/EventTemplates/ReviewShipmentAddressUpdate/reviewShipmentAddressUpdate'; + +describe('when given a Review Shipment Address Update history record', () => { + const context = [ + { + name: 'Shipment', + }, + ]; + + it('displays the correct event name', () => { + const historyRecord = { + action: a.UPDATE, + changedValues: { + status: 'APPROVED', + }, + context, + eventName: o.reviewShipmentAddressUpdate, + tableName: t.shipment_address_updates, + }; + + const template = getTemplate(historyRecord); + expect(template).toMatchObject(e); + expect(template.getEventNameDisplay(historyRecord)).toEqual('Shipment Destination Address Request'); + }); + + it('displays the status as "Approved"', () => { + const historyRecord = { + action: a.UPDATE, + changedValues: { + status: 'APPROVED', + }, + context, + eventName: o.reviewShipmentAddressUpdate, + tableName: t.shipment_address_updates, + }; + + const template = getTemplate(historyRecord); + + render(template.getDetails(historyRecord)); + expect(screen.getByText('Status')).toBeInTheDocument(); + expect(screen.getByText(/Approved/)).toBeInTheDocument(); + }); + + it('displays the status as "Rejected"', () => { + const historyRecord = { + action: a.UPDATE, + changedValues: { + status: 'REJECTED', + }, + context, + eventName: o.reviewShipmentAddressUpdate, + tableName: t.shipment_address_updates, + }; + + const template = getTemplate(historyRecord); + + render(template.getDetails(historyRecord)); + expect(screen.getByText('Status')).toBeInTheDocument(); + expect(screen.getByText(/Rejected/)).toBeInTheDocument(); + }); + + it('returns null if the status is not "APPROVED" or "REJECTED"', () => { + const historyRecord = { + action: a.UPDATE, + changedValues: { + status: 'PENDING', + }, + context, + eventName: o.reviewShipmentAddressUpdate, + tableName: t.shipment_address_updates, + }; + + const template = getTemplate(historyRecord); + expect(template.getDetails(historyRecord)).toBeNull(); + }); +}); diff --git a/src/constants/MoveHistory/EventTemplates/UpdateMTOServiceItem/updateServiceItemPricingAndWeights.jsx b/src/constants/MoveHistory/EventTemplates/UpdateMTOServiceItem/updateServiceItemPricingAndWeights.jsx new file mode 100644 index 00000000000..1397c699d7b --- /dev/null +++ b/src/constants/MoveHistory/EventTemplates/UpdateMTOServiceItem/updateServiceItemPricingAndWeights.jsx @@ -0,0 +1,42 @@ +import React from 'react'; + +import o from 'constants/MoveHistory/UIDisplay/Operations'; +import a from 'constants/MoveHistory/Database/Actions'; +import t from 'constants/MoveHistory/Database/Tables'; +import { formatCents, formatWeight } from 'utils/formatters'; + +export default { + action: a.UPDATE, + eventName: o.updateMTOShipment, + tableName: t.mto_service_items, + getEventNameDisplay: ({ changedValues }) => { + if (changedValues.pricing_estimate) { + return 'Service item estimated price updated'; + } + if (changedValues.estimated_weight) { + return 'Service item estimated weight updated'; + } + return 'Service item updated'; + }, + getDetails: ({ changedValues, context }) => { + if (changedValues.pricing_estimate) { + return ( +
+ Service item: {context[0].name} +
+ Estimated Price: ${formatCents(changedValues.pricing_estimate)} +
+ ); + } + if (changedValues.estimated_weight) { + return ( +
+ Service item: {context[0].name} +
+ Estimated weight: {formatWeight(changedValues.estimated_weight)} +
+ ); + } + return null; + }, +}; diff --git a/src/constants/MoveHistory/EventTemplates/UpdateMTOServiceItem/updateServiceItemPricingAndWeights.test.jsx b/src/constants/MoveHistory/EventTemplates/UpdateMTOServiceItem/updateServiceItemPricingAndWeights.test.jsx new file mode 100644 index 00000000000..eae1e8fe630 --- /dev/null +++ b/src/constants/MoveHistory/EventTemplates/UpdateMTOServiceItem/updateServiceItemPricingAndWeights.test.jsx @@ -0,0 +1,75 @@ +import { render, screen } from '@testing-library/react'; + +import getTemplate from 'constants/MoveHistory/TemplateManager'; +import o from 'constants/MoveHistory/UIDisplay/Operations'; +import a from 'constants/MoveHistory/Database/Actions'; +import t from 'constants/MoveHistory/Database/Tables'; + +describe('when given an UpdateMTOServiceItem history record with pricing/weight changes', () => { + const context = [ + { + name: 'International Shipping & Linehaul', + shipment_type: 'HHG', + shipment_locator: 'RQ38D4-01', + shipment_id_abbr: 'a1b2c', + }, + ]; + + it('correctly matches the service item price update event', () => { + const historyRecord = { + action: a.UPDATE, + changedValues: { + pricing_estimate: 150000, + }, + context, + eventName: o.updateMTOShipment, + tableName: t.mto_service_items, + }; + + const template = getTemplate(historyRecord); + expect(template.getEventNameDisplay(historyRecord)).toEqual('Service item estimated price updated'); + + render(template.getDetails(historyRecord)); + expect(screen.getByText('Service item')).toBeInTheDocument(); + expect(screen.getByText(/International Shipping & Linehaul/)).toBeInTheDocument(); + expect(screen.getByText('Estimated Price')).toBeInTheDocument(); + expect(screen.getByText(/\$1,500\.00/)).toBeInTheDocument(); + }); + + it('correctly matches the service item weight update event', () => { + const historyRecord = { + action: a.UPDATE, + changedValues: { + estimated_weight: 1000, + }, + context, + eventName: o.updateMTOShipment, + tableName: t.mto_service_items, + }; + + const template = getTemplate(historyRecord); + expect(template.getEventNameDisplay(historyRecord)).toEqual('Service item estimated weight updated'); + + render(template.getDetails(historyRecord)); + expect(screen.getByText('Service item')).toBeInTheDocument(); + expect(screen.getByText(/International Shipping & Linehaul/)).toBeInTheDocument(); + expect(screen.getByText('Estimated weight')).toBeInTheDocument(); + expect(screen.getByText(/1,000 lbs/)).toBeInTheDocument(); + }); + + it('returns "Service item updated" for unknown changes', () => { + const historyRecord = { + action: a.UPDATE, + changedValues: { + unknownField: 'some value', + }, + context, + eventName: o.updateMTOShipment, + tableName: t.mto_service_items, + }; + + const template = getTemplate(historyRecord); + expect(template.getEventNameDisplay(historyRecord)).toEqual('Service item updated'); + expect(template.getDetails(historyRecord)).toBeNull(); + }); +}); diff --git a/src/constants/MoveHistory/EventTemplates/index.js b/src/constants/MoveHistory/EventTemplates/index.js index 4da6eeba4a1..9036e31baaf 100644 --- a/src/constants/MoveHistory/EventTemplates/index.js +++ b/src/constants/MoveHistory/EventTemplates/index.js @@ -30,6 +30,7 @@ export { default as updateBillableWeightAsTIO } from './UpdateBillableWeight/upd export { default as updateBillableWeightRemarksAsTIO } from './UpdateBillableWeight/updateBillableWeightRemarksAsTIO'; export { default as updateMoveTaskOrderStatus } from './UpdateMoveTaskOrderStatus/updateMoveTaskOrderStatus'; export { default as updateMTOServiceItem } from './UpdateMTOServiceItem/updateMTOServiceItem'; +export { default as updateServiceItemPricingAndWeights } from './UpdateMTOServiceItem/updateServiceItemPricingAndWeights'; export { default as updateMTOShipment } from './UpdateMTOShipment/updateMTOShipment'; export { default as updateMTOShipmentAgent } from './UpdateMTOShipment/updateMTOShipmentAgent'; export { default as updateMTOShipmentDeprecatePaymentRequest } from './UpdateMTOShipment/updateMTOShipmentDeprecatePaymentRequest'; @@ -113,3 +114,4 @@ export { default as cancelMovePPMShipments } from './CancelMove/CancelMovePPMShi export { default as updateAssignedOfficeUser } from './UpdateAssignedOfficeUser/UpdateAssignedOfficeUser'; export { default as deleteAssignedOfficeUser } from './UpdateAssignedOfficeUser/DeleteAssignedOfficeUser'; export { default as UpdatePaymentRequestStatusMoves } from './UpdatePaymentRequestStatus/UpdatePaymentRequestStatusMoves'; +export { default as reviewShipmentAddressUpdate } from './ReviewShipmentAddressUpdate/reviewShipmentAddressUpdate'; diff --git a/src/constants/MoveHistory/UIDisplay/Operations.js b/src/constants/MoveHistory/UIDisplay/Operations.js index bd72a4a291f..b3a68442f79 100644 --- a/src/constants/MoveHistory/UIDisplay/Operations.js +++ b/src/constants/MoveHistory/UIDisplay/Operations.js @@ -66,6 +66,7 @@ export default { addAppealToViolation: 'addAppealToViolation', // ghc.yaml addAppealToSeriousIncident: 'addAppealToSeriousIncident', // ghc.yaml cancelMove: 'cancelMove', // internal.yaml + reviewShipmentAddressUpdate: 'reviewShipmentAddressUpdate', // ghc.yaml updateAssignedOfficeUser: 'updateAssignedOfficeUser', // ghc.yaml deleteAssignedOfficeUser: 'deleteAssignedOfficeUser', // ghc.yaml }; diff --git a/swagger-def/ghc.yaml b/swagger-def/ghc.yaml index e95349bcc3c..3119666e431 100644 --- a/swagger-def/ghc.yaml +++ b/swagger-def/ghc.yaml @@ -1421,6 +1421,7 @@ paths: $ref: '#/responses/ServerError' tags: - shipment + - shipment_address_updates description: This endpoint is used to approve a address update request. Office remarks are required. Approving the address update will update the Destination Final Address of the associated service item operationId: reviewShipmentAddressUpdate diff --git a/swagger/ghc.yaml b/swagger/ghc.yaml index 23c28fdb556..5316218cb28 100644 --- a/swagger/ghc.yaml +++ b/swagger/ghc.yaml @@ -1474,6 +1474,7 @@ paths: $ref: '#/responses/ServerError' tags: - shipment + - shipment_address_updates description: >- This endpoint is used to approve a address update request. Office remarks are required. Approving the address update will update the