Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

B 22462 sit pricing fuel surcharge #14964

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions migrations/app/ddl_functions_manifest.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# This is the functions(procedures) migrations manifest.
# If a migration is not recorded here, then it will error.
# Naming convention: fn_some_function.up.sql running <generate-ddl-migration some_function functions> will create this file.
20250221213413_fn_update_service_item_pricing.up.sql
20250223023132_fn_get_counseling_offices.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
--B-22462 M.Inthavongsay Adding initial migration file for update_service_item_pricing stored procedure using new migration process.
--Also updating to allow IOSFSC and IDSFSC SIT service items.
CREATE OR REPLACE PROCEDURE update_service_item_pricing(
shipment_id UUID,
mileage INT
) AS
'
DECLARE
shipment RECORD;
service_item RECORD;
escalated_price NUMERIC;
estimated_price NUMERIC;
o_rate_area_id UUID;
d_rate_area_id UUID;
contract_id UUID;
service_code TEXT;
o_zip_code TEXT;
d_zip_code TEXT;
distance NUMERIC;
estimated_fsc_multiplier NUMERIC;
fuel_price NUMERIC;
cents_above_baseline NUMERIC;
price_difference NUMERIC;
BEGIN
SELECT ms.id, ms.pickup_address_id, ms.destination_address_id, ms.requested_pickup_date, ms.prime_estimated_weight
INTO shipment
FROM mto_shipments ms
WHERE ms.id = shipment_id;

IF shipment IS NULL THEN
RAISE EXCEPTION ''Shipment with ID % not found'', shipment_id;
END IF;

-- exit the proc if prime_estimated_weight is NULL
IF shipment.prime_estimated_weight IS NULL THEN
RETURN;
END IF;

-- loop through service items in the shipment
FOR service_item IN
SELECT si.id, si.re_service_id, si.sit_delivery_miles
FROM mto_service_items si
WHERE si.mto_shipment_id = shipment_id
LOOP
-- get the service code for the current service item to determine calculation
SELECT code
INTO service_code
FROM re_services
WHERE id = service_item.re_service_id;

CASE
WHEN service_code IN (''ISLH'', ''UBP'') THEN
contract_id := get_contract_id(shipment.requested_pickup_date);
o_rate_area_id := get_rate_area_id(shipment.pickup_address_id, service_item.re_service_id, contract_id);
d_rate_area_id := get_rate_area_id(shipment.destination_address_id, service_item.re_service_id, contract_id);
escalated_price := calculate_escalated_price(o_rate_area_id, d_rate_area_id, service_item.re_service_id, contract_id, service_code, shipment.requested_pickup_date);

IF shipment.prime_estimated_weight IS NOT NULL THEN
-- multiply by 110% of estimated weight
estimated_price := ROUND((escalated_price * (shipment.prime_estimated_weight * 1.1) / 100), 2) * 100;
RAISE NOTICE ''%: Received estimated price of % (% * (% * 1.1) / 100)) cents'', service_code, estimated_price, escalated_price, shipment.prime_estimated_weight;
-- update the pricing_estimate value in mto_service_items
UPDATE mto_service_items
SET pricing_estimate = estimated_price
WHERE id = service_item.id;
END IF;

WHEN service_code IN (''IHPK'', ''IUBPK'', ''IOSHUT'') THEN
-- perform IHPK/IUBPK-specific logic (no destination rate area)
contract_id := get_contract_id(shipment.requested_pickup_date);
o_rate_area_id := get_rate_area_id(shipment.pickup_address_id, service_item.re_service_id, contract_id);
escalated_price := calculate_escalated_price(o_rate_area_id, NULL, service_item.re_service_id, contract_id, service_code, shipment.requested_pickup_date);

IF shipment.prime_estimated_weight IS NOT NULL THEN
-- multiply by 110% of estimated weight
estimated_price := ROUND((escalated_price * (shipment.prime_estimated_weight * 1.1) / 100), 2) * 100;
RAISE NOTICE ''%: Received estimated price of % (% * (% * 1.1) / 100)) cents'', service_code, estimated_price, escalated_price, shipment.prime_estimated_weight;
-- update the pricing_estimate value in mto_service_items
UPDATE mto_service_items
SET pricing_estimate = estimated_price
WHERE id = service_item.id;
END IF;

