diff --git a/migrations/app/migrations_manifest.txt b/migrations/app/migrations_manifest.txt index bfc6656f717..88ce324d65d 100644 --- a/migrations/app/migrations_manifest.txt +++ b/migrations/app/migrations_manifest.txt @@ -951,5 +951,6 @@ 20240531153321_update_tio_role_name.up.sql 20240531154303_add_more_submitted_columns_to_ppm_document_tables.up.sql 20240603040207_add_submitted_cols_to_moving_expenses.up.sql +20240604203456_add_effective_expiraton_date_ghc_fuel.up.sql 20240603152949_update_too_role_name.up.sql 20240606195706_adding_uncapped_request_total.up.sql diff --git a/migrations/app/schema/20240604203456_add_effective_expiraton_date_ghc_fuel.up.sql b/migrations/app/schema/20240604203456_add_effective_expiraton_date_ghc_fuel.up.sql new file mode 100644 index 00000000000..3868cee329d --- /dev/null +++ b/migrations/app/schema/20240604203456_add_effective_expiraton_date_ghc_fuel.up.sql @@ -0,0 +1,8 @@ +ALTER Table ghc_diesel_fuel_prices +ADD column IF NOT EXISTS effective_date date, +ADD column IF NOT EXISTS end_date date; + +-- update current records with effective date and end date +-- business rule is that the diesel fuel prices are posted on Mondays and are effective Tuesday and end the following Monday +update ghc_diesel_fuel_prices set effective_date = publication_date + interval '1' day; +update ghc_diesel_fuel_prices set end_date = effective_date + interval '6' day; \ No newline at end of file diff --git a/pkg/handlers/primeapi/payment_request_test.go b/pkg/handlers/primeapi/payment_request_test.go index 8f8a281fada..9823b007442 100644 --- a/pkg/handlers/primeapi/payment_request_test.go +++ b/pkg/handlers/primeapi/payment_request_test.go @@ -716,6 +716,8 @@ func (suite *HandlerSuite) setupDomesticLinehaulData() (models.Move, models.MTOS ghcDieselFuelPrice := models.GHCDieselFuelPrice{ PublicationDate: publicationDate, FuelPriceInMillicents: unit.Millicents(277600), + EffectiveDate: publicationDate.AddDate(0, 0, 1), + EndDate: publicationDate.AddDate(0, 0, 7), } suite.MustSave(&ghcDieselFuelPrice) diff --git a/pkg/models/ghc_diesel_fuel_price.go b/pkg/models/ghc_diesel_fuel_price.go index c596cf517ba..73e6457614c 100644 --- a/pkg/models/ghc_diesel_fuel_price.go +++ b/pkg/models/ghc_diesel_fuel_price.go @@ -18,6 +18,8 @@ type GHCDieselFuelPrice struct { UpdatedAt time.Time `json:"updated_at" db:"updated_at"` FuelPriceInMillicents unit.Millicents `json:"fuel_price_in_millicents" db:"fuel_price_in_millicents"` PublicationDate time.Time `json:"publication_date" db:"publication_date"` + EffectiveDate time.Time `json:"effective_date" db:"effective_date"` + EndDate time.Time `json:"end_date" db:"end_date"` } // TableName overrides the table name used by Pop. diff --git a/pkg/payment_request/service_param_value_lookups/eia_fuel_price_lookup.go b/pkg/payment_request/service_param_value_lookups/eia_fuel_price_lookup.go index 404a21d74dc..a5d0840636d 100644 --- a/pkg/payment_request/service_param_value_lookups/eia_fuel_price_lookup.go +++ b/pkg/payment_request/service_param_value_lookups/eia_fuel_price_lookup.go @@ -25,13 +25,22 @@ func (r EIAFuelPriceLookup) lookup(appCtx appcontext.AppContext, _ *ServiceItemP return "", fmt.Errorf("could not find actual pickup date for MTOShipment [%s]", r.MTOShipment.ID) } - // Find the GHCDieselFuelPrice object with the closest prior PublicationDate to the ActualPickupDate of the MTOShipment in question + // Find the GHCDieselFuelPrice object effective before the shipment's ActualPickupDate and ends after the ActualPickupDate var ghcDieselFuelPrice models.GHCDieselFuelPrice - err := db.Where("publication_date <= ?", actualPickupDate).Order("publication_date DESC").Last(&ghcDieselFuelPrice) + err := db.Where("? BETWEEN effective_date and end_date", actualPickupDate).Order("publication_date DESC").First(&ghcDieselFuelPrice) //only want the first published price per week if err != nil { switch err { case sql.ErrNoRows: - return "", apperror.NewNotFoundError(uuid.Nil, "Looking for GHCDieselFuelPrice") + // If no published price is found, look for the first published price after the actual pickup date + err = db.Where("publication_date <= ?", actualPickupDate).Order("publication_date DESC").Last(&ghcDieselFuelPrice) + if err != nil { + switch err { + case sql.ErrNoRows: + return "", apperror.NewNotFoundError(uuid.Nil, "Looking for GHCDieselFuelPrice") + default: + return "", apperror.NewQueryError("GHCDieselFuelPrice", err, "") + } + } default: return "", apperror.NewQueryError("GHCDieselFuelPrice", err, "") } diff --git a/pkg/payment_request/service_param_value_lookups/eia_fuel_price_lookup_test.go b/pkg/payment_request/service_param_value_lookups/eia_fuel_price_lookup_test.go index 91e551d7cd4..38275948976 100644 --- a/pkg/payment_request/service_param_value_lookups/eia_fuel_price_lookup_test.go +++ b/pkg/payment_request/service_param_value_lookups/eia_fuel_price_lookup_test.go @@ -28,6 +28,8 @@ func (suite *ServiceParamValueLookupsSuite) TestEIAFuelPriceLookup() { firstGHCDieselFuelPrice.PublicationDate = time.Date(2020, time.July, 06, 0, 0, 0, 0, time.UTC) firstGHCDieselFuelPrice.FuelPriceInMillicents = unit.Millicents(243699) + firstGHCDieselFuelPrice.EffectiveDate = firstGHCDieselFuelPrice.PublicationDate.AddDate(0, 0, 1) + firstGHCDieselFuelPrice.EndDate = firstGHCDieselFuelPrice.PublicationDate.AddDate(0, 0, 7) var existingFuelPrice1 models.GHCDieselFuelPrice err := suite.DB().Where("ghc_diesel_fuel_prices.publication_date = ?", firstGHCDieselFuelPrice.PublicationDate).First(&existingFuelPrice1) @@ -39,6 +41,8 @@ func (suite *ServiceParamValueLookupsSuite) TestEIAFuelPriceLookup() { secondGHCDieselFuelPrice.PublicationDate = time.Date(2020, time.July, 13, 0, 0, 0, 0, time.UTC) secondGHCDieselFuelPrice.FuelPriceInMillicents = unit.Millicents(243799) + secondGHCDieselFuelPrice.EffectiveDate = secondGHCDieselFuelPrice.PublicationDate.AddDate(0, 0, 1) + secondGHCDieselFuelPrice.EndDate = secondGHCDieselFuelPrice.PublicationDate.AddDate(0, 0, 7) var existingFuelPrice2 models.GHCDieselFuelPrice err = suite.DB().Where("ghc_diesel_fuel_prices.publication_date = ?", secondGHCDieselFuelPrice.PublicationDate).First(&existingFuelPrice2) @@ -50,6 +54,9 @@ func (suite *ServiceParamValueLookupsSuite) TestEIAFuelPriceLookup() { thirdGHCDieselFuelPrice.PublicationDate = time.Date(2020, time.July, 20, 0, 0, 0, 0, time.UTC) thirdGHCDieselFuelPrice.FuelPriceInMillicents = unit.Millicents(243299) + thirdGHCDieselFuelPrice.EffectiveDate = thirdGHCDieselFuelPrice.PublicationDate.AddDate(0, 0, 1) + thirdGHCDieselFuelPrice.EndDate = thirdGHCDieselFuelPrice.PublicationDate.AddDate(0, 0, 7) + var existingFuelPrice3 models.GHCDieselFuelPrice err = suite.DB().Where("ghc_diesel_fuel_prices.publication_date = ?", thirdGHCDieselFuelPrice.PublicationDate).First(&existingFuelPrice3) if err == nil { @@ -168,3 +175,85 @@ func (suite *ServiceParamValueLookupsSuite) TestEIAFuelPriceLookup() { suite.Equal("Not found looking for pickup address", err.Error()) }) } +func (suite *ServiceParamValueLookupsSuite) TestEIAFuelPriceLookupWithInvalidActualPickupDate() { + key := models.ServiceItemParamNameEIAFuelPrice + var mtoServiceItem models.MTOServiceItem + var paymentRequest models.PaymentRequest + + setupTestData := func() { + testdatagen.MakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + StartDate: time.Now().Add(-24 * time.Hour), + EndDate: time.Now().Add(24 * time.Hour), + }, + }) + mtoServiceItem = factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + ActualPickupDate: nil, + }, + }, + }, []factory.Trait{ + factory.GetTraitAvailableToPrimeMove, + }) + + paymentRequest = factory.BuildPaymentRequest(suite.DB(), []factory.Customization{ + { + Model: mtoServiceItem.MoveTaskOrder, + LinkOnly: true, + }, + }, nil) + } + + suite.Run("lookup GHC diesel fuel price with nil actual pickup date", func() { + setupTestData() + + paramLookup, err := ServiceParamLookupInitialize(suite.AppContextForTest(), suite.planner, mtoServiceItem, paymentRequest.ID, paymentRequest.MoveTaskOrderID, nil) + suite.FatalNoError(err) + _, err = paramLookup.ServiceParamValue(suite.AppContextForTest(), key) + suite.Error(err) + suite.Contains(err.Error(), "EIAFuelPriceLookup with error Not found Looking for GHCDieselFuelPrice") + }) +} + +func (suite *ServiceParamValueLookupsSuite) TestEIAFuelPriceLookupWithNoGHCDieselFuelPriceData() { + key := models.ServiceItemParamNameEIAFuelPrice + var mtoServiceItem models.MTOServiceItem + var paymentRequest models.PaymentRequest + actualPickupDate := time.Date(2020, time.July, 15, 0, 0, 0, 0, time.UTC) + + setupTestData := func() { + testdatagen.MakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + StartDate: time.Now().Add(-24 * time.Hour), + EndDate: time.Now().Add(24 * time.Hour), + }, + }) + mtoServiceItem = factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + ActualPickupDate: &actualPickupDate, + }, + }, + }, []factory.Trait{ + factory.GetTraitAvailableToPrimeMove, + }) + + paymentRequest = factory.BuildPaymentRequest(suite.DB(), []factory.Customization{ + { + Model: mtoServiceItem.MoveTaskOrder, + LinkOnly: true, + }, + }, nil) + } + + suite.Run("lookup GHC diesel fuel price with no data", func() { + setupTestData() + + paramLookup, err := ServiceParamLookupInitialize(suite.AppContextForTest(), suite.planner, mtoServiceItem, paymentRequest.ID, paymentRequest.MoveTaskOrderID, nil) + suite.FatalNoError(err) + _, err = paramLookup.ServiceParamValue(suite.AppContextForTest(), key) + suite.Error(err) + suite.Contains(err.Error(), "Looking for GHCDieselFuelPrice") + }) +} diff --git a/pkg/services/ghcdieselfuelprice/ghc_diesel_fuel_price_storer.go b/pkg/services/ghcdieselfuelprice/ghc_diesel_fuel_price_storer.go index ba3194b1b02..edf1306fa39 100644 --- a/pkg/services/ghcdieselfuelprice/ghc_diesel_fuel_price_storer.go +++ b/pkg/services/ghcdieselfuelprice/ghc_diesel_fuel_price_storer.go @@ -43,6 +43,29 @@ func (d *DieselFuelPriceInfo) RunStorer(appCtx appcontext.AppContext) error { err = appCtx.DB().Where("publication_date = ?", publicationDate).First(&lastGHCDieselFuelPrice) if err != nil { appCtx.Logger().Info("no existing GHCDieselFuelPrice record found with", zap.String("publication_date", publicationDate.String())) + newGHCDieselFuelPrice.EffectiveDate = publicationDate.AddDate(0, 0, 1) + + dayOfWeek := publicationDate.Weekday().String() + appCtx.Logger().Info("day_of_week", zap.String("day_of_week", dayOfWeek)) + var daysAdded int + //fuel prices are generally published on mondays and then by business rule should expire on monday no matter what- but in case its published on a different day, we will still always expire on the following monday + switch dayOfWeek { + case "Monday": + daysAdded = 7 + case "Tuesday": + daysAdded = 6 + //very unlikely to get past here- monday is the normal publish day- tuesday if monday is holiday.. but adding other weekdays just in case + case "Wednesday": + daysAdded = 6 + case "Thursday": + daysAdded = 4 + case "Friday": + daysAdded = 3 + } + + newGHCDieselFuelPrice.EndDate = publicationDate.AddDate(0, 0, daysAdded) + appCtx.Logger().Info("effective_date", zap.String("effective_date", newGHCDieselFuelPrice.EffectiveDate.String())) + appCtx.Logger().Info("end_date", zap.String("EndDate", newGHCDieselFuelPrice.EndDate.String())) verrs, err := appCtx.DB().ValidateAndCreate(&newGHCDieselFuelPrice) if err != nil { @@ -52,16 +75,13 @@ func (d *DieselFuelPriceInfo) RunStorer(appCtx appcontext.AppContext) error { return fmt.Errorf("failed to validate ghcDieselFuelPrice: %w", verrs) } } else if priceInMillicents != lastGHCDieselFuelPrice.FuelPriceInMillicents { - appCtx.Logger().Info("Updating existing GHCDieselFuelPrice record found with", zap.String("publication_date", publicationDate.String())) - lastGHCDieselFuelPrice.FuelPriceInMillicents = priceInMillicents + appCtx.Logger().Info("existing GHCDieselFuelPrice record found with", zap.String("publication_date", publicationDate.String())) - verrs, err := appCtx.DB().ValidateAndUpdate(&lastGHCDieselFuelPrice) + //no longer updating prices throughout the week- only accept the first published price per week if err != nil { return fmt.Errorf("failed to update ghcDieselFuelPrice: %w", err) } - if verrs.HasAny() { - return fmt.Errorf("failed to validate ghcDieselFuelPrice: %w", verrs) - } + } else { appCtx.Logger().Info( "Existing GHCDieselFuelPrice record found with matching fuel prices", diff --git a/pkg/services/ghcdieselfuelprice/ghc_diesel_fuel_price_storer_test.go b/pkg/services/ghcdieselfuelprice/ghc_diesel_fuel_price_storer_test.go index c74430890aa..95188677e61 100644 --- a/pkg/services/ghcdieselfuelprice/ghc_diesel_fuel_price_storer_test.go +++ b/pkg/services/ghcdieselfuelprice/ghc_diesel_fuel_price_storer_test.go @@ -30,41 +30,11 @@ func (suite *GHCDieselFuelPriceServiceSuite) Test_ghcDieselFuelPriceStorer() { suite.NoError(err) var ghcDieselFuelPrice models.GHCDieselFuelPrice - err = suite.DB().Last(&ghcDieselFuelPrice) suite.NoError(err) - suite.Equal("2020-06-22T00:00:00Z", ghcDieselFuelPrice.PublicationDate.Format(time.RFC3339)) suite.Equal(unit.Millicents(265900), ghcDieselFuelPrice.FuelPriceInMillicents) - }) - - suite.Run("run storer for existing publication date", func() { - // Under test: RunStorer function (creates or updates fuel price data for a specific publication date) - // Mocked: None - // Set up: Create a fuel price object for 20200622 then try to update it - // Expected outcome: fuel price is updated - dieselFuelPriceInfo := defaultDieselFuelPriceInfo - err := dieselFuelPriceInfo.RunStorer(suite.AppContextForTest()) - suite.NoError(err) - - updatedDieselFuelPriceInfo := defaultDieselFuelPriceInfo - updatedDieselFuelPriceInfo.dieselFuelPriceData.price = 2.420 - - err = updatedDieselFuelPriceInfo.RunStorer(suite.AppContextForTest()) - suite.NoError(err) - - var ghcDieselFuelPrice models.GHCDieselFuelPrice - - err = suite.DB().Last(&ghcDieselFuelPrice) - suite.NoError(err) - - suite.Equal("2020-06-22T00:00:00Z", ghcDieselFuelPrice.PublicationDate.Format(time.RFC3339)) - suite.Equal(unit.Millicents(242000), ghcDieselFuelPrice.FuelPriceInMillicents) - - count, err := suite.DB().Count(models.GHCDieselFuelPrice{}) - suite.NoError(err) - suite.Equal(1, count) }) suite.Run("test publication date in time", func() { diff --git a/pkg/services/payment_request/payment_request_recalculator_test.go b/pkg/services/payment_request/payment_request_recalculator_test.go index 99ecf8f6ac4..1feab91124c 100644 --- a/pkg/services/payment_request/payment_request_recalculator_test.go +++ b/pkg/services/payment_request/payment_request_recalculator_test.go @@ -470,9 +470,13 @@ func (suite *PaymentRequestServiceSuite) setupRecalculateData1() (models.Move, m // FSC price data (needs actual pickup date from move created above) publicationDate := moveTaskOrder.MTOShipments[0].ActualPickupDate.AddDate(0, 0, -3) // 3 days earlier + effectiveDate := publicationDate.AddDate(0, 0, 1) + endDate := publicationDate.AddDate(0, 0, 7) ghcDieselFuelPrice := models.GHCDieselFuelPrice{ PublicationDate: publicationDate, FuelPriceInMillicents: recalculateTestFSCPrice, + EffectiveDate: effectiveDate, + EndDate: endDate, } suite.MustSave(&ghcDieselFuelPrice) diff --git a/pkg/services/ppm_closeout/ppm_closeout_test.go b/pkg/services/ppm_closeout/ppm_closeout_test.go index 3b8996af2ba..9bcc78587aa 100644 --- a/pkg/services/ppm_closeout/ppm_closeout_test.go +++ b/pkg/services/ppm_closeout/ppm_closeout_test.go @@ -39,6 +39,8 @@ func (suite *PPMCloseoutSuite) TestPPMShipmentCreator() { GHCDieselFuelPrice: models.GHCDieselFuelPrice{ FuelPriceInMillicents: unit.Millicents(281400), PublicationDate: time.Date(2020, time.March, 9, 0, 0, 0, 0, time.UTC), + EffectiveDate: time.Date(2020, time.March, 10, 0, 0, 0, 0, time.UTC), + EndDate: time.Date(2020, time.March, 17, 0, 0, 0, 0, time.UTC), }, }) diff --git a/pkg/services/ppmshipment/ppm_estimator_test.go b/pkg/services/ppmshipment/ppm_estimator_test.go index ef1c3a4262c..d8c7676250b 100644 --- a/pkg/services/ppmshipment/ppm_estimator_test.go +++ b/pkg/services/ppmshipment/ppm_estimator_test.go @@ -157,6 +157,8 @@ func (suite *PPMShipmentSuite) TestPPMEstimator() { GHCDieselFuelPrice: models.GHCDieselFuelPrice{ FuelPriceInMillicents: unit.Millicents(281400), PublicationDate: time.Date(2020, time.March, 9, 0, 0, 0, 0, time.UTC), + EffectiveDate: time.Date(2020, time.March, 10, 0, 0, 0, 0, time.UTC), + EndDate: time.Date(2020, time.March, 16, 0, 0, 0, 0, time.UTC), }, }) diff --git a/pkg/testdatagen/make_ghc_diesel_fuel_price.go b/pkg/testdatagen/make_ghc_diesel_fuel_price.go index 8686ee1c3a7..2cce2499c13 100644 --- a/pkg/testdatagen/make_ghc_diesel_fuel_price.go +++ b/pkg/testdatagen/make_ghc_diesel_fuel_price.go @@ -17,8 +17,9 @@ func MakeGHCDieselFuelPrice(db *pop.Connection, assertions Assertions) models.GH ghcDieselFuelPrice := models.GHCDieselFuelPrice{ FuelPriceInMillicents: unit.Millicents(243300), PublicationDate: time.Date(GHCTestYear, time.July, 20, 0, 0, 0, 0, time.UTC), + EffectiveDate: time.Date(GHCTestYear, time.July, 21, 0, 0, 0, 0, time.UTC), + EndDate: time.Date(GHCTestYear, time.July, 27, 0, 0, 0, 0, time.UTC), } - mergeModels(&ghcDieselFuelPrice, assertions.GHCDieselFuelPrice) mustCreate(db, &ghcDieselFuelPrice, assertions.Stub) diff --git a/scripts/run-server-test b/scripts/run-server-test index 85fefb1bf4b..2d547e5231e 100755 --- a/scripts/run-server-test +++ b/scripts/run-server-test @@ -47,6 +47,8 @@ else gotest_args+=("-parallel" "8") gotest_args+=("-failfast") fi +## mac users uncomment the following line to run tests with the classic linker, which clears a lot of warnings that fill the console, do not commit to repo uncommented +#gotest_args+=("-ldflags=-extldflags=-Wl,-ld_classic") # Try to compile tests, but don't run them. if [[ "${DRY_RUN:-}" == "1" ]]; then