From 9210233dcfdd0a9d5f9ed429f0971f32311ddd87 Mon Sep 17 00:00:00 2001 From: Daniel Jordan Date: Tue, 7 Jan 2025 21:11:34 +0000 Subject: [PATCH 1/9] initial commit, workin on it --- pkg/models/re_contract.go | 32 ++++++++++ pkg/models/re_rate_area.go | 4 +- pkg/models/re_rate_area_test.go | 6 +- .../per_unit_cents_lookup.go | 8 +-- .../shipment_address_update_requester.go | 59 ++++++++++++------- 5 files changed, 78 insertions(+), 31 deletions(-) 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_rate_area.go b/pkg/models/re_rate_area.go index 7b613b42a28..72ae7116ab7 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 && serviceID != 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 { 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/shipment_address_update/shipment_address_update_requester.go b/pkg/services/shipment_address_update/shipment_address_update_requester.go index a0ed36fccdd..1c06fb790a8 100644 --- a/pkg/services/shipment_address_update/shipment_address_update_requester.go +++ b/pkg/services/shipment_address_update/shipment_address_update_requester.go @@ -52,32 +52,49 @@ 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) { + if shipment.MarketCode == models.MarketCodeInternational { + // we need to make sure we didn't change rate areas + originalRateArea, err := models.FetchRateAreaID(appCtx.DB(), originalDeliveryAddress.ID, nil, contractID) + if err != nil || originalRateArea == uuid.Nil { + return false, err + } + newRateArea, err := models.FetchRateAreaID(appCtx.DB(), newDeliveryAddress.ID, nil, contractID) + if err != nil || newRateArea == uuid.Nil { + return false, err + } + if originalRateArea != newRateArea { + 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) { @@ -333,7 +350,7 @@ 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 } From 67c56e16a53ab63f7dbda489589bdb7d5f28d670 Mon Sep 17 00:00:00 2001 From: Daniel Jordan Date: Wed, 8 Jan 2025 17:09:26 +0000 Subject: [PATCH 2/9] added functionality, waiting on answers to questions for 50 mile changes for international shipments --- pkg/models/re_oconus_rate_areas.go | 14 +++++++ pkg/models/re_rate_area.go | 16 +++++++- .../shipment_address_update_requester.go | 37 +++++++++++++------ .../AddressUpdatePreview.jsx | 29 ++++++++++----- ...hipmentAddressUpdateReviewRequestModal.jsx | 5 +-- 5 files changed, 76 insertions(+), 25 deletions(-) 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 72ae7116ab7..8eb7c56328f 100644 --- a/pkg/models/re_rate_area.go +++ b/pkg/models/re_rate_area.go @@ -57,7 +57,7 @@ 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 != nil && contractID != uuid.Nil { + 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.UU } 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/services/shipment_address_update/shipment_address_update_requester.go b/pkg/services/shipment_address_update/shipment_address_update_requester.go index 1c06fb790a8..a1fef897a65 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 } @@ -53,17 +53,30 @@ func (f *shipmentAddressUpdateRequester) isAddressChangeDistanceOver50(appCtx ap } 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 need to make sure we didn't change rate areas + // 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 } - newRateArea, err := models.FetchRateAreaID(appCtx.DB(), newDeliveryAddress.ID, nil, contractID) - if err != nil || newRateArea == uuid.Nil { + // 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 originalRateArea != newRateArea { + // 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 @@ -355,7 +368,8 @@ func (f *shipmentAddressUpdateRequester) RequestShipmentDeliveryAddressUpdate(ap return nil, err } - if !updateNeedsTOOReview { + // international shipments don't need to be concerned with shorthaul/linehaul + if !updateNeedsTOOReview && shipment.MarketCode != models.MarketCodeInternational { if shipment.ShipmentType == models.MTOShipmentTypeHHG { updateNeedsTOOReview, err = f.doesDeliveryAddressUpdateChangeShipmentPricingType(*shipment.PickupAddress, addressUpdate.OriginalAddress, newAddress) if err != nil { @@ -371,7 +385,7 @@ func (f *shipmentAddressUpdateRequester) RequestShipmentDeliveryAddressUpdate(ap } } - if !updateNeedsTOOReview { + if !updateNeedsTOOReview && shipment.MarketCode != models.MarketCodeInternational { if shipment.ShipmentType == models.MTOShipmentTypeHHG { updateNeedsTOOReview, err = f.doesDeliveryAddressUpdateChangeMileageBracket(appCtx, *shipment.PickupAddress, addressUpdate.OriginalAddress, newAddress) if err != nil { @@ -388,7 +402,8 @@ func (f *shipmentAddressUpdateRequester) RequestShipmentDeliveryAddressUpdate(ap } if !updateNeedsTOOReview { - updateNeedsTOOReview, err = f.isAddressChangeDistanceOver50(appCtx, addressUpdate) + internationalShipment := shipment.MarketCode == models.MarketCodeInternational + updateNeedsTOOReview, err = f.isAddressChangeDistanceOver50(appCtx, addressUpdate, internationalShipment) if err != nil { return nil, err } @@ -407,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 { diff --git a/src/components/Office/AddressUpdatePreview/AddressUpdatePreview.jsx b/src/components/Office/AddressUpdatePreview/AddressUpdatePreview.jsx index 74a2ac1e06b..7ab6cd9cf4b 100644 --- a/src/components/Office/AddressUpdatePreview/AddressUpdatePreview.jsx +++ b/src/components/Office/AddressUpdatePreview/AddressUpdatePreview.jsx @@ -9,23 +9,34 @@ 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 the international shipping & linehaul service item. + 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/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

From 200a5018533bf84abbd4d01de69966c01fc47e42 Mon Sep 17 00:00:00 2001 From: Daniel Jordan Date: Wed, 8 Jan 2025 21:58:40 +0000 Subject: [PATCH 3/9] added functionality and test for address change, also adding in move history forgottens --- .../shipment_address_update_requester.go | 3 +- .../shipment_address_update_requester_test.go | 138 +++++++++++++++++- .../AddressUpdatePreview.jsx | 3 +- .../AddressUpdatePreview.test.jsx | 55 ++++++- .../Office/ShipmentForm/ShipmentForm.jsx | 1 + src/constants/MoveHistory/Database/Tables.js | 1 + .../reviewShipmentAddressUpdate.jsx | 27 ++++ .../reviewShipmentAddressUpdate.test.jsx | 81 ++++++++++ .../updateServiceItemPricingAndWeights.jsx | 42 ++++++ ...pdateServiceItemPricingAndWeights.test.jsx | 75 ++++++++++ .../MoveHistory/EventTemplates/index.js | 2 + .../MoveHistory/UIDisplay/Operations.js | 1 + 12 files changed, 423 insertions(+), 6 deletions(-) create mode 100644 src/constants/MoveHistory/EventTemplates/ReviewShipmentAddressUpdate/reviewShipmentAddressUpdate.jsx create mode 100644 src/constants/MoveHistory/EventTemplates/ReviewShipmentAddressUpdate/reviewShipmentAddressUpdate.test.jsx create mode 100644 src/constants/MoveHistory/EventTemplates/UpdateMTOServiceItem/updateServiceItemPricingAndWeights.jsx create mode 100644 src/constants/MoveHistory/EventTemplates/UpdateMTOServiceItem/updateServiceItemPricingAndWeights.test.jsx 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 a1fef897a65..52f433b4bfc 100644 --- a/pkg/services/shipment_address_update/shipment_address_update_requester.go +++ b/pkg/services/shipment_address_update/shipment_address_update_requester.go @@ -504,6 +504,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 { @@ -531,7 +532,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 && shipment.MarketCode != models.MarketCodeInternational { 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 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 4330b13f9eb..b7579c61d16 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 @@ -66,7 +66,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", @@ -108,6 +108,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 7ab6cd9cf4b..dc7e9d3a9d4 100644 --- a/src/components/Office/AddressUpdatePreview/AddressUpdatePreview.jsx +++ b/src/components/Office/AddressUpdatePreview/AddressUpdatePreview.jsx @@ -33,7 +33,8 @@ const AddressUpdatePreview = ({ deliveryAddressUpdate, shipment }) => { 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 the international shipping & linehaul service item. + 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. )} 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/ShipmentForm/ShipmentForm.jsx b/src/components/Office/ShipmentForm/ShipmentForm.jsx index f4b0a723828..076212d6953 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..f46b20b2da4 --- /dev/null +++ b/src/constants/MoveHistory/EventTemplates/ReviewShipmentAddressUpdate/reviewShipmentAddressUpdate.jsx @@ -0,0 +1,27 @@ +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.moves, + getEventNameDisplay: () => { + return 'Shipment Destination Address Request'; + }, + getDetails: ({ changedValues }) => { + if (changedValues.status === 'APPROVED') { + return ( +
+ Status: Approved +
+ ); + } + if (changedValues.status === 'REJECTED') { + return '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..43edc996166 --- /dev/null +++ b/src/constants/MoveHistory/EventTemplates/ReviewShipmentAddressUpdate/reviewShipmentAddressUpdate.test.jsx @@ -0,0 +1,81 @@ +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.moves, + }; + + 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.moves, + }; + + 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.moves, + }; + + const template = getTemplate(historyRecord); + + render(template.getDetails(historyRecord)); + 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.moves, + }; + + 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 afcbd6b7060..66944d5fb42 100644 --- a/src/constants/MoveHistory/EventTemplates/index.js +++ b/src/constants/MoveHistory/EventTemplates/index.js @@ -29,6 +29,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'; @@ -109,3 +110,4 @@ export { default as moveCancelerPPMShipments } from './MoveCanceler/MoveCanceler export { default as cancelMove } from './CancelMove/CancelMove'; export { default as cancelMoveMTOShipments } from './CancelMove/CancelMoveMTOShipments'; export { default as cancelMovePPMShipments } from './CancelMove/CancelMovePPMShipments'; +export { default as reviewShipmentAddressUpdate } from './ReviewShipmentAddressUpdate/reviewShipmentAddressUpdate'; diff --git a/src/constants/MoveHistory/UIDisplay/Operations.js b/src/constants/MoveHistory/UIDisplay/Operations.js index 8ca4a3f5cd0..db1a84b0511 100644 --- a/src/constants/MoveHistory/UIDisplay/Operations.js +++ b/src/constants/MoveHistory/UIDisplay/Operations.js @@ -65,4 +65,5 @@ export default { addAppealToViolation: 'addAppealToViolation', // ghc.yaml addAppealToSeriousIncident: 'addAppealToSeriousIncident', // ghc.yaml cancelMove: 'cancelMove', // internal.yaml + reviewShipmentAddressUpdate: 'reviewShipmentAddressUpdate', // ghc.yaml }; From 0e7ae54ef28ba08031fbc25db00a29c927a3119e Mon Sep 17 00:00:00 2001 From: Daniel Jordan Date: Wed, 8 Jan 2025 22:12:11 +0000 Subject: [PATCH 4/9] added model test --- pkg/models/re_contract_test.go | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) 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{}) + }) +} From a66bfe0502734f861f5d630f23ba81f1f9b56b62 Mon Sep 17 00:00:00 2001 From: Maria Traskowsky Date: Fri, 10 Jan 2025 19:01:47 +0000 Subject: [PATCH 5/9] add shipment_address_updates info to move history logs --- migrations/app/migrations_manifest.txt | 1 + ...ent_address_updates_to_move_history.up.sql | 9 +++++++++ .../sql_scripts/move_history_fetcher.sql | 20 +++++++++++++++++++ pkg/gen/ghcapi/embedded_spec.go | 6 ++++-- .../review_shipment_address_update.go | 2 +- .../reviewShipmentAddressUpdate.jsx | 8 ++++++-- .../reviewShipmentAddressUpdate.test.jsx | 11 +++++----- swagger-def/ghc.yaml | 1 + swagger/ghc.yaml | 1 + 9 files changed, 49 insertions(+), 10 deletions(-) create mode 100644 migrations/app/schema/20250110153428_add_shipment_address_updates_to_move_history.up.sql diff --git a/migrations/app/migrations_manifest.txt b/migrations/app/migrations_manifest.txt index a0db58ee090..aa0be3bfeb8 100644 --- a/migrations/app/migrations_manifest.txt +++ b/migrations/app/migrations_manifest.txt @@ -1065,3 +1065,4 @@ 20241230190638_remove_AK_zips_from_zip3.up.sql 20241230190647_add_missing_AK_zips_to_zip3_distances.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 dacacf55d78..fc40dbdb138 100644 --- a/pkg/assets/sql_scripts/move_history_fetcher.sql +++ b/pkg/assets/sql_scripts/move_history_fetcher.sql @@ -642,6 +642,21 @@ WITH move AS ( JOIN gsr_appeals ON gsr_appeals.id = audit_history.object_id WHERE audit_history.table_name = 'gsr_appeals' ), + move_shipment_address_updates AS ( + SELECT shipment_address_updates.* + FROM + shipment_address_updates + WHERE + shipment_address_updates.shipment_id = (SELECT id FROM move_shipments.id) + ), + shipment_address_updates_logs as ( + SELECT audit_history.*, + NULL AS context, + NULL AS context_id + FROM + audit_history + JOIN move_shipment_address_updates ON move_shipment_address_updates.id = audit_history.object_id + ), combined_logs AS ( SELECT * @@ -732,6 +747,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 3b8e14d3e66..2b6a627eb98 100644 --- a/pkg/gen/ghcapi/embedded_spec.go +++ b/pkg/gen/ghcapi/embedded_spec.go @@ -5671,7 +5671,8 @@ func init() { "application/json" ], "tags": [ - "shipment" + "shipment", + "shipment_address_updates" ], "summary": "Allows TOO to review a shipment address update", "operationId": "reviewShipmentAddressUpdate", @@ -22268,7 +22269,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/src/constants/MoveHistory/EventTemplates/ReviewShipmentAddressUpdate/reviewShipmentAddressUpdate.jsx b/src/constants/MoveHistory/EventTemplates/ReviewShipmentAddressUpdate/reviewShipmentAddressUpdate.jsx index f46b20b2da4..3597791218a 100644 --- a/src/constants/MoveHistory/EventTemplates/ReviewShipmentAddressUpdate/reviewShipmentAddressUpdate.jsx +++ b/src/constants/MoveHistory/EventTemplates/ReviewShipmentAddressUpdate/reviewShipmentAddressUpdate.jsx @@ -7,7 +7,7 @@ import t from 'constants/MoveHistory/Database/Tables'; export default { action: a.UPDATE, eventName: o.reviewShipmentAddressUpdate, - tableName: t.moves, + tableName: t.shipment_address_updates, getEventNameDisplay: () => { return 'Shipment Destination Address Request'; }, @@ -20,7 +20,11 @@ export default { ); } if (changedValues.status === 'REJECTED') { - return '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 index 43edc996166..8077e4f324e 100644 --- a/src/constants/MoveHistory/EventTemplates/ReviewShipmentAddressUpdate/reviewShipmentAddressUpdate.test.jsx +++ b/src/constants/MoveHistory/EventTemplates/ReviewShipmentAddressUpdate/reviewShipmentAddressUpdate.test.jsx @@ -21,7 +21,7 @@ describe('when given a Review Shipment Address Update history record', () => { }, context, eventName: o.reviewShipmentAddressUpdate, - tableName: t.moves, + tableName: t.shipment_address_updates, }; const template = getTemplate(historyRecord); @@ -37,7 +37,7 @@ describe('when given a Review Shipment Address Update history record', () => { }, context, eventName: o.reviewShipmentAddressUpdate, - tableName: t.moves, + tableName: t.shipment_address_updates, }; const template = getTemplate(historyRecord); @@ -55,13 +55,14 @@ describe('when given a Review Shipment Address Update history record', () => { }, context, eventName: o.reviewShipmentAddressUpdate, - tableName: t.moves, + tableName: t.shipment_address_updates, }; const template = getTemplate(historyRecord); render(template.getDetails(historyRecord)); - expect(screen.getByText('Rejected')).toBeInTheDocument(); + expect(screen.getByText('Status')).toBeInTheDocument(); + expect(screen.getByText(/Rejected/)).toBeInTheDocument(); }); it('returns null if the status is not "APPROVED" or "REJECTED"', () => { @@ -72,7 +73,7 @@ describe('when given a Review Shipment Address Update history record', () => { }, context, eventName: o.reviewShipmentAddressUpdate, - tableName: t.moves, + tableName: t.shipment_address_updates, }; const template = getTemplate(historyRecord); diff --git a/swagger-def/ghc.yaml b/swagger-def/ghc.yaml index a0df07f3800..598cc954174 100644 --- a/swagger-def/ghc.yaml +++ b/swagger-def/ghc.yaml @@ -1380,6 +1380,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 2456656b1c6..0d12e7d1b05 100644 --- a/swagger/ghc.yaml +++ b/swagger/ghc.yaml @@ -1429,6 +1429,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 From 77a620bd676e6ef215a3cca063650cb320c9821c Mon Sep 17 00:00:00 2001 From: Maria Traskowsky Date: Fri, 10 Jan 2025 19:41:07 +0000 Subject: [PATCH 6/9] storybook updates --- .../AddressUpdatePreview/AddressUpdatePreview.stories.jsx | 7 +++++++ 1 file changed, 7 insertions(+) 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 = { ,
From 47ac9bcbd6f97ef4e8a9c3880fd791023230bddf Mon Sep 17 00:00:00 2001 From: Maria Traskowsky Date: Fri, 10 Jan 2025 20:57:06 +0000 Subject: [PATCH 7/9] fix some failing tests, some still broken --- pkg/assets/sql_scripts/move_history_fetcher.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/assets/sql_scripts/move_history_fetcher.sql b/pkg/assets/sql_scripts/move_history_fetcher.sql index fc40dbdb138..fc09c0e6490 100644 --- a/pkg/assets/sql_scripts/move_history_fetcher.sql +++ b/pkg/assets/sql_scripts/move_history_fetcher.sql @@ -646,8 +646,7 @@ WITH move AS ( SELECT shipment_address_updates.* FROM shipment_address_updates - WHERE - shipment_address_updates.shipment_id = (SELECT id FROM move_shipments.id) + JOIN move_shipments ON shipment_address_updates.shipment_id = move_shipments.id ), shipment_address_updates_logs as ( SELECT audit_history.*, @@ -655,7 +654,8 @@ WITH move AS ( NULL AS context_id FROM audit_history - JOIN move_shipment_address_updates ON move_shipment_address_updates.id = audit_history.object_id + JOIN move_shipments ON move_shipments.id = move_shipments.id + WHERE audit_history.table_name = 'shipment_address_updates' ), combined_logs AS ( SELECT From bb4442791a6620ec9cba97dbeea2a3d815d00a23 Mon Sep 17 00:00:00 2001 From: Maria Traskowsky Date: Fri, 10 Jan 2025 22:04:20 +0000 Subject: [PATCH 8/9] modify query further --- pkg/assets/sql_scripts/move_history_fetcher.sql | 16 ++++++++++------ .../move_history/move_history_fetcher_test.go | 6 ++++-- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/pkg/assets/sql_scripts/move_history_fetcher.sql b/pkg/assets/sql_scripts/move_history_fetcher.sql index fc09c0e6490..28f88eab583 100644 --- a/pkg/assets/sql_scripts/move_history_fetcher.sql +++ b/pkg/assets/sql_scripts/move_history_fetcher.sql @@ -642,19 +642,23 @@ WITH move AS ( JOIN gsr_appeals ON gsr_appeals.id = audit_history.object_id WHERE audit_history.table_name = 'gsr_appeals' ), - move_shipment_address_updates AS ( - SELECT shipment_address_updates.* - FROM - shipment_address_updates + 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.*, - NULL AS context, + shipment_address_updates.context AS context, NULL AS context_id FROM audit_history - JOIN move_shipments ON move_shipments.id = move_shipments.id + JOIN shipment_address_updates ON shipment_address_updates.id = audit_history.object_id WHERE audit_history.table_name = 'shipment_address_updates' ), combined_logs AS ( diff --git a/pkg/services/move_history/move_history_fetcher_test.go b/pkg/services/move_history/move_history_fetcher_test.go index 9da8d13f3bb..161dde49e93 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 From f76c232706345edca2765d495b01e9eda7d3b849 Mon Sep 17 00:00:00 2001 From: Daniel Jordan Date: Mon, 13 Jan 2025 15:27:20 +0000 Subject: [PATCH 9/9] little variable reusability goes a long way --- .../shipment_address_update_requester.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) 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 52f433b4bfc..239f95243f8 100644 --- a/pkg/services/shipment_address_update/shipment_address_update_requester.go +++ b/pkg/services/shipment_address_update/shipment_address_update_requester.go @@ -281,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) @@ -369,7 +370,7 @@ func (f *shipmentAddressUpdateRequester) RequestShipmentDeliveryAddressUpdate(ap } // international shipments don't need to be concerned with shorthaul/linehaul - if !updateNeedsTOOReview && shipment.MarketCode != models.MarketCodeInternational { + if !updateNeedsTOOReview && !isInternationalShipment { if shipment.ShipmentType == models.MTOShipmentTypeHHG { updateNeedsTOOReview, err = f.doesDeliveryAddressUpdateChangeShipmentPricingType(*shipment.PickupAddress, addressUpdate.OriginalAddress, newAddress) if err != nil { @@ -385,7 +386,7 @@ func (f *shipmentAddressUpdateRequester) RequestShipmentDeliveryAddressUpdate(ap } } - if !updateNeedsTOOReview && shipment.MarketCode != models.MarketCodeInternational { + if !updateNeedsTOOReview && !isInternationalShipment { if shipment.ShipmentType == models.MTOShipmentTypeHHG { updateNeedsTOOReview, err = f.doesDeliveryAddressUpdateChangeMileageBracket(appCtx, *shipment.PickupAddress, addressUpdate.OriginalAddress, newAddress) if err != nil { @@ -402,8 +403,7 @@ func (f *shipmentAddressUpdateRequester) RequestShipmentDeliveryAddressUpdate(ap } if !updateNeedsTOOReview { - internationalShipment := shipment.MarketCode == models.MarketCodeInternational - updateNeedsTOOReview, err = f.isAddressChangeDistanceOver50(appCtx, addressUpdate, internationalShipment) + updateNeedsTOOReview, err = f.isAddressChangeDistanceOver50(appCtx, addressUpdate, isInternationalShipment) if err != nil { return nil, err } @@ -495,6 +495,7 @@ func (f *shipmentAddressUpdateRequester) ReviewShipmentAddressChange(appCtx appc } shipment = addressUpdate.Shipment + isInternationalShipment := shipment.MarketCode == models.MarketCodeInternational if tooApprovalStatus == models.ShipmentAddressUpdateStatusApproved { queryBuilder := query.NewQueryBuilder() @@ -532,7 +533,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 && shipment.MarketCode != models.MarketCodeInternational { + 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 @@ -638,7 +639,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 {