WHEN service_code IN (''IHUPK'', ''IUBUPK'', ''IDSHUT'') THEN
-- perform IHUPK/IUBUPK-specific logic (no origin rate area)
contract_id := get_contract_id(shipment.requested_pickup_date);
d_rate_area_id := get_rate_area_id(shipment.destination_address_id, service_item.re_service_id, contract_id);
escalated_price := calculate_escalated_price(NULL, d_rate_area_id, service_item.re_service_id, contract_id, service_code, shipment.requested_pickup_date);

IF shipment.prime_estimated_weight IS NOT NULL THEN
-- multiply by 110% of estimated weight
estimated_price := ROUND((escalated_price * (shipment.prime_estimated_weight * 1.1) / 100), 2) * 100;
RAISE NOTICE ''%: Received estimated price of % (% * (% * 1.1) / 100)) cents'', service_code, estimated_price, escalated_price, shipment.prime_estimated_weight;
-- update the pricing_estimate value in mto_service_items
UPDATE mto_service_items
SET pricing_estimate = estimated_price
WHERE id = service_item.id;
END IF;

WHEN service_code IN (''POEFSC'', ''PODFSC'') THEN
distance = mileage;

-- getting FSC multiplier from re_fsc_multipliers
estimated_fsc_multiplier := get_fsc_multiplier(shipment.prime_estimated_weight);

fuel_price := get_fuel_price(shipment.requested_pickup_date);

price_difference := calculate_price_difference(fuel_price);

IF estimated_fsc_multiplier IS NOT NULL AND distance IS NOT NULL THEN
cents_above_baseline := distance * estimated_fsc_multiplier;
RAISE NOTICE ''Distance: % * FSC Multipler: % = $% cents above baseline of $2.50'', distance, estimated_fsc_multiplier, cents_above_baseline;
RAISE NOTICE ''The fuel price is % above the baseline (% - 250000 baseline)'', price_difference, fuel_price;
estimated_price := ROUND((cents_above_baseline * price_difference) * 100);
RAISE NOTICE ''Received estimated price of % cents for service_code: %.'', estimated_price, service_code;

-- update the pricing_estimate value in mto_service_items
UPDATE mto_service_items
SET pricing_estimate = estimated_price
WHERE id = service_item.id;
END IF;

WHEN service_code IN (''IOSFSC'', ''IDSFSC'') THEN
distance = service_item.sit_delivery_miles;

-- getting FSC multiplier from re_fsc_multipliers. inflate estimated weight by 10%.
estimated_fsc_multiplier := get_fsc_multiplier(CAST((shipment.prime_estimated_weight * 1.1) as INTEGER));

fuel_price := get_fuel_price(shipment.requested_pickup_date);

price_difference := calculate_price_difference(fuel_price);

IF estimated_fsc_multiplier IS NOT NULL AND distance IS NOT NULL THEN
cents_above_baseline := distance * estimated_fsc_multiplier;
RAISE NOTICE ''Distance: % * FSC Multipler: % = $% cents above baseline of $2.50'', distance, estimated_fsc_multiplier, cents_above_baseline;
RAISE NOTICE ''The fuel price is % above the baseline (% - 250000 baseline)'', price_difference, fuel_price;
estimated_price := ROUND((cents_above_baseline * price_difference) * 100);
RAISE NOTICE ''Received estimated price of % cents for service_code: %.'', estimated_price, service_code;

-- update the pricing_estimate value in mto_service_items
UPDATE mto_service_items
SET pricing_estimate = estimated_price
WHERE id = service_item.id;
END IF;
ELSE
-- DEFAULT HERE
END CASE;
END LOOP;
END;
'
LANGUAGE plpgsql;
1 change: 1 addition & 0 deletions migrations/app/dml_migrations_manifest.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
# Naming convention: 202502201325_B-123456_update_some_table.up.sql running <milmove gen migration -n B-123456_update_some_table> will create this file.
20250227211521_update_re_countries.up.sql
20250227211534_update_re_country_prn_divisions.up.sql
20250303210036_B-22462_service_params_add_param_and_remove_ones__not_needed.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
-- Remove DistanceZipSITOrigin service lookup for IDSFSC
delete from service_params where service_id = (select id from re_services where code = 'IDSFSC') and service_item_param_key_id in
(select id from service_item_param_keys where key = 'DistanceZipSITOrigin');

-- Remove ZipSITOriginHHGOriginalAddress service lookup for IDSFSC
delete from service_params where service_id = (select id from re_services where code = 'IDSFSC') and service_item_param_key_id in
(select id from service_item_param_keys where key = 'ZipSITOriginHHGOriginalAddress');

-- Associate DistanceZipSITDest to service lookup for IDSFSC.
INSERT INTO service_params (id, service_id, service_item_param_key_id, created_at, updated_at, is_optional)
VALUES
('15c5ff37-99db-d162-4202-44f45181588a', (SELECT id FROM re_services WHERE code = 'IDSFSC'), (SELECT id FROM service_item_param_keys WHERE key = 'DistanceZipSITDest'), now(), now(), false)
1 change: 0 additions & 1 deletion pkg/handlers/primeapi/mto_service_item_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,6 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemHandler() {
mock.Anything,
mock.Anything,
false,
false,
).Return(400, nil)
creator := mtoserviceitem.NewMTOServiceItemCreator(planner, builder, moveRouter, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticPackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticShorthaulPricer(), ghcrateengine.NewDomesticOriginPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer())
handler := CreateMTOServiceItemHandler{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,10 @@ func ServiceParamLookupInitialize(
return nil, err
}
serviceItemDimensions = mtoServiceItem.Dimensions
case models.ReServiceCodeDDASIT, models.ReServiceCodeDDDSIT, models.ReServiceCodeDDFSIT, models.ReServiceCodeDDSFSC:
case models.ReServiceCodeDDASIT, models.ReServiceCodeDDDSIT,
models.ReServiceCodeDDFSIT, models.ReServiceCodeDDSFSC,
models.ReServiceCodeIDASIT, models.ReServiceCodeIDDSIT,
models.ReServiceCodeIDFSIT, models.ReServiceCodeIDSFSC:
// load destination address from final address on service item
if mtoServiceItem.SITDestinationFinalAddressID != nil && *mtoServiceItem.SITDestinationFinalAddressID != uuid.Nil {
err := appCtx.DB().Load(&mtoServiceItem, "SITDestinationFinalAddress")
Expand Down Expand Up @@ -605,7 +608,10 @@ func getDestinationAddressForService(appCtx appcontext.AppContext, serviceCode m
}

switch siCopy.ReService.Code {
case models.ReServiceCodeDDASIT, models.ReServiceCodeDDDSIT, models.ReServiceCodeDDFSIT, models.ReServiceCodeDDSFSC:
case models.ReServiceCodeDDASIT, models.ReServiceCodeDDDSIT,
models.ReServiceCodeDDFSIT, models.ReServiceCodeDDSFSC,
models.ReServiceCodeIDASIT, models.ReServiceCodeIDDSIT,
models.ReServiceCodeIDFSIT, models.ReServiceCodeIDSFSC:
if shipmentCopy.DeliveryAddressUpdate != nil && shipmentCopy.DeliveryAddressUpdate.Status == models.ShipmentAddressUpdateStatusApproved {
if siCopy.ApprovedAt != nil && shipmentCopy.DeliveryAddressUpdate.UpdatedAt.After(*siCopy.ApprovedAt) {
return shipmentCopy.DeliveryAddressUpdate.OriginalAddress, nil
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ func (r WeightBilledLookup) lookup(appCtx appcontext.AppContext, keyData *Servic
return value, nil
case models.ReServiceCodeDDSFSC,
models.ReServiceCodeDOSFSC,
models.ReServiceCodeFSC:
models.ReServiceCodeFSC,
models.ReServiceCodeIDSFSC,
models.ReServiceCodeIOSFSC:

var weightBilled string

Expand Down Expand Up @@ -245,7 +247,9 @@ func applyMinimum(code models.ReServiceCode, shipmentType models.MTOShipmentType
models.ReServiceCodeIDSHUT,
models.ReServiceCodeFSC,
models.ReServiceCodePODFSC,
models.ReServiceCodePOEFSC:
models.ReServiceCodePOEFSC,
models.ReServiceCodeIOSFSC,
models.ReServiceCodeIDSFSC:
if weight < 500 {
result = 500
}
Expand Down
28 changes: 28 additions & 0 deletions pkg/services/ghc_rate_engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,14 @@ type DomesticDestinationSITFuelSurchargePricer interface {
ParamsPricer
}

// InternationalDestinationSITFuelSurchargePricer prices international destination SIT fuel surcharge
//
//go:generate mockery --name InternationalDestinationSITFuelSurchargePricer
type InternationalDestinationSITFuelSurchargePricer interface {
Price(appCtx appcontext.AppContext, actualPickupDate time.Time, distance unit.Miles, weight unit.Pound, fscWeightBasedDistanceMultiplier float64, eiaFuelPrice unit.Millicents, isPPM bool) (unit.Cents, PricingDisplayParams, error)
ParamsPricer
}

// DomesticOriginSITFuelSurchargePricer prices domestic origin SIT fuel surcharge
//
//go:generate mockery --name DomesticOriginSITFuelSurchargePricer
Expand All @@ -249,6 +257,26 @@ type DomesticOriginSITFuelSurchargePricer interface {
ParamsPricer
}

// InternationalOriginSITFuelSurchargePricer prices international origin SIT fuel surcharge
//
//go:generate mockery --name InternationalOriginSITFuelSurchargePricer
type InternationalOriginSITFuelSurchargePricer interface {
Price(
appCtx appcontext.AppContext,
actualPickupDate time.Time,
distance unit.Miles,
weight unit.Pound,
fscWeightBasedDistanceMultiplier float64,
eiaFuelPrice unit.Millicents,
isPPM bool,
) (
unit.Cents,
PricingDisplayParams,
error,
)
ParamsPricer
}

// IntlShippingAndLinehaulPricer prices international shipping and linehaul for a move
//
//go:generate mockery --name IntlShippingAndLinehaulPricer
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package ghcrateengine

import (
"database/sql"
"time"

"github.com/gofrs/uuid"

"github.com/transcom/mymove/pkg/appcontext"
"github.com/transcom/mymove/pkg/apperror"
"github.com/transcom/mymove/pkg/models"
"github.com/transcom/mymove/pkg/services"
"github.com/transcom/mymove/pkg/unit"
)

type internationalDestinationSITFuelSurchargePricer struct {
}

func NewInternationalDestinationSITFuelSurchargePricer() services.InternationalDestinationSITFuelSurchargePricer {
return &internationalDestinationSITFuelSurchargePricer{}
}

func (p internationalDestinationSITFuelSurchargePricer) Price(appCtx appcontext.AppContext, actualPickupDate time.Time, distance unit.Miles, weight unit.Pound, fscWeightBasedDistanceMultiplier float64, eiaFuelPrice unit.Millicents, isPPM bool) (unit.Cents, services.PricingDisplayParams, error) {
return priceIntlFuelSurcharge(appCtx, actualPickupDate, distance, weight, fscWeightBasedDistanceMultiplier, eiaFuelPrice, isPPM)
}

func (p internationalDestinationSITFuelSurchargePricer) PriceUsingParams(appCtx appcontext.AppContext, params models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error) {
actualPickupDate, err := getParamTime(params, models.ServiceItemParamNameActualPickupDate)
if err != nil {
return unit.Cents(0), nil, err
}

var paymentServiceItem models.PaymentServiceItem
mtoShipment := params[0].PaymentServiceItem.MTOServiceItem.MTOShipment

if mtoShipment.ID == uuid.Nil {
err = appCtx.DB().Eager("MTOServiceItem", "MTOServiceItem.MTOShipment").Find(&paymentServiceItem, params[0].PaymentServiceItemID)
if err != nil {
switch err {
case sql.ErrNoRows:
return unit.Cents(0), nil, apperror.NewNotFoundError(params[0].PaymentServiceItemID, "looking for PaymentServiceItem")
default:
return unit.Cents(0), nil, apperror.NewQueryError("PaymentServiceItem", err, "")
}
}
mtoShipment = paymentServiceItem.MTOServiceItem.MTOShipment
}

distance, err := getParamInt(params, models.ServiceItemParamNameDistanceZipSITDest)
if err != nil {
return unit.Cents(0), nil, err
}

weightBilled, err := getParamInt(params, models.ServiceItemParamNameWeightBilled)
if err != nil {
return unit.Cents(0), nil, err
}

fscWeightBasedDistanceMultiplier, err := getParamFloat(params, models.ServiceItemParamNameFSCWeightBasedDistanceMultiplier)
if err != nil {
return unit.Cents(0), nil, err
}

eiaFuelPrice, err := getParamInt(params, models.ServiceItemParamNameEIAFuelPrice)
if err != nil {
return unit.Cents(0), nil, err
}

var isPPM = false
if params[0].PaymentServiceItem.MTOServiceItem.MTOShipment.ShipmentType == models.MTOShipmentTypePPM {
// PPMs do not require minimums for a shipment's weight
// this flag is passed into the Price function to ensure the weight min
// are not enforced for PPMs
isPPM = true
}

return p.Price(appCtx, actualPickupDate, unit.Miles(distance), unit.Pound(weightBilled), fscWeightBasedDistanceMultiplier, unit.Millicents(eiaFuelPrice), isPPM)
}
Loading