From 4221cc2b3b4816185df8b97384167229261d98e9 Mon Sep 17 00:00:00 2001 From: Daniel Jordan Date: Thu, 26 Dec 2024 21:16:44 +0000 Subject: [PATCH 01/14] initial commit, added pricers, waiting on ordering issue regarding packing/unpacking --- migrations/app/migrations_manifest.txt | 1 + ...aram_values_to_service_params_table.up.sql | 103 +++++++++++++ .../allowed_payment_service_item_params.go | 15 ++ pkg/handlers/primeapi/payment_request_test.go | 14 -- pkg/services/ghc_rate_engine.go | 32 ++++ .../ghcrateengine/fuel_surcharge_pricer.go | 2 +- .../ghcrateengine/intl_hhg_pack_pricer.go | 56 +++++++ .../ghcrateengine/intl_hhg_unpack_pricer.go | 56 +++++++ .../intl_port_fuel_surcharge_pricer.go | 111 ++++++++++++++ .../intl_shipping_and_linehaul_pricer.go | 137 ++++++++++++++++++ .../ghcrateengine/pricer_helpers_intl.go | 86 +++++++++++ .../ghcrateengine/pricer_query_helpers.go | 18 +++ .../ghcrateengine/service_item_pricer.go | 10 ++ pkg/services/ghcrateengine/shared.go | 3 + .../payment_request_creator.go | 1 - 15 files changed, 629 insertions(+), 16 deletions(-) create mode 100644 migrations/app/schema/20241226173330_add_intl_param_values_to_service_params_table.up.sql create mode 100644 pkg/services/ghcrateengine/intl_hhg_pack_pricer.go create mode 100644 pkg/services/ghcrateengine/intl_hhg_unpack_pricer.go create mode 100644 pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer.go create mode 100644 pkg/services/ghcrateengine/intl_shipping_and_linehaul_pricer.go create mode 100644 pkg/services/ghcrateengine/pricer_helpers_intl.go diff --git a/migrations/app/migrations_manifest.txt b/migrations/app/migrations_manifest.txt index 32afaa072da..be5080ccd3d 100644 --- a/migrations/app/migrations_manifest.txt +++ b/migrations/app/migrations_manifest.txt @@ -1058,6 +1058,7 @@ 20241217180136_add_AK_zips_to_zip3_distances.up.sql 20241218201833_add_PPPO_BASE_ELIZABETH.up.sql 20241220171035_add_additional_AK_zips_to_zip3_distances.up.sql +20241226173330_add_intl_param_values_to_service_params_table.up.sql 20241227153723_remove_empty_string_emplid_values.up.sql 20241227202424_insert_transportation_offices_camp_pendelton.up.sql 20241230190638_remove_AK_zips_from_zip3.up.sql diff --git a/migrations/app/schema/20241226173330_add_intl_param_values_to_service_params_table.up.sql b/migrations/app/schema/20241226173330_add_intl_param_values_to_service_params_table.up.sql new file mode 100644 index 00000000000..26ccf6dff90 --- /dev/null +++ b/migrations/app/schema/20241226173330_add_intl_param_values_to_service_params_table.up.sql @@ -0,0 +1,103 @@ + +-- inserting params for PODFSC +INSERT INTO service_params (id,service_id,service_item_param_key_id,created_at,updated_at,is_optional) VALUES + ('9848562b-50c1-4e6e-aef0-f9539bf243fa'::uuid,'388115e8-abe9-441d-96cf-a39f24baa0a3','adeb57e5-6b1c-4c0f-b5c9-9e57e600303f','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('9c244768-07ce-4368-936b-0ac14a8078a4'::uuid,'388115e8-abe9-441d-96cf-a39f24baa0a3','14a93209-370d-42f3-8ca2-479c953be839','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('5668accf-afac-46a3-b097-177b74076fc9'::uuid,'388115e8-abe9-441d-96cf-a39f24baa0a3','54c9cc4e-0d46-4956-b92e-be9847f894de','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('0e8bd8d5-40fd-46fb-8228-5b66088681a2'::uuid,'388115e8-abe9-441d-96cf-a39f24baa0a3','b9739817-6408-4829-8719-1e26f8a9ceb3','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('a22090b4-3ce6-448d-82b0-36592655d822'::uuid,'388115e8-abe9-441d-96cf-a39f24baa0a3','117da2f5-fff0-41e0-bba1-837124373098','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('d59c674a-eaf9-4158-8303-dbcb50a7230b'::uuid,'388115e8-abe9-441d-96cf-a39f24baa0a3','6ba0aeca-19f8-4247-a317-fffa81c5d5c1','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('450dad51-dcc9-4258-ba56-db39de6a8637'::uuid,'388115e8-abe9-441d-96cf-a39f24baa0a3','0c95581d-67de-48ae-a54b-a3748851d613','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('bf006fc0-8f33-4553-b567-a529af04eafe'::uuid,'388115e8-abe9-441d-96cf-a39f24baa0a3','45fce5ce-6a4c-4a6c-ab37-16ee0133628c','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('ad005c4b-dd71-4d42-99d8-95de7b1ed571'::uuid,'388115e8-abe9-441d-96cf-a39f24baa0a3','e6096350-9ac4-40aa-90c4-bbdff6e0b194','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('274356bc-8139-4e34-9332-ce7396f42c79'::uuid,'388115e8-abe9-441d-96cf-a39f24baa0a3','a1d31d35-c87d-4a7d-b0b8-8b2646b96e43','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('aa68a318-fe17-445c-ab53-0505fe48d0bb'::uuid,'388115e8-abe9-441d-96cf-a39f24baa0a3','b79978a7-21b7-4656-af83-25585acffb20','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), + ('882e7978-9754-4c8e-bb71-8fe4f4059503'::uuid,'388115e8-abe9-441d-96cf-a39f24baa0a3','d87d82da-3ac2-44e8-bce0-cb4de40f9a72','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), + ('8fc4571d-235b-4d4f-90e4-77e7ad9250d5'::uuid,'388115e8-abe9-441d-96cf-a39f24baa0a3','1e6257e9-757d-4d59-8846-727dd8a055e7','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), + ('836606ce-894e-4765-bba5-b696cb5fe8cc'::uuid,'388115e8-abe9-441d-96cf-a39f24baa0a3','2cbc2251-eb7d-4c69-a120-9a83785c994b','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false); + +-- inserting params for POEFSC +INSERT INTO service_params (id,service_id,service_item_param_key_id,created_at,updated_at,is_optional) VALUES + ('a57c01b1-cb1c-40f7-87e0-99d1dfd69902'::uuid,'f75758d8-2fcd-40ba-9432-3ff3032a71d1','adeb57e5-6b1c-4c0f-b5c9-9e57e600303f','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('8f49b289-c1d0-438d-b9fc-3cb234167987'::uuid,'f75758d8-2fcd-40ba-9432-3ff3032a71d1','14a93209-370d-42f3-8ca2-479c953be839','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('46742d5d-dde9-4e3c-9e59-f2cf87ff016a'::uuid,'f75758d8-2fcd-40ba-9432-3ff3032a71d1','54c9cc4e-0d46-4956-b92e-be9847f894de','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('f6e178a9-5de3-4312-87c5-81d88ae0b45b'::uuid,'f75758d8-2fcd-40ba-9432-3ff3032a71d1','b9739817-6408-4829-8719-1e26f8a9ceb3','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('34d4c1a3-b218-4083-93b5-cbbc57688594'::uuid,'f75758d8-2fcd-40ba-9432-3ff3032a71d1','117da2f5-fff0-41e0-bba1-837124373098','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('6b07db8d-9f7a-4d33-9a5f-7d98fc7038f1'::uuid,'f75758d8-2fcd-40ba-9432-3ff3032a71d1','6ba0aeca-19f8-4247-a317-fffa81c5d5c1','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('9d8cc94b-4d5f-4c62-b9db-b87bb0213b8d'::uuid,'f75758d8-2fcd-40ba-9432-3ff3032a71d1','0c95581d-67de-48ae-a54b-a3748851d613','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('d63a9079-c99b-4d92-864f-46cc9bb18388'::uuid,'f75758d8-2fcd-40ba-9432-3ff3032a71d1','45fce5ce-6a4c-4a6c-ab37-16ee0133628c','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('625970f9-7c93-4c3d-97fe-62f5a9d598f1'::uuid,'f75758d8-2fcd-40ba-9432-3ff3032a71d1','e6096350-9ac4-40aa-90c4-bbdff6e0b194','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('34fa9839-8289-473a-9095-c2b1159ef5d3'::uuid,'f75758d8-2fcd-40ba-9432-3ff3032a71d1','a1d31d35-c87d-4a7d-b0b8-8b2646b96e43','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('f747f231-66f5-4a52-bb71-8d7b5f618d23'::uuid,'f75758d8-2fcd-40ba-9432-3ff3032a71d1','b79978a7-21b7-4656-af83-25585acffb20','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), + ('0177f93a-15f6-41e5-a3ca-dc8f5bb727ab'::uuid,'f75758d8-2fcd-40ba-9432-3ff3032a71d1','d87d82da-3ac2-44e8-bce0-cb4de40f9a72','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), + ('cbf5b41f-2d89-4284-858f-d2cda7b060f7'::uuid,'f75758d8-2fcd-40ba-9432-3ff3032a71d1','1e6257e9-757d-4d59-8846-727dd8a055e7','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), + ('ebed3001-93f1-49ba-a935-3d463b0d76fc'::uuid,'f75758d8-2fcd-40ba-9432-3ff3032a71d1','2cbc2251-eb7d-4c69-a120-9a83785c994b','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false); + + +-- inserting params for ISLH +INSERT INTO service_params (id,service_id,service_item_param_key_id,created_at,updated_at,is_optional) VALUES + ('7e2e4b79-2f4c-451e-a28f-df1ad61c4f3b'::uuid,'9f3d551a-0725-430e-897e-80ee9add3ae9','164050e3-e35b-480d-bf6e-ed2fab86f370','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), + ('adff4edb-1f78-45de-9269-29016d09d597'::uuid,'9f3d551a-0725-430e-897e-80ee9add3ae9','e6096350-9ac4-40aa-90c4-bbdff6e0b194','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('2239c77e-e073-47f3-aed7-e1edc6b8a9a4'::uuid,'9f3d551a-0725-430e-897e-80ee9add3ae9','45fce5ce-6a4c-4a6c-ab37-16ee0133628c','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('53ab68f9-8da7-48fa-80ac-bf91f05a4650'::uuid,'9f3d551a-0725-430e-897e-80ee9add3ae9','b9739817-6408-4829-8719-1e26f8a9ceb3','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('e9d3bc63-bc4a-43d0-98f1-48e3e51d2307'::uuid,'9f3d551a-0725-430e-897e-80ee9add3ae9','0c95581d-67de-48ae-a54b-a3748851d613','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('6632cbad-3fa0-46b8-a1ac-0b2bb5123401'::uuid,'9f3d551a-0725-430e-897e-80ee9add3ae9','b79978a7-21b7-4656-af83-25585acffb20','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), + ('4ef58b87-8a93-44ec-b5c8-5b5779d8392e'::uuid,'9f3d551a-0725-430e-897e-80ee9add3ae9','599bbc21-8d1d-4039-9a89-ff52e3582144','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('d53e5f61-e92f-4ecb-9e1d-72ab8a99790f'::uuid,'9f3d551a-0725-430e-897e-80ee9add3ae9','a335e38a-7d95-4ba3-9c8b-75a5e00948bc','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), + ('d94d2c5e-e91a-47d1-96b3-1c5d68a745dd'::uuid,'9f3d551a-0725-430e-897e-80ee9add3ae9','b03af5dc-7701-4e22-a986-d1889a2a8f27','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), + ('7b8db256-e881-451e-a722-6431784e957f'::uuid,'9f3d551a-0725-430e-897e-80ee9add3ae9','add5114b-2a23-4e23-92b3-6dd0778dfc33','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), + ('fedbd62d-d2fa-42b1-b6f6-c9c07e8c4014'::uuid,'9f3d551a-0725-430e-897e-80ee9add3ae9','a1d31d35-c87d-4a7d-b0b8-8b2646b96e43','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('c7a66e66-dabe-4f0b-a70d-f9639e87761a'::uuid,'9f3d551a-0725-430e-897e-80ee9add3ae9','2e091a7d-a1fd-4017-9f2d-73ad752a30c2','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('18f8b39f-37a5-4536-85d8-4b8b0a6bff94'::uuid,'9f3d551a-0725-430e-897e-80ee9add3ae9','739bbc23-cd08-4612-8e5d-da992202344e','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('7f20ed2e-1bdf-4370-b028-1251c07d3da1'::uuid,'9f3d551a-0725-430e-897e-80ee9add3ae9','95ee2e21-b232-4d74-9ec5-218564a8a8b9','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('a5be6b6f-e007-4d9f-8b1b-63e8ed5c4337'::uuid,'9f3d551a-0725-430e-897e-80ee9add3ae9','9de7fd2a-75c7-4c5c-ba5d-1a92f0b2f5f4','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('df5665d7-7b3d-487d-9d71-95d0e2832ae1'::uuid,'9f3d551a-0725-430e-897e-80ee9add3ae9','d87d82da-3ac2-44e8-bce0-cb4de40f9a72','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), + ('c08f7ab1-6c3c-4627-b22f-1e987ba6f4f2'::uuid,'9f3d551a-0725-430e-897e-80ee9add3ae9','1e6257e9-757d-4d59-8846-727dd8a055e7','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), + ('4d9ed9b0-957d-4e6a-a3d4-5e2e2784ef62'::uuid,'9f3d551a-0725-430e-897e-80ee9add3ae9','14a93209-370d-42f3-8ca2-479c953be839','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), + ('6acb30b9-65a0-4902-85ed-1acb6f4ac930'::uuid,'9f3d551a-0725-430e-897e-80ee9add3ae9','5335e243-ab5b-4906-b84f-bd8c35ba64b3','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('fd83c2ba-0c59-4598-81d6-b56cc8d9979d'::uuid,'9f3d551a-0725-430e-897e-80ee9add3ae9','2cbc2251-eb7d-4c69-a120-9a83785c994b','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false); + +-- inserting params fo IOSFSC +INSERT INTO service_params (id,service_id,service_item_param_key_id,created_at,updated_at,is_optional) VALUES + ('f61ab040-dab4-4505-906d-d9a3a5da3515'::uuid,'81e29d0c-02a6-4a7a-be02-554deb3ee49e','0c95581d-67de-48ae-a54b-a3748851d613','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('421053c8-7b4a-44fc-8c73-14b72755c1f7'::uuid,'81e29d0c-02a6-4a7a-be02-554deb3ee49e','117da2f5-fff0-41e0-bba1-837124373098','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('8f8a8783-0ca2-4f0f-961f-07d1e3cdbf64'::uuid,'81e29d0c-02a6-4a7a-be02-554deb3ee49e','14a93209-370d-42f3-8ca2-479c953be839','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('ab332c8f-f46e-4d49-b29a-6adf7e67f9c7'::uuid,'81e29d0c-02a6-4a7a-be02-554deb3ee49e','1e6257e9-757d-4d59-8846-727dd8a055e7','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), + ('a6e24b83-9cb4-4e56-9e38-7bdbd9d5c5fe'::uuid,'81e29d0c-02a6-4a7a-be02-554deb3ee49e','84d86517-9b88-4520-8d67-5ba892b85d10','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('4e05a7c7-bd0a-4c94-a99b-f052a8812aef'::uuid,'81e29d0c-02a6-4a7a-be02-554deb3ee49e','54c9cc4e-0d46-4956-b92e-be9847f894de','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('8ec5cd5d-d249-42e9-b11d-76a243d4045f'::uuid,'81e29d0c-02a6-4a7a-be02-554deb3ee49e','6ba0aeca-19f8-4247-a317-fffa81c5d5c1','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('2b6b9d89-65f3-4291-9e44-48d18d2a4070'::uuid,'81e29d0c-02a6-4a7a-be02-554deb3ee49e','a1d31d35-c87d-4a7d-b0b8-8b2646b96e43','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('a3d3c08f-d2a3-4ad1-b85f-b1daee12d71c'::uuid,'81e29d0c-02a6-4a7a-be02-554deb3ee49e','adeb57e5-6b1c-4c0f-b5c9-9e57e600303f','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('94d45661-82ac-479f-b7be-8e7a50ad46db'::uuid,'81e29d0c-02a6-4a7a-be02-554deb3ee49e','b79978a7-21b7-4656-af83-25585acffb20','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), + ('c897cfa3-9b06-47b5-8e12-522f0897e59a'::uuid,'81e29d0c-02a6-4a7a-be02-554deb3ee49e','b9739817-6408-4829-8719-1e26f8a9ceb3','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('61832b60-2a2d-4e35-a799-b7ff9fa6a01e'::uuid,'81e29d0c-02a6-4a7a-be02-554deb3ee49e','d87d82da-3ac2-44e8-bce0-cb4de40f9a72','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), + ('46531d32-91a5-4d98-a206-d0f1e14e2ff4'::uuid,'81e29d0c-02a6-4a7a-be02-554deb3ee49e','cd6d6ddf-7104-4d24-a8d6-d37fed61defe','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('a1d4f95e-f28f-4b6a-b83f-8f328f2b2498'::uuid,'81e29d0c-02a6-4a7a-be02-554deb3ee49e','f9753611-4b3e-4bf5-8e00-6d9ce9900f50','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false); + + +-- inserting params fo IDSFSC +INSERT INTO service_params (id,service_id,service_item_param_key_id,created_at,updated_at,is_optional) VALUES + ('25d90d5b-c58f-45e7-8c60-e7f63a0535b6'::uuid,'690a5fc1-0ea5-4554-8294-a367b5daefa9','0c95581d-67de-48ae-a54b-a3748851d613','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('8bb29da3-32e7-4e98-b241-63b8c8c81c3b'::uuid,'690a5fc1-0ea5-4554-8294-a367b5daefa9','117da2f5-fff0-41e0-bba1-837124373098','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('b8c12287-dcf6-4f88-bf7b-f7e99283f23d'::uuid,'690a5fc1-0ea5-4554-8294-a367b5daefa9','14a93209-370d-42f3-8ca2-479c953be839','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('3b9f3eab-8e18-4888-81f4-c442b4e951cf'::uuid,'690a5fc1-0ea5-4554-8294-a367b5daefa9','1e6257e9-757d-4d59-8846-727dd8a055e7','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), + ('7a8430f7-ff55-4a80-b174-e1d4a2f21f25'::uuid,'690a5fc1-0ea5-4554-8294-a367b5daefa9','70eecf7f-beae-4906-95ba-cbfe6797cf3a','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('2eaf2e5b-254e-48f0-a2c5-98d04087293f'::uuid,'690a5fc1-0ea5-4554-8294-a367b5daefa9','54c9cc4e-0d46-4956-b92e-be9847f894de','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('71f571c5-99a0-420a-b375-bb859e3488a2'::uuid,'690a5fc1-0ea5-4554-8294-a367b5daefa9','6ba0aeca-19f8-4247-a317-fffa81c5d5c1','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('cce73f9e-e3db-4d7f-a908-d9985f1b3f27'::uuid,'690a5fc1-0ea5-4554-8294-a367b5daefa9','a1d31d35-c87d-4a7d-b0b8-8b2646b96e43','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('46c559e5-9f49-4b7e-98b6-b9d8e2a4e2cf'::uuid,'690a5fc1-0ea5-4554-8294-a367b5daefa9','adeb57e5-6b1c-4c0f-b5c9-9e57e600303f','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('4512b905-cb68-4087-90b5-74e80ba9ec16'::uuid,'690a5fc1-0ea5-4554-8294-a367b5daefa9','b79978a7-21b7-4656-af83-25585acffb20','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), + ('d9887f60-e930-4f95-b53e-e9f0d8a445d3'::uuid,'690a5fc1-0ea5-4554-8294-a367b5daefa9','b9739817-6408-4829-8719-1e26f8a9ceb3','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('f65b54fa-0e1c-45cc-b7f4-b41d356b970d'::uuid,'690a5fc1-0ea5-4554-8294-a367b5daefa9','d87d82da-3ac2-44e8-bce0-cb4de40f9a72','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), + ('ce36b6e0-bcbf-4e96-9c5e-bd93fe9084c9'::uuid,'690a5fc1-0ea5-4554-8294-a367b5daefa9','cd6d6ddf-7104-4d24-a8d6-d37fed61defe','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('d9acb388-09a5-464b-bb50-bf418b25e96a'::uuid,'690a5fc1-0ea5-4554-8294-a367b5daefa9','f9753611-4b3e-4bf5-8e00-6d9ce9900f50','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false); + +-- inserting params fo IHPK +INSERT INTO service_params (id,service_id,service_item_param_key_id,created_at,updated_at,is_optional) VALUES + ('d9acb388-09a5-464b-bb50-bf418b25e96b'::uuid,'67ba1eaf-6ffd-49de-9a69-497be7789877','a1d31d35-c87d-4a7d-b0b8-8b2646b96e43','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('3c2297b0-1ec7-4261-a41d-37e58999258b'::uuid,'67ba1eaf-6ffd-49de-9a69-497be7789877','cd37b2a6-ac7d-4c93-a148-ca67f7f67cff','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false); + +-- inserting params fo IHUPK +INSERT INTO service_params (id,service_id,service_item_param_key_id,created_at,updated_at,is_optional) VALUES + ('d9acb388-09a5-464b-bb50-bf418b25e96c'::uuid,'56e91c2d-015d-4243-9657-3ed34867abaa','a1d31d35-c87d-4a7d-b0b8-8b2646b96e43','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('c045524a-90ec-4116-80a1-e2edb5cdf38f'::uuid,'56e91c2d-015d-4243-9657-3ed34867abaa','cd37b2a6-ac7d-4c93-a148-ca67f7f67cff','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false); diff --git a/pkg/handlers/primeapi/allowed_payment_service_item_params.go b/pkg/handlers/primeapi/allowed_payment_service_item_params.go index cc40edc4ff8..809582a592a 100644 --- a/pkg/handlers/primeapi/allowed_payment_service_item_params.go +++ b/pkg/handlers/primeapi/allowed_payment_service_item_params.go @@ -67,6 +67,21 @@ var ( models.ReServiceCodeDOSFSC: { models.ServiceItemParamNameWeightBilled, }, + models.ReServiceCodeISLH: { + models.ServiceItemParamNameWeightBilled, + }, + models.ReServiceCodeIHPK: { + models.ServiceItemParamNameWeightBilled, + }, + models.ReServiceCodeIHUPK: { + models.ServiceItemParamNameWeightBilled, + }, + models.ReServiceCodePOEFSC: { + models.ServiceItemParamNameWeightBilled, + }, + models.ReServiceCodePODFSC: { + models.ServiceItemParamNameWeightBilled, + }, } ) diff --git a/pkg/handlers/primeapi/payment_request_test.go b/pkg/handlers/primeapi/payment_request_test.go index 768e10c0033..114f03aa49b 100644 --- a/pkg/handlers/primeapi/payment_request_test.go +++ b/pkg/handlers/primeapi/payment_request_test.go @@ -947,13 +947,6 @@ func (suite *HandlerSuite) TestCreatePaymentRequestHandlerInvalidMTOReferenceID( suite.IsType(&paymentrequestop.CreatePaymentRequestUnprocessableEntity{}, response) typedResponse := response.(*paymentrequestop.CreatePaymentRequestUnprocessableEntity) - // Validate outgoing payload - // TODO: Can't validate the response because of the issue noted below. Figure out a way to - // either alter the service or relax the swagger requirements. - // suite.NoError(typedResponse.Payload.Validate(strfmt.Default)) - // CreatePaymentRequestCheck is returning apperror.InvalidCreateInputError without any validation errors - // so InvalidFields won't be added to the payload. - suite.Contains(*typedResponse.Payload.Detail, "has missing ReferenceID") }) @@ -1013,13 +1006,6 @@ func (suite *HandlerSuite) TestCreatePaymentRequestHandlerInvalidMTOReferenceID( suite.IsType(&paymentrequestop.CreatePaymentRequestUnprocessableEntity{}, response) typedResponse := response.(*paymentrequestop.CreatePaymentRequestUnprocessableEntity) - // Validate outgoing payload - // TODO: Can't validate the response because of the issue noted below. Figure out a way to - // either alter the service or relax the swagger requirements. - // suite.NoError(typedResponse.Payload.Validate(strfmt.Default)) - // CreatePaymentRequestCheck is returning apperror.InvalidCreateInputError without any validation errors - // so InvalidFields won't be added to the payload. - suite.Contains(*typedResponse.Payload.Detail, "has missing ReferenceID") }) } diff --git a/pkg/services/ghc_rate_engine.go b/pkg/services/ghc_rate_engine.go index 5d17e0388ce..9bee5c8f59f 100644 --- a/pkg/services/ghc_rate_engine.go +++ b/pkg/services/ghc_rate_engine.go @@ -232,3 +232,35 @@ type DomesticOriginSITFuelSurchargePricer interface { ) ParamsPricer } + +// IntlShippingAndLinehaulPricer prices international shipping and linehaul for a move +// +//go:generate mockery --name IntlShippingAndLinehaulPricer +type IntlShippingAndLinehaulPricer interface { + Price(appCtx appcontext.AppContext, contractCode string, requestedPickupDate time.Time, distance unit.Miles, weight unit.Pound, serviceArea string, isPPM bool) (unit.Cents, PricingDisplayParams, error) + ParamsPricer +} + +// IntlHHGPackPricer prices international packing for an HHG shipment within a move +// +//go:generate mockery --name IntlHHGPackPricer +type IntlHHGPackPricer interface { + Price(appCtx appcontext.AppContext, contractCode string, requestedPickupDate time.Time, weight unit.Pound, servicesScheduleOrigin int, isPPM bool) (unit.Cents, PricingDisplayParams, error) + ParamsPricer +} + +// IntlHHGUnpackPricer prices international unpacking for an HHG shipment within a move +// +//go:generate mockery --name IntlHHGUnpackPricer +type IntlHHGUnpackPricer interface { + Price(appCtx appcontext.AppContext, contractCode string, requestedPickupDate time.Time, weight unit.Pound, servicesScheduleDest int, isPPM bool) (unit.Cents, PricingDisplayParams, error) + ParamsPricer +} + +// IntlPortFuelSurchargePricer prices the POEFSC/PODFSC service items on a shipment within a move +// +//go:generate mockery --name IntlPortFuelSurchargePricer +type IntlPortFuelSurchargePricer 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 +} diff --git a/pkg/services/ghcrateengine/fuel_surcharge_pricer.go b/pkg/services/ghcrateengine/fuel_surcharge_pricer.go index 7371a7f397f..72b71bafce6 100644 --- a/pkg/services/ghcrateengine/fuel_surcharge_pricer.go +++ b/pkg/services/ghcrateengine/fuel_surcharge_pricer.go @@ -27,7 +27,7 @@ func NewFuelSurchargePricer() services.FuelSurchargePricer { return &fuelSurchargePricer{} } -// Price determines the price for a counseling service +// Price determines the price for fuel surcharge func (p fuelSurchargePricer) Price(_ appcontext.AppContext, actualPickupDate time.Time, distance unit.Miles, weight unit.Pound, fscWeightBasedDistanceMultiplier float64, eiaFuelPrice unit.Millicents, isPPM bool) (unit.Cents, services.PricingDisplayParams, error) { // Validate parameters if actualPickupDate.IsZero() { diff --git a/pkg/services/ghcrateengine/intl_hhg_pack_pricer.go b/pkg/services/ghcrateengine/intl_hhg_pack_pricer.go new file mode 100644 index 00000000000..9318f930e10 --- /dev/null +++ b/pkg/services/ghcrateengine/intl_hhg_pack_pricer.go @@ -0,0 +1,56 @@ +package ghcrateengine + +import ( + "time" + + "github.com/transcom/mymove/pkg/appcontext" + "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/unit" +) + +type intlHHGPackPricer struct { +} + +// NewDomesticPackPricer creates a new pricer for the domestic pack service +func NewIntlHHGPackPricer() services.IntlHHGPackPricer { + return &intlHHGPackPricer{} +} + +// Price determines the price for a domestic pack service +func (p intlHHGPackPricer) Price(appCtx appcontext.AppContext, contractCode string, referenceDate time.Time, weight unit.Pound, servicesScheduleOrigin int, isPPM bool) (unit.Cents, services.PricingDisplayParams, error) { + return priceIntlPackUnpack(appCtx, models.ReServiceCodeIHPK, contractCode, referenceDate, weight, servicesScheduleOrigin, isPPM) +} + +// PriceUsingParams determines the price for a domestic pack service given PaymentServiceItemParams +func (p intlHHGPackPricer) PriceUsingParams(appCtx appcontext.AppContext, params models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error) { + contractCode, err := getParamString(params, models.ServiceItemParamNameContractCode) + if err != nil { + return unit.Cents(0), nil, err + } + + referenceDate, err := getParamTime(params, models.ServiceItemParamNameReferenceDate) + if err != nil { + return unit.Cents(0), nil, err + } + + servicesScheduleOrigin, err := getParamInt(params, models.ServiceItemParamNameServicesScheduleOrigin) + if err != nil { + return unit.Cents(0), nil, err + } + + weightBilled, err := getParamInt(params, models.ServiceItemParamNameWeightBilled) + 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, contractCode, referenceDate, unit.Pound(weightBilled), servicesScheduleOrigin, isPPM) +} diff --git a/pkg/services/ghcrateengine/intl_hhg_unpack_pricer.go b/pkg/services/ghcrateengine/intl_hhg_unpack_pricer.go new file mode 100644 index 00000000000..47e428f1e6d --- /dev/null +++ b/pkg/services/ghcrateengine/intl_hhg_unpack_pricer.go @@ -0,0 +1,56 @@ +package ghcrateengine + +import ( + "time" + + "github.com/transcom/mymove/pkg/appcontext" + "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/unit" +) + +type intlHHGUnpackPricer struct { +} + +// NewDomesticUnpackPricer creates a new pricer for the domestic unpack service +func NewIntlHHGUnpackPricer() services.IntlHHGUnpackPricer { + return &intlHHGUnpackPricer{} +} + +// Price determines the price for a domestic unpack service +func (p intlHHGUnpackPricer) Price(appCtx appcontext.AppContext, contractCode string, referenceDate time.Time, weight unit.Pound, servicesScheduleDest int, isPPM bool) (unit.Cents, services.PricingDisplayParams, error) { + return priceDomesticPackUnpack(appCtx, models.ReServiceCodeDUPK, contractCode, referenceDate, weight, servicesScheduleDest, isPPM) +} + +// PriceUsingParams determines the price for a domestic unpack service given PaymentServiceItemParams +func (p intlHHGUnpackPricer) PriceUsingParams(appCtx appcontext.AppContext, params models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error) { + contractCode, err := getParamString(params, models.ServiceItemParamNameContractCode) + if err != nil { + return unit.Cents(0), nil, err + } + + referenceDate, err := getParamTime(params, models.ServiceItemParamNameReferenceDate) + if err != nil { + return unit.Cents(0), nil, err + } + + servicesScheduleDest, err := getParamInt(params, models.ServiceItemParamNameServicesScheduleDest) + if err != nil { + return unit.Cents(0), nil, err + } + + weightBilled, err := getParamInt(params, models.ServiceItemParamNameWeightBilled) + 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, contractCode, referenceDate, unit.Pound(weightBilled), servicesScheduleDest, isPPM) +} diff --git a/pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer.go b/pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer.go new file mode 100644 index 00000000000..0acac1eaffa --- /dev/null +++ b/pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer.go @@ -0,0 +1,111 @@ +package ghcrateengine + +import ( + "database/sql" + "fmt" + "math" + "time" + + "github.com/gofrs/uuid" + "github.com/pkg/errors" + + "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" +) + +// FuelSurchargePricer is a service object to price domestic shorthaul +type portFuelSurchargePricer struct { +} + +// NewFuelSurchargePricer is the public constructor for a domesticFuelSurchargePricer using Pop +func NewPortFuelSurchargePricer() services.IntlPortFuelSurchargePricer { + return &portFuelSurchargePricer{} +} + +// Price determines the price for fuel surcharge +func (p portFuelSurchargePricer) Price(_ appcontext.AppContext, actualPickupDate time.Time, distance unit.Miles, weight unit.Pound, fscWeightBasedDistanceMultiplier float64, eiaFuelPrice unit.Millicents, isPPM bool) (unit.Cents, services.PricingDisplayParams, error) { + // Validate parameters + if actualPickupDate.IsZero() { + return 0, nil, errors.New("ActualPickupDate is required") + } + if distance <= 0 { + return 0, nil, errors.New("Distance must be greater than 0") + } + if !isPPM && weight < minIntlWeightHHG { + return 0, nil, fmt.Errorf("weight must be a minimum of %d", minIntlWeightHHG) + } + if fscWeightBasedDistanceMultiplier == 0 { + return 0, nil, errors.New("WeightBasedDistanceMultiplier is required") + } + if eiaFuelPrice == 0 { + return 0, nil, errors.New("EIAFuelPrice is required") + } + + fscPriceDifferenceInCents := (eiaFuelPrice - baseGHCDieselFuelPrice).Float64() / 1000.0 + fscMultiplier := fscWeightBasedDistanceMultiplier * distance.Float64() + fscPrice := fscMultiplier * fscPriceDifferenceInCents * 100 + totalCost := unit.Cents(math.Round(fscPrice)) + + displayParams := services.PricingDisplayParams{ + {Key: models.ServiceItemParamNameFSCPriceDifferenceInCents, Value: FormatFloat(fscPriceDifferenceInCents, 1)}, + {Key: models.ServiceItemParamNameFSCMultiplier, Value: FormatFloat(fscMultiplier, 7)}, + } + + return totalCost, displayParams, nil +} + +func (p portFuelSurchargePricer) 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.ServiceItemParamNameDistanceZip) + 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) +} diff --git a/pkg/services/ghcrateengine/intl_shipping_and_linehaul_pricer.go b/pkg/services/ghcrateengine/intl_shipping_and_linehaul_pricer.go new file mode 100644 index 00000000000..2fec86a9b57 --- /dev/null +++ b/pkg/services/ghcrateengine/intl_shipping_and_linehaul_pricer.go @@ -0,0 +1,137 @@ +package ghcrateengine + +import ( + "fmt" + "math" + "time" + + "github.com/pkg/errors" + + "github.com/transcom/mymove/pkg/appcontext" + "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/unit" +) + +type intlShippingAndLinehaulPricer struct { +} + +// NewDomesticLinehaulPricer creates a new pricer for domestic linehaul services +func NewIntlShippingAndLinehaulPricer() services.DomesticLinehaulPricer { + return &intlShippingAndLinehaulPricer{} +} + +// Price determines the price for a domestic linehaul +func (p intlShippingAndLinehaulPricer) Price(appCtx appcontext.AppContext, contractCode string, referenceDate time.Time, distance unit.Miles, weight unit.Pound, serviceArea string, isPPM bool) (unit.Cents, services.PricingDisplayParams, error) { + // Validate parameters + if len(contractCode) == 0 { + return 0, nil, errors.New("ContractCode is required") + } + if referenceDate.IsZero() { + return 0, nil, errors.New("ReferenceDate is required") + } + if !isPPM && weight < dlhPricerMinimumWeight { + return 0, nil, fmt.Errorf("Weight must be at least %d", dlhPricerMinimumWeight) + } + if len(serviceArea) == 0 { + return 0, nil, errors.New("ServiceArea is required") + } + + isPeakPeriod := IsPeakPeriod(referenceDate) + finalWeight := weight + + if isPPM && weight < dlhPricerMinimumWeight { + finalWeight = dlhPricerMinimumWeight + } + + domesticLinehaulPrice, err := fetchDomesticLinehaulPrice(appCtx, contractCode, isPeakPeriod, distance, finalWeight, serviceArea) + if err != nil { + return unit.Cents(0), nil, fmt.Errorf("could not fetch domestic linehaul rate: %w", err) + } + + basePrice := domesticLinehaulPrice.PriceMillicents.Float64() / 1000 + escalatedPrice, contractYear, err := escalatePriceForContractYear( + appCtx, + domesticLinehaulPrice.ContractID, + referenceDate, + true, + basePrice) + if err != nil { + return 0, nil, fmt.Errorf("could not calculate escalated price: %w", err) + } + + totalPrice := finalWeight.ToCWTFloat64() * distance.Float64() * escalatedPrice + totalPriceCents := unit.Cents(math.Round(totalPrice)) + + params := services.PricingDisplayParams{ + {Key: models.ServiceItemParamNameContractYearName, Value: contractYear.Name}, + {Key: models.ServiceItemParamNameEscalationCompounded, Value: FormatEscalation(contractYear.EscalationCompounded)}, + {Key: models.ServiceItemParamNameIsPeak, Value: FormatBool(isPeakPeriod)}, + {Key: models.ServiceItemParamNamePriceRateOrFactor, Value: FormatFloat(domesticLinehaulPrice.PriceMillicents.ToDollarFloatNoRound(), 3)}, + } + + if isPPM && weight < dlhPricerMinimumWeight { + weightFactor := float64(weight) / float64(dlhPricerMinimumWeight) + cost := float64(weightFactor) * float64(totalPriceCents) + return unit.Cents(cost), params, nil + } + + return totalPriceCents, params, nil +} + +// PriceUsingParams determines the price for a domestic linehaul given PaymentServiceItemParams +func (p intlShippingAndLinehaulPricer) PriceUsingParams(appCtx appcontext.AppContext, params models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error) { + contractCode, err := getParamString(params, models.ServiceItemParamNameContractCode) + if err != nil { + return unit.Cents(0), nil, err + } + + distance, err := getParamInt(params, models.ServiceItemParamNameDistanceZip) + if err != nil { + return unit.Cents(0), nil, err + } + + referenceDate, err := getParamTime(params, models.ServiceItemParamNameReferenceDate) + if err != nil { + return unit.Cents(0), nil, err + } + + serviceAreaOrigin, err := getParamString(params, models.ServiceItemParamNameServiceAreaOrigin) + if err != nil { + return unit.Cents(0), nil, err + } + + weightBilled, err := getParamInt(params, models.ServiceItemParamNameWeightBilled) + 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 or distance + // this flag is passed into the Price function to ensure the weight and distance mins + // are not enforced for PPMs + isPPM = true + } + + return p.Price(appCtx, contractCode, referenceDate, unit.Miles(distance), unit.Pound(weightBilled), serviceAreaOrigin, isPPM) +} + +// func fetchDomesticLinehaulPrice(appCtx appcontext.AppContext, contractCode string, isPeakPeriod bool, distance unit.Miles, weight unit.Pound, serviceArea string) (models.ReDomesticLinehaulPrice, error) { +// var domesticLinehaulPrice models.ReDomesticLinehaulPrice +// err := appCtx.DB().Q(). +// Join("re_domestic_service_areas sa", "domestic_service_area_id = sa.id"). +// Join("re_contracts c", "re_domestic_linehaul_prices.contract_id = c.id"). +// Where("c.code = $1", contractCode). +// Where("re_domestic_linehaul_prices.is_peak_period = $2", isPeakPeriod). +// Where("$3 between weight_lower and weight_upper", weight). +// Where("$4 between miles_lower and miles_upper", distance). +// Where("sa.service_area = $5", serviceArea). +// First(&domesticLinehaulPrice) + +// if err != nil { +// return models.ReDomesticLinehaulPrice{}, err +// } + +// return domesticLinehaulPrice, nil +// } diff --git a/pkg/services/ghcrateengine/pricer_helpers_intl.go b/pkg/services/ghcrateengine/pricer_helpers_intl.go new file mode 100644 index 00000000000..9f88b2d4ef1 --- /dev/null +++ b/pkg/services/ghcrateengine/pricer_helpers_intl.go @@ -0,0 +1,86 @@ +package ghcrateengine + +import ( + "fmt" + "math" + "time" + + "github.com/pkg/errors" + + "github.com/transcom/mymove/pkg/appcontext" + "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/unit" +) + +func priceIntlPackUnpack(appCtx appcontext.AppContext, packUnpackCode models.ReServiceCode, contractCode string, referenceDate time.Time, weight unit.Pound, servicesSchedule int, isPPM bool) (unit.Cents, services.PricingDisplayParams, error) { + // Validate parameters + var intlOtherPriceCode models.ReServiceCode + switch packUnpackCode { + case models.ReServiceCodeIHPK: + intlOtherPriceCode = models.ReServiceCodeIHPK + case models.ReServiceCodeIHUPK: + intlOtherPriceCode = models.ReServiceCodeIHUPK + default: + return 0, nil, fmt.Errorf("unsupported pack/unpack code of %s", packUnpackCode) + } + if len(contractCode) == 0 { + return 0, nil, errors.New("ContractCode is required") + } + if referenceDate.IsZero() { + return 0, nil, errors.New("ReferenceDate is required") + } + if !isPPM && weight < minDomesticWeight { + return 0, nil, fmt.Errorf("Weight must be a minimum of %d", minDomesticWeight) + } + if servicesSchedule == 0 { + return 0, nil, errors.New("Services schedule is required") + } + + isPeakPeriod := IsPeakPeriod(referenceDate) + + intlOtherPrice, err := fetchIntlOtherPrice(appCtx, contractCode, intlOtherPriceCode, servicesSchedule, isPeakPeriod) + if err != nil { + return 0, nil, fmt.Errorf("could not lookup domestic other price: %w", err) + } + + finalWeight := weight + if isPPM && weight < minDomesticWeight { + finalWeight = minDomesticWeight + } + + basePrice := intlOtherPrice.PerUnitCents.Float64() + escalatedPrice, contractYear, err := escalatePriceForContractYear(appCtx, intlOtherPrice.ContractID, referenceDate, false, basePrice) + if err != nil { + return 0, nil, fmt.Errorf("could not calculate escalated price: %w", err) + } + + escalatedPrice = escalatedPrice * finalWeight.ToCWTFloat64() + + displayParams := services.PricingDisplayParams{ + { + Key: models.ServiceItemParamNameContractYearName, + Value: contractYear.Name, + }, + { + Key: models.ServiceItemParamNamePriceRateOrFactor, + Value: FormatCents(intlOtherPrice.PerUnitCents), + }, + { + Key: models.ServiceItemParamNameIsPeak, + Value: FormatBool(isPeakPeriod), + }, + { + Key: models.ServiceItemParamNameEscalationCompounded, + Value: FormatEscalation(contractYear.EscalationCompounded), + }, + } + + totalCost := unit.Cents(math.Round(escalatedPrice)) + if isPPM && weight < minDomesticWeight { + weightFactor := float64(weight) / float64(minDomesticWeight) + cost := float64(weightFactor) * float64(totalCost) + return unit.Cents(cost), displayParams, nil + } + return totalCost, displayParams, nil +} diff --git a/pkg/services/ghcrateengine/pricer_query_helpers.go b/pkg/services/ghcrateengine/pricer_query_helpers.go index 5fc88dd7d5e..05c7bf668df 100644 --- a/pkg/services/ghcrateengine/pricer_query_helpers.go +++ b/pkg/services/ghcrateengine/pricer_query_helpers.go @@ -45,6 +45,24 @@ func fetchDomOtherPrice(appCtx appcontext.AppContext, contractCode string, servi return domOtherPrice, nil } +func fetchIntlOtherPrice(appCtx appcontext.AppContext, contractCode string, serviceCode models.ReServiceCode, schedule int, isPeakPeriod bool) (models.ReIntlOtherPrice, error) { + var intlOtherPrice models.ReIntlOtherPrice + err := appCtx.DB().Q(). + Join("re_services", "service_id = re_services.id"). + Join("re_contracts", "re_contracts.id = re_intl_other_prices.contract_id"). + Where("re_contracts.code = $1", contractCode). + Where("re_services.code = $2", serviceCode). + Where("schedule = $3", schedule). + Where("is_peak_period = $4", isPeakPeriod). + First(&intlOtherPrice) + + if err != nil { + return models.ReIntlOtherPrice{}, err + } + + return intlOtherPrice, nil +} + func fetchDomServiceAreaPrice(appCtx appcontext.AppContext, contractCode string, serviceCode models.ReServiceCode, serviceArea string, isPeakPeriod bool) (models.ReDomesticServiceAreaPrice, error) { var domServiceAreaPrice models.ReDomesticServiceAreaPrice err := appCtx.DB().Q(). diff --git a/pkg/services/ghcrateengine/service_item_pricer.go b/pkg/services/ghcrateengine/service_item_pricer.go index 81ad0a42cf5..ecaf9b8139b 100644 --- a/pkg/services/ghcrateengine/service_item_pricer.go +++ b/pkg/services/ghcrateengine/service_item_pricer.go @@ -94,6 +94,16 @@ func PricerForServiceItem(serviceCode models.ReServiceCode) (services.ParamsPric return NewDomesticOriginSITPickupPricer(), nil case models.ReServiceCodeDDDSIT: return NewDomesticDestinationSITDeliveryPricer(), nil + case models.ReServiceCodeISLH: + return NewIntlShippingAndLinehaulPricer(), nil + case models.ReServiceCodeIHPK: + return NewIntlHHGPackPricer(), nil + case models.ReServiceCodeIHUPK: + return NewIntlHHGUnpackPricer(), nil + case models.ReServiceCodePOEFSC: + return NewPortFuelSurchargePricer(), nil + case models.ReServiceCodePODFSC: + return NewPortFuelSurchargePricer(), nil default: // TODO: We may want a different error type here after all pricers have been implemented return nil, apperror.NewNotImplementedError(fmt.Sprintf("pricer not found for code %s", serviceCode)) diff --git a/pkg/services/ghcrateengine/shared.go b/pkg/services/ghcrateengine/shared.go index 3f89a29cb40..171d8668bb1 100644 --- a/pkg/services/ghcrateengine/shared.go +++ b/pkg/services/ghcrateengine/shared.go @@ -9,6 +9,9 @@ import ( // minDomesticWeight is the minimum weight used in domestic calculations (weights below this are upgraded to the min) const minDomesticWeight = unit.Pound(500) +// minIntlWeightHHG is the minimum weight used in intl calculations (weights below this are upgraded to the min) +const minIntlWeightHHG = unit.Pound(500) + // dateInYear represents a specific date in a year (without caring what year it is) type dateInYear struct { month time.Month diff --git a/pkg/services/payment_request/payment_request_creator.go b/pkg/services/payment_request/payment_request_creator.go index 0b301193287..63b1f0950a7 100644 --- a/pkg/services/payment_request/payment_request_creator.go +++ b/pkg/services/payment_request/payment_request_creator.go @@ -402,7 +402,6 @@ func (p *paymentRequestCreator) createPaymentServiceItem(appCtx appcontext.AppCo paymentServiceItem.PaymentRequestID = paymentRequest.ID paymentServiceItem.PaymentRequest = *paymentRequest paymentServiceItem.Status = models.PaymentServiceItemStatusRequested - // No pricing at this point, so skipping the PriceCents field. paymentServiceItem.RequestedAt = requestedAt verrs, err := appCtx.DB().ValidateAndCreate(&paymentServiceItem) From 834cd2b2063dc0615501dbda34b215781e66ee41 Mon Sep 17 00:00:00 2001 From: Daniel Jordan Date: Thu, 2 Jan 2025 17:08:33 +0000 Subject: [PATCH 02/14] pack and unpack done, need tests --- ...aram_values_to_service_params_table.up.sql | 20 ++++-- pkg/models/re_rate_area.go | 17 +++++ pkg/models/service_item_param_key.go | 8 +++ .../per_unit_cents_lookup.go | 63 +++++++++++++++++++ .../port_name_lookup.go | 34 ++++++++++ .../service_param_value_lookups.go | 13 ++++ pkg/services/ghc_rate_engine.go | 4 +- .../ghcrateengine/intl_hhg_pack_pricer.go | 19 ++---- .../ghcrateengine/intl_hhg_unpack_pricer.go | 19 ++---- .../ghcrateengine/pricer_helpers_intl.go | 39 +++--------- .../ghcrateengine/pricer_query_helpers.go | 28 +++------ .../CreatePaymentRequestForm.jsx | 4 +- 12 files changed, 180 insertions(+), 88 deletions(-) create mode 100644 pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup.go create mode 100644 pkg/payment_request/service_param_value_lookups/port_name_lookup.go diff --git a/migrations/app/schema/20241226173330_add_intl_param_values_to_service_params_table.up.sql b/migrations/app/schema/20241226173330_add_intl_param_values_to_service_params_table.up.sql index 26ccf6dff90..14dc1422582 100644 --- a/migrations/app/schema/20241226173330_add_intl_param_values_to_service_params_table.up.sql +++ b/migrations/app/schema/20241226173330_add_intl_param_values_to_service_params_table.up.sql @@ -1,3 +1,7 @@ +-- need to add in param keys for international shipments, this will be used to show breakdowns to the TIO +INSERT INTO service_item_param_keys (id, key,description,type,origin,created_at,updated_at) VALUES + ('d9ad3878-4b94-4722-bbaf-d4b8080f339d','PortName','Name of the port for an international shipment','STRING','PRICER','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957'), + ('597bb77e-0ce7-4ba2-9624-24300962625f','PerUnitCents','Per unit cents for a service item','INTEGER','SYSTEM','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957'); -- inserting params for PODFSC INSERT INTO service_params (id,service_id,service_item_param_key_id,created_at,updated_at,is_optional) VALUES @@ -14,7 +18,8 @@ INSERT INTO service_params (id,service_id,service_item_param_key_id,created_at,u ('aa68a318-fe17-445c-ab53-0505fe48d0bb'::uuid,'388115e8-abe9-441d-96cf-a39f24baa0a3','b79978a7-21b7-4656-af83-25585acffb20','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), ('882e7978-9754-4c8e-bb71-8fe4f4059503'::uuid,'388115e8-abe9-441d-96cf-a39f24baa0a3','d87d82da-3ac2-44e8-bce0-cb4de40f9a72','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), ('8fc4571d-235b-4d4f-90e4-77e7ad9250d5'::uuid,'388115e8-abe9-441d-96cf-a39f24baa0a3','1e6257e9-757d-4d59-8846-727dd8a055e7','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), - ('836606ce-894e-4765-bba5-b696cb5fe8cc'::uuid,'388115e8-abe9-441d-96cf-a39f24baa0a3','2cbc2251-eb7d-4c69-a120-9a83785c994b','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false); + ('836606ce-894e-4765-bba5-b696cb5fe8cc'::uuid,'388115e8-abe9-441d-96cf-a39f24baa0a3','2cbc2251-eb7d-4c69-a120-9a83785c994b','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('08701fa6-6352-4808-88b6-1fe103068f29'::uuid,'388115e8-abe9-441d-96cf-a39f24baa0a3','d9ad3878-4b94-4722-bbaf-d4b8080f339d','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false); -- inserting params for POEFSC INSERT INTO service_params (id,service_id,service_item_param_key_id,created_at,updated_at,is_optional) VALUES @@ -31,7 +36,8 @@ INSERT INTO service_params (id,service_id,service_item_param_key_id,created_at,u ('f747f231-66f5-4a52-bb71-8d7b5f618d23'::uuid,'f75758d8-2fcd-40ba-9432-3ff3032a71d1','b79978a7-21b7-4656-af83-25585acffb20','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), ('0177f93a-15f6-41e5-a3ca-dc8f5bb727ab'::uuid,'f75758d8-2fcd-40ba-9432-3ff3032a71d1','d87d82da-3ac2-44e8-bce0-cb4de40f9a72','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), ('cbf5b41f-2d89-4284-858f-d2cda7b060f7'::uuid,'f75758d8-2fcd-40ba-9432-3ff3032a71d1','1e6257e9-757d-4d59-8846-727dd8a055e7','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), - ('ebed3001-93f1-49ba-a935-3d463b0d76fc'::uuid,'f75758d8-2fcd-40ba-9432-3ff3032a71d1','2cbc2251-eb7d-4c69-a120-9a83785c994b','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false); + ('ebed3001-93f1-49ba-a935-3d463b0d76fc'::uuid,'f75758d8-2fcd-40ba-9432-3ff3032a71d1','2cbc2251-eb7d-4c69-a120-9a83785c994b','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('aa7c3492-be44-46dd-983e-478623edc0be'::uuid,'f75758d8-2fcd-40ba-9432-3ff3032a71d1','d9ad3878-4b94-4722-bbaf-d4b8080f339d','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false); -- inserting params for ISLH @@ -94,10 +100,12 @@ INSERT INTO service_params (id,service_id,service_item_param_key_id,created_at,u -- inserting params fo IHPK INSERT INTO service_params (id,service_id,service_item_param_key_id,created_at,updated_at,is_optional) VALUES - ('d9acb388-09a5-464b-bb50-bf418b25e96b'::uuid,'67ba1eaf-6ffd-49de-9a69-497be7789877','a1d31d35-c87d-4a7d-b0b8-8b2646b96e43','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), - ('3c2297b0-1ec7-4261-a41d-37e58999258b'::uuid,'67ba1eaf-6ffd-49de-9a69-497be7789877','cd37b2a6-ac7d-4c93-a148-ca67f7f67cff','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false); + ('d9acb388-09a5-464b-bb50-bf418b25e96b'::uuid,'67ba1eaf-6ffd-49de-9a69-497be7789877','a1d31d35-c87d-4a7d-b0b8-8b2646b96e43','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), -- ContractCode + ('0b31db7a-fbab-4e49-8526-00458ac3900c'::uuid,'67ba1eaf-6ffd-49de-9a69-497be7789877','597bb77e-0ce7-4ba2-9624-24300962625f','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), -- PerUnitCents + ('9b0a74e3-afc4-4f42-8eb3-828f80fbfaf0'::uuid,'67ba1eaf-6ffd-49de-9a69-497be7789877','95ee2e21-b232-4d74-9ec5-218564a8a8b9','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false); -- IsPeak -- inserting params fo IHUPK INSERT INTO service_params (id,service_id,service_item_param_key_id,created_at,updated_at,is_optional) VALUES - ('d9acb388-09a5-464b-bb50-bf418b25e96c'::uuid,'56e91c2d-015d-4243-9657-3ed34867abaa','a1d31d35-c87d-4a7d-b0b8-8b2646b96e43','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), - ('c045524a-90ec-4116-80a1-e2edb5cdf38f'::uuid,'56e91c2d-015d-4243-9657-3ed34867abaa','cd37b2a6-ac7d-4c93-a148-ca67f7f67cff','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false); + ('cb110853-6b1d-452b-9607-345721a70313'::uuid,'56e91c2d-015d-4243-9657-3ed34867abaa','a1d31d35-c87d-4a7d-b0b8-8b2646b96e43','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), -- ContractCode + ('cc95d5df-1167-4fe9-8682-07f8fbe7c286'::uuid,'56e91c2d-015d-4243-9657-3ed34867abaa','597bb77e-0ce7-4ba2-9624-24300962625f','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), -- PerUnitCents + ('759bd482-b2f6-461b-a898-792415efa5f1'::uuid,'56e91c2d-015d-4243-9657-3ed34867abaa','95ee2e21-b232-4d74-9ec5-218564a8a8b9','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false); -- IsPeak diff --git a/pkg/models/re_rate_area.go b/pkg/models/re_rate_area.go index 167b70577ce..a2b2a274761 100644 --- a/pkg/models/re_rate_area.go +++ b/pkg/models/re_rate_area.go @@ -1,6 +1,7 @@ package models import ( + "fmt" "time" "github.com/gobuffalo/pop/v6" @@ -53,3 +54,19 @@ func FetchReRateAreaItem(tx *pop.Connection, contractID uuid.UUID, code string) return &area, err } + +// 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) (uuid.UUID, error) { + if addressID != uuid.Nil && serviceID != uuid.Nil { + var rateAreaID uuid.UUID + err := db.RawQuery("SELECT get_rate_area_id($1, $2)", addressID, serviceID). + First(&rateAreaID) + + if err != nil { + return uuid.Nil, fmt.Errorf("error fetching rate area id for shipment ID: %s and service ID %s: %s", addressID, serviceID, err) + } + return rateAreaID, nil + } + // Return error if required parameters are not provided + return uuid.Nil, fmt.Errorf("error fetching rate area ID - required parameters not provided") +} diff --git a/pkg/models/service_item_param_key.go b/pkg/models/service_item_param_key.go index 3bfd789dcc4..868c6785b55 100644 --- a/pkg/models/service_item_param_key.go +++ b/pkg/models/service_item_param_key.go @@ -61,6 +61,10 @@ const ( ServiceItemParamNameNTSPackingFactor ServiceItemParamName = "NTSPackingFactor" // ServiceItemParamNameNumberDaysSIT is the param key name NumberDaysSIT ServiceItemParamNameNumberDaysSIT ServiceItemParamName = "NumberDaysSIT" + // ServiceItemParamNamePerUnitCents is the param key name PerUnitCents + ServiceItemParamNamePerUnitCents ServiceItemParamName = "PerUnitCents" + // ServiceItemParamNamePortName is the param key name PortName + ServiceItemParamNamePortName ServiceItemParamName = "PortName" // ServiceItemParamNamePriceAreaDest is the param key name PriceAreaDest ServiceItemParamNamePriceAreaDest ServiceItemParamName = "PriceAreaDest" // ServiceItemParamNamePriceAreaIntlDest is the param key name PriceAreaIntlDest @@ -275,6 +279,8 @@ var ValidServiceItemParamNames = []ServiceItemParamName{ ServiceItemParamNameStandaloneCrateCap, ServiceItemParamNameUncappedRequestTotal, ServiceItemParamNameLockedPriceCents, + ServiceItemParamNamePerUnitCents, + ServiceItemParamNamePortName, } // ValidServiceItemParamNameStrings lists all valid service item param key names @@ -349,6 +355,8 @@ var ValidServiceItemParamNameStrings = []string{ string(ServiceItemParamNameStandaloneCrateCap), string(ServiceItemParamNameUncappedRequestTotal), string(ServiceItemParamNameLockedPriceCents), + string(ServiceItemParamNamePerUnitCents), + string(ServiceItemParamNamePortName), } // ValidServiceItemParamTypes lists all valid service item param types 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 new file mode 100644 index 00000000000..07670b009fa --- /dev/null +++ b/pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup.go @@ -0,0 +1,63 @@ +package serviceparamvaluelookups + +import ( + "database/sql" + "fmt" + + "github.com/transcom/mymove/pkg/appcontext" + "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services/ghcrateengine" +) + +// PerUnitCents does lookup on the per unit cents value associated with a service item +type PerUnitCentsLookup struct { + ServiceItem models.MTOServiceItem + MTOShipment models.MTOShipment +} + +func (p PerUnitCentsLookup) lookup(appCtx appcontext.AppContext, s *ServiceItemParamKeyData) (string, error) { + var isPeakPeriod bool + serviceID := p.ServiceItem.ReServiceID + contractID := s.ContractID + if p.ServiceItem.ReService.Code == models.ReServiceCodeIHPK { + // IHPK we need the rate area id for the pickup address + rateAreaID, err := models.FetchRateAreaID(appCtx.DB(), *p.MTOShipment.PickupAddressID, p.ServiceItem.ReServiceID) + if err != nil { + return "", fmt.Errorf("error fetching rate area id for shipment ID: %s and service ID %s: %s", p.MTOShipment.ID, p.ServiceItem.ReServiceID, err) + } + isPeakPeriod = ghcrateengine.IsPeakPeriod(*p.MTOShipment.RequestedPickupDate) + var reIntlOtherPrice models.ReIntlOtherPrice + err = appCtx.DB().Q(). + Where("contract_id = ?", contractID). + Where("service_id = ?", serviceID). + Where("is_peak_period = ?", isPeakPeriod). + Where("rate_area_id = ?", rateAreaID). + First(&reIntlOtherPrice) + if err != nil { + return "", fmt.Errorf("error fetching IHPK per unit cents for contractID: %s, serviceID %s, isPeakPeriod: %t, and rateAreaID: %s: %s", contractID, serviceID, isPeakPeriod, rateAreaID, err) + } + return reIntlOtherPrice.PerUnitCents.ToMillicents().ToCents().String(), nil + } + + if p.ServiceItem.ReService.Code == models.ReServiceCodeIHUPK { + // IHUPK we need the rate area id for the destination address + rateAreaID, err := models.FetchRateAreaID(appCtx.DB(), *p.MTOShipment.PickupAddressID, p.ServiceItem.ReServiceID) + if err != nil && err != sql.ErrNoRows { + return "", fmt.Errorf("error fetching rate area id for shipment ID: %s and service ID %s: %s", p.MTOShipment.ID, p.ServiceItem.ReServiceID, err) + } + isPeakPeriod = ghcrateengine.IsPeakPeriod(*p.MTOShipment.RequestedPickupDate) + var reIntlOtherPrice models.ReIntlOtherPrice + err = appCtx.DB().Q(). + Where("contract_id = ?", contractID). + Where("service_id = ?", serviceID). + Where("is_peak_period = ?", isPeakPeriod). + Where("rate_area_id = ?", rateAreaID). + First(&reIntlOtherPrice) + if err != nil { + return "", fmt.Errorf("error fetching IHUPK per unit cents for contractID: %s, serviceID %s, isPeakPeriod: %t, and rateAreaID: %s: %s", contractID, serviceID, isPeakPeriod, rateAreaID, err) + } + return reIntlOtherPrice.PerUnitCents.ToMillicents().ToCents().String(), nil + } else { + return "", fmt.Errorf("unsupported service code to retrieve service item param PerUnitCents") + } +} diff --git a/pkg/payment_request/service_param_value_lookups/port_name_lookup.go b/pkg/payment_request/service_param_value_lookups/port_name_lookup.go new file mode 100644 index 00000000000..5013d3ae2c8 --- /dev/null +++ b/pkg/payment_request/service_param_value_lookups/port_name_lookup.go @@ -0,0 +1,34 @@ +package serviceparamvaluelookups + +import ( + "fmt" + + "github.com/gofrs/uuid" + + "github.com/transcom/mymove/pkg/appcontext" + "github.com/transcom/mymove/pkg/models" +) + +// PortNameLookup does lookup on the shipment and finds the port name +type PortNameLookup struct { + ServiceItem models.MTOServiceItem +} + +func (p PortNameLookup) lookup(appCtx appcontext.AppContext, _ *ServiceItemParamKeyData) (string, error) { + var portLocationID *uuid.UUID + if p.ServiceItem.PODLocationID != nil { + portLocationID = p.ServiceItem.PODLocationID + } else if p.ServiceItem.POELocationID != nil { + portLocationID = p.ServiceItem.POELocationID + } else { + return "", nil + } + var portLocation models.PortLocation + err := appCtx.DB().Q(). + EagerPreload("Port"). + Where("id = $1", portLocationID).First(&portLocation) + if err != nil { + return "", fmt.Errorf("unable to find port location with id %s", portLocationID) + } + return portLocation.Port.PortName, nil +} diff --git a/pkg/payment_request/service_param_value_lookups/service_param_value_lookups.go b/pkg/payment_request/service_param_value_lookups/service_param_value_lookups.go index 0bb499be70b..c3669e5cb41 100644 --- a/pkg/payment_request/service_param_value_lookups/service_param_value_lookups.go +++ b/pkg/payment_request/service_param_value_lookups/service_param_value_lookups.go @@ -21,6 +21,7 @@ type ServiceItemParamKeyData struct { MTOServiceItem models.MTOServiceItem PaymentRequestID uuid.UUID MoveTaskOrderID uuid.UUID + ContractID uuid.UUID ContractCode string mtoShipmentID *uuid.UUID paramCache *ServiceParamsCache @@ -85,6 +86,8 @@ var ServiceItemParamsWithLookups = []models.ServiceItemParamName{ models.ServiceItemParamNameStandaloneCrate, models.ServiceItemParamNameStandaloneCrateCap, models.ServiceItemParamNameLockedPriceCents, + models.ServiceItemParamNamePerUnitCents, + models.ServiceItemParamNamePortName, } // ServiceParamLookupInitialize initializes service parameter lookup @@ -120,6 +123,7 @@ func ServiceParamLookupInitialize( to this query. Otherwise the contract_code field could be added to the MTO. */ ContractCode: contract.Code, + ContractID: contract.ID, } // @@ -430,6 +434,15 @@ func InitializeLookups(appCtx appcontext.AppContext, shipment models.MTOShipment ServiceItem: serviceItem, } + lookups[models.ServiceItemParamNamePerUnitCents] = PerUnitCentsLookup{ + ServiceItem: serviceItem, + MTOShipment: shipment, + } + + lookups[models.ServiceItemParamNamePortName] = PortNameLookup{ + ServiceItem: serviceItem, + } + return lookups } diff --git a/pkg/services/ghc_rate_engine.go b/pkg/services/ghc_rate_engine.go index 9bee5c8f59f..440d710f4b2 100644 --- a/pkg/services/ghc_rate_engine.go +++ b/pkg/services/ghc_rate_engine.go @@ -245,7 +245,7 @@ type IntlShippingAndLinehaulPricer interface { // //go:generate mockery --name IntlHHGPackPricer type IntlHHGPackPricer interface { - Price(appCtx appcontext.AppContext, contractCode string, requestedPickupDate time.Time, weight unit.Pound, servicesScheduleOrigin int, isPPM bool) (unit.Cents, PricingDisplayParams, error) + Price(appCtx appcontext.AppContext, contractCode string, requestedPickupDate time.Time, weight unit.Pound, perUnitCents int) (unit.Cents, PricingDisplayParams, error) ParamsPricer } @@ -253,7 +253,7 @@ type IntlHHGPackPricer interface { // //go:generate mockery --name IntlHHGUnpackPricer type IntlHHGUnpackPricer interface { - Price(appCtx appcontext.AppContext, contractCode string, requestedPickupDate time.Time, weight unit.Pound, servicesScheduleDest int, isPPM bool) (unit.Cents, PricingDisplayParams, error) + Price(appCtx appcontext.AppContext, contractCode string, requestedPickupDate time.Time, weight unit.Pound, perUnitCents int) (unit.Cents, PricingDisplayParams, error) ParamsPricer } diff --git a/pkg/services/ghcrateengine/intl_hhg_pack_pricer.go b/pkg/services/ghcrateengine/intl_hhg_pack_pricer.go index 9318f930e10..12090aa3bde 100644 --- a/pkg/services/ghcrateengine/intl_hhg_pack_pricer.go +++ b/pkg/services/ghcrateengine/intl_hhg_pack_pricer.go @@ -12,17 +12,14 @@ import ( type intlHHGPackPricer struct { } -// NewDomesticPackPricer creates a new pricer for the domestic pack service func NewIntlHHGPackPricer() services.IntlHHGPackPricer { return &intlHHGPackPricer{} } -// Price determines the price for a domestic pack service -func (p intlHHGPackPricer) Price(appCtx appcontext.AppContext, contractCode string, referenceDate time.Time, weight unit.Pound, servicesScheduleOrigin int, isPPM bool) (unit.Cents, services.PricingDisplayParams, error) { - return priceIntlPackUnpack(appCtx, models.ReServiceCodeIHPK, contractCode, referenceDate, weight, servicesScheduleOrigin, isPPM) +func (p intlHHGPackPricer) Price(appCtx appcontext.AppContext, contractCode string, referenceDate time.Time, weight unit.Pound, perUnitCents int) (unit.Cents, services.PricingDisplayParams, error) { + return priceIntlPackUnpack(appCtx, models.ReServiceCodeIHPK, contractCode, referenceDate, weight, perUnitCents) } -// PriceUsingParams determines the price for a domestic pack service given PaymentServiceItemParams func (p intlHHGPackPricer) PriceUsingParams(appCtx appcontext.AppContext, params models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error) { contractCode, err := getParamString(params, models.ServiceItemParamNameContractCode) if err != nil { @@ -34,7 +31,7 @@ func (p intlHHGPackPricer) PriceUsingParams(appCtx appcontext.AppContext, params return unit.Cents(0), nil, err } - servicesScheduleOrigin, err := getParamInt(params, models.ServiceItemParamNameServicesScheduleOrigin) + perUnitCents, err := getParamInt(params, models.ServiceItemParamNamePerUnitCents) if err != nil { return unit.Cents(0), nil, err } @@ -44,13 +41,5 @@ func (p intlHHGPackPricer) PriceUsingParams(appCtx appcontext.AppContext, params 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, contractCode, referenceDate, unit.Pound(weightBilled), servicesScheduleOrigin, isPPM) + return p.Price(appCtx, contractCode, referenceDate, unit.Pound(weightBilled), perUnitCents) } diff --git a/pkg/services/ghcrateengine/intl_hhg_unpack_pricer.go b/pkg/services/ghcrateengine/intl_hhg_unpack_pricer.go index 47e428f1e6d..d4cb95dd315 100644 --- a/pkg/services/ghcrateengine/intl_hhg_unpack_pricer.go +++ b/pkg/services/ghcrateengine/intl_hhg_unpack_pricer.go @@ -12,17 +12,14 @@ import ( type intlHHGUnpackPricer struct { } -// NewDomesticUnpackPricer creates a new pricer for the domestic unpack service func NewIntlHHGUnpackPricer() services.IntlHHGUnpackPricer { return &intlHHGUnpackPricer{} } -// Price determines the price for a domestic unpack service -func (p intlHHGUnpackPricer) Price(appCtx appcontext.AppContext, contractCode string, referenceDate time.Time, weight unit.Pound, servicesScheduleDest int, isPPM bool) (unit.Cents, services.PricingDisplayParams, error) { - return priceDomesticPackUnpack(appCtx, models.ReServiceCodeDUPK, contractCode, referenceDate, weight, servicesScheduleDest, isPPM) +func (p intlHHGUnpackPricer) Price(appCtx appcontext.AppContext, contractCode string, referenceDate time.Time, weight unit.Pound, perUnitCents int) (unit.Cents, services.PricingDisplayParams, error) { + return priceIntlPackUnpack(appCtx, models.ReServiceCodeIHUPK, contractCode, referenceDate, weight, perUnitCents) } -// PriceUsingParams determines the price for a domestic unpack service given PaymentServiceItemParams func (p intlHHGUnpackPricer) PriceUsingParams(appCtx appcontext.AppContext, params models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error) { contractCode, err := getParamString(params, models.ServiceItemParamNameContractCode) if err != nil { @@ -34,7 +31,7 @@ func (p intlHHGUnpackPricer) PriceUsingParams(appCtx appcontext.AppContext, para return unit.Cents(0), nil, err } - servicesScheduleDest, err := getParamInt(params, models.ServiceItemParamNameServicesScheduleDest) + perUnitCents, err := getParamInt(params, models.ServiceItemParamNamePerUnitCents) if err != nil { return unit.Cents(0), nil, err } @@ -44,13 +41,5 @@ func (p intlHHGUnpackPricer) PriceUsingParams(appCtx appcontext.AppContext, para 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, contractCode, referenceDate, unit.Pound(weightBilled), servicesScheduleDest, isPPM) + return p.Price(appCtx, contractCode, referenceDate, unit.Pound(weightBilled), perUnitCents) } diff --git a/pkg/services/ghcrateengine/pricer_helpers_intl.go b/pkg/services/ghcrateengine/pricer_helpers_intl.go index 9f88b2d4ef1..73428847e51 100644 --- a/pkg/services/ghcrateengine/pricer_helpers_intl.go +++ b/pkg/services/ghcrateengine/pricer_helpers_intl.go @@ -13,15 +13,8 @@ import ( "github.com/transcom/mymove/pkg/unit" ) -func priceIntlPackUnpack(appCtx appcontext.AppContext, packUnpackCode models.ReServiceCode, contractCode string, referenceDate time.Time, weight unit.Pound, servicesSchedule int, isPPM bool) (unit.Cents, services.PricingDisplayParams, error) { - // Validate parameters - var intlOtherPriceCode models.ReServiceCode - switch packUnpackCode { - case models.ReServiceCodeIHPK: - intlOtherPriceCode = models.ReServiceCodeIHPK - case models.ReServiceCodeIHUPK: - intlOtherPriceCode = models.ReServiceCodeIHUPK - default: +func priceIntlPackUnpack(appCtx appcontext.AppContext, packUnpackCode models.ReServiceCode, contractCode string, referenceDate time.Time, weight unit.Pound, perUnitCents int) (unit.Cents, services.PricingDisplayParams, error) { + if packUnpackCode != models.ReServiceCodeIHPK && packUnpackCode != models.ReServiceCodeIHUPK { return 0, nil, fmt.Errorf("unsupported pack/unpack code of %s", packUnpackCode) } if len(contractCode) == 0 { @@ -30,32 +23,21 @@ func priceIntlPackUnpack(appCtx appcontext.AppContext, packUnpackCode models.ReS if referenceDate.IsZero() { return 0, nil, errors.New("ReferenceDate is required") } - if !isPPM && weight < minDomesticWeight { - return 0, nil, fmt.Errorf("Weight must be a minimum of %d", minDomesticWeight) - } - if servicesSchedule == 0 { - return 0, nil, errors.New("Services schedule is required") - } isPeakPeriod := IsPeakPeriod(referenceDate) - intlOtherPrice, err := fetchIntlOtherPrice(appCtx, contractCode, intlOtherPriceCode, servicesSchedule, isPeakPeriod) + contract, err := fetchContractsByContractCode(appCtx, contractCode) if err != nil { - return 0, nil, fmt.Errorf("could not lookup domestic other price: %w", err) + return 0, nil, fmt.Errorf("could not find contract with code: %s: %w", contractCode, err) } - finalWeight := weight - if isPPM && weight < minDomesticWeight { - finalWeight = minDomesticWeight - } - - basePrice := intlOtherPrice.PerUnitCents.Float64() - escalatedPrice, contractYear, err := escalatePriceForContractYear(appCtx, intlOtherPrice.ContractID, referenceDate, false, basePrice) + basePrice := float64(perUnitCents) + escalatedPrice, contractYear, err := escalatePriceForContractYear(appCtx, contract.ID, referenceDate, false, basePrice) if err != nil { return 0, nil, fmt.Errorf("could not calculate escalated price: %w", err) } - escalatedPrice = escalatedPrice * finalWeight.ToCWTFloat64() + escalatedPrice = escalatedPrice * weight.ToCWTFloat64() displayParams := services.PricingDisplayParams{ { @@ -64,7 +46,7 @@ func priceIntlPackUnpack(appCtx appcontext.AppContext, packUnpackCode models.ReS }, { Key: models.ServiceItemParamNamePriceRateOrFactor, - Value: FormatCents(intlOtherPrice.PerUnitCents), + Value: FormatCents(unit.Cents(perUnitCents)), }, { Key: models.ServiceItemParamNameIsPeak, @@ -77,10 +59,5 @@ func priceIntlPackUnpack(appCtx appcontext.AppContext, packUnpackCode models.ReS } totalCost := unit.Cents(math.Round(escalatedPrice)) - if isPPM && weight < minDomesticWeight { - weightFactor := float64(weight) / float64(minDomesticWeight) - cost := float64(weightFactor) * float64(totalCost) - return unit.Cents(cost), displayParams, nil - } return totalCost, displayParams, nil } diff --git a/pkg/services/ghcrateengine/pricer_query_helpers.go b/pkg/services/ghcrateengine/pricer_query_helpers.go index 05c7bf668df..51acb06f9bd 100644 --- a/pkg/services/ghcrateengine/pricer_query_helpers.go +++ b/pkg/services/ghcrateengine/pricer_query_helpers.go @@ -45,24 +45,6 @@ func fetchDomOtherPrice(appCtx appcontext.AppContext, contractCode string, servi return domOtherPrice, nil } -func fetchIntlOtherPrice(appCtx appcontext.AppContext, contractCode string, serviceCode models.ReServiceCode, schedule int, isPeakPeriod bool) (models.ReIntlOtherPrice, error) { - var intlOtherPrice models.ReIntlOtherPrice - err := appCtx.DB().Q(). - Join("re_services", "service_id = re_services.id"). - Join("re_contracts", "re_contracts.id = re_intl_other_prices.contract_id"). - Where("re_contracts.code = $1", contractCode). - Where("re_services.code = $2", serviceCode). - Where("schedule = $3", schedule). - Where("is_peak_period = $4", isPeakPeriod). - First(&intlOtherPrice) - - if err != nil { - return models.ReIntlOtherPrice{}, err - } - - return intlOtherPrice, nil -} - func fetchDomServiceAreaPrice(appCtx appcontext.AppContext, contractCode string, serviceCode models.ReServiceCode, serviceArea string, isPeakPeriod bool) (models.ReDomesticServiceAreaPrice, error) { var domServiceAreaPrice models.ReDomesticServiceAreaPrice err := appCtx.DB().Q(). @@ -121,6 +103,16 @@ func fetchContractsByContractId(appCtx appcontext.AppContext, contractID uuid.UU return contracts, nil } +func fetchContractsByContractCode(appCtx appcontext.AppContext, contractCode string) (models.ReContract, error) { + var contract models.ReContract + err := appCtx.DB().Where("code = $1", contractCode).First(&contract) + if err != nil { + return models.ReContract{}, err + } + + return contract, nil +} + func fetchShipmentTypePrice(appCtx appcontext.AppContext, contractCode string, serviceCode models.ReServiceCode, market models.Market) (models.ReShipmentTypePrice, error) { var shipmentTypePrice models.ReShipmentTypePrice err := appCtx.DB().Q(). diff --git a/src/components/PrimeUI/CreatePaymentRequestForm/CreatePaymentRequestForm.jsx b/src/components/PrimeUI/CreatePaymentRequestForm/CreatePaymentRequestForm.jsx index 3a3edafb556..5aa3fdc0187 100644 --- a/src/components/PrimeUI/CreatePaymentRequestForm/CreatePaymentRequestForm.jsx +++ b/src/components/PrimeUI/CreatePaymentRequestForm/CreatePaymentRequestForm.jsx @@ -143,7 +143,9 @@ const CreatePaymentRequestForm = ({ mtoServiceItem.reServiceCode === 'DPK' || mtoServiceItem.reServiceCode === 'DDSFSC' || mtoServiceItem.reServiceCode === 'DOSFSC' || - mtoServiceItem.reServiceCode === 'DDSHUT') && ( + mtoServiceItem.reServiceCode === 'DDSHUT' || + mtoServiceItem.reServiceCode === 'IHPK' || + mtoServiceItem.reServiceCode === 'IHUPK') && ( Date: Thu, 2 Jan 2025 17:53:50 +0000 Subject: [PATCH 03/14] POEFSC and PODFSC should be good, onto linehaul, still need tests --- ...aram_values_to_service_params_table.up.sql | 2 +- pkg/models/re_rate_area.go | 1 - pkg/services/ghc_rate_engine.go | 2 +- .../intl_port_fuel_surcharge_pricer.go | 21 ++++++++----------- pkg/services/ghcrateengine/pricer_helpers.go | 4 ++-- .../ghcrateengine/service_item_pricer.go | 1 - 6 files changed, 13 insertions(+), 18 deletions(-) diff --git a/migrations/app/schema/20241226173330_add_intl_param_values_to_service_params_table.up.sql b/migrations/app/schema/20241226173330_add_intl_param_values_to_service_params_table.up.sql index 14dc1422582..eec414779d0 100644 --- a/migrations/app/schema/20241226173330_add_intl_param_values_to_service_params_table.up.sql +++ b/migrations/app/schema/20241226173330_add_intl_param_values_to_service_params_table.up.sql @@ -1,6 +1,6 @@ -- need to add in param keys for international shipments, this will be used to show breakdowns to the TIO INSERT INTO service_item_param_keys (id, key,description,type,origin,created_at,updated_at) VALUES - ('d9ad3878-4b94-4722-bbaf-d4b8080f339d','PortName','Name of the port for an international shipment','STRING','PRICER','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957'), + ('d9ad3878-4b94-4722-bbaf-d4b8080f339d','PortName','Name of the port for an international shipment','STRING','SYSTEM','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957'), ('597bb77e-0ce7-4ba2-9624-24300962625f','PerUnitCents','Per unit cents for a service item','INTEGER','SYSTEM','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957'); -- inserting params for PODFSC diff --git a/pkg/models/re_rate_area.go b/pkg/models/re_rate_area.go index a2b2a274761..960f4258d8c 100644 --- a/pkg/models/re_rate_area.go +++ b/pkg/models/re_rate_area.go @@ -67,6 +67,5 @@ func FetchRateAreaID(db *pop.Connection, addressID uuid.UUID, serviceID uuid.UUI } return rateAreaID, nil } - // Return error if required parameters are not provided return uuid.Nil, fmt.Errorf("error fetching rate area ID - required parameters not provided") } diff --git a/pkg/services/ghc_rate_engine.go b/pkg/services/ghc_rate_engine.go index 440d710f4b2..4a74692a5bb 100644 --- a/pkg/services/ghc_rate_engine.go +++ b/pkg/services/ghc_rate_engine.go @@ -261,6 +261,6 @@ type IntlHHGUnpackPricer interface { // //go:generate mockery --name IntlPortFuelSurchargePricer type IntlPortFuelSurchargePricer 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) + Price(appCtx appcontext.AppContext, actualPickupDate time.Time, distance unit.Miles, weight unit.Pound, fscWeightBasedDistanceMultiplier float64, eiaFuelPrice unit.Millicents, portName string) (unit.Cents, PricingDisplayParams, error) ParamsPricer } diff --git a/pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer.go b/pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer.go index 0acac1eaffa..7d2c94e7096 100644 --- a/pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer.go +++ b/pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer.go @@ -16,17 +16,14 @@ import ( "github.com/transcom/mymove/pkg/unit" ) -// FuelSurchargePricer is a service object to price domestic shorthaul type portFuelSurchargePricer struct { } -// NewFuelSurchargePricer is the public constructor for a domesticFuelSurchargePricer using Pop func NewPortFuelSurchargePricer() services.IntlPortFuelSurchargePricer { return &portFuelSurchargePricer{} } -// Price determines the price for fuel surcharge -func (p portFuelSurchargePricer) Price(_ appcontext.AppContext, actualPickupDate time.Time, distance unit.Miles, weight unit.Pound, fscWeightBasedDistanceMultiplier float64, eiaFuelPrice unit.Millicents, isPPM bool) (unit.Cents, services.PricingDisplayParams, error) { +func (p portFuelSurchargePricer) Price(_ appcontext.AppContext, actualPickupDate time.Time, distance unit.Miles, weight unit.Pound, fscWeightBasedDistanceMultiplier float64, eiaFuelPrice unit.Millicents, portName string) (unit.Cents, services.PricingDisplayParams, error) { // Validate parameters if actualPickupDate.IsZero() { return 0, nil, errors.New("ActualPickupDate is required") @@ -34,7 +31,7 @@ func (p portFuelSurchargePricer) Price(_ appcontext.AppContext, actualPickupDate if distance <= 0 { return 0, nil, errors.New("Distance must be greater than 0") } - if !isPPM && weight < minIntlWeightHHG { + if weight < minIntlWeightHHG { return 0, nil, fmt.Errorf("weight must be a minimum of %d", minIntlWeightHHG) } if fscWeightBasedDistanceMultiplier == 0 { @@ -43,6 +40,9 @@ func (p portFuelSurchargePricer) Price(_ appcontext.AppContext, actualPickupDate if eiaFuelPrice == 0 { return 0, nil, errors.New("EIAFuelPrice is required") } + if portName == "" { + return 0, nil, errors.New("PortName is required") + } fscPriceDifferenceInCents := (eiaFuelPrice - baseGHCDieselFuelPrice).Float64() / 1000.0 fscMultiplier := fscWeightBasedDistanceMultiplier * distance.Float64() @@ -99,13 +99,10 @@ func (p portFuelSurchargePricer) PriceUsingParams(appCtx appcontext.AppContext, 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 + portName, err := getParamString(params, models.ServiceItemParamNamePortName) + if err != nil { + return unit.Cents(0), nil, err } - return p.Price(appCtx, actualPickupDate, unit.Miles(distance), unit.Pound(weightBilled), fscWeightBasedDistanceMultiplier, unit.Millicents(eiaFuelPrice), isPPM) + return p.Price(appCtx, actualPickupDate, unit.Miles(distance), unit.Pound(weightBilled), fscWeightBasedDistanceMultiplier, unit.Millicents(eiaFuelPrice), portName) } diff --git a/pkg/services/ghcrateengine/pricer_helpers.go b/pkg/services/ghcrateengine/pricer_helpers.go index faa802afb0b..cb804b0da49 100644 --- a/pkg/services/ghcrateengine/pricer_helpers.go +++ b/pkg/services/ghcrateengine/pricer_helpers.go @@ -448,10 +448,10 @@ func createPricerGeneratedParams(appCtx appcontext.AppContext, paymentServiceIte Where("key = ?", param.Key). First(&serviceItemParamKey) if err != nil { - return paymentServiceItemParams, fmt.Errorf("Unable to find service item param key for %v", param.Key) + return paymentServiceItemParams, fmt.Errorf("unable to find service item param key for %v", param.Key) } if serviceItemParamKey.Origin != models.ServiceItemParamOriginPricer { - return paymentServiceItemParams, fmt.Errorf("Service item param key is not a pricer param. Param key: %v", serviceItemParamKey.Key) + return paymentServiceItemParams, fmt.Errorf("service item param key is not a pricer param. Param key: %v", serviceItemParamKey.Key) } // Create the PaymentServiceItemParam from the PricingDisplayParam and store it in the DB diff --git a/pkg/services/ghcrateengine/service_item_pricer.go b/pkg/services/ghcrateengine/service_item_pricer.go index ecaf9b8139b..a673f832b63 100644 --- a/pkg/services/ghcrateengine/service_item_pricer.go +++ b/pkg/services/ghcrateengine/service_item_pricer.go @@ -36,7 +36,6 @@ func (p serviceItemPricer) PriceServiceItem(appCtx appcontext.AppContext, item m // createPricerGeneratedParams will throw an error if pricingParams is an empty slice // currently our pricers are returning empty slices for pricingParams // once all pricers have been updated to return pricingParams - // TODO: this conditional logic should be removed var displayParams models.PaymentServiceItemParams if len(pricingParams) > 0 { displayParams, err = createPricerGeneratedParams(appCtx, item.ID, pricingParams) From 1a0073e2fd9d3c9fe45a035fb45b2cc4c4ff3d5e Mon Sep 17 00:00:00 2001 From: Daniel Jordan Date: Thu, 2 Jan 2025 18:56:51 +0000 Subject: [PATCH 04/14] linehaul done, now it is time for tests and refinement please save me baby jesus in a tuxedo --- ...aram_values_to_service_params_table.up.sql | 3 +- .../per_unit_cents_lookup.go | 24 ++++- pkg/services/ghc_rate_engine.go | 2 +- .../intl_shipping_and_linehaul_pricer.go | 97 +++++++------------ .../ghcrateengine/pricer_helpers_intl.go | 2 +- 5 files changed, 61 insertions(+), 67 deletions(-) diff --git a/migrations/app/schema/20241226173330_add_intl_param_values_to_service_params_table.up.sql b/migrations/app/schema/20241226173330_add_intl_param_values_to_service_params_table.up.sql index eec414779d0..63c1e404e50 100644 --- a/migrations/app/schema/20241226173330_add_intl_param_values_to_service_params_table.up.sql +++ b/migrations/app/schema/20241226173330_add_intl_param_values_to_service_params_table.up.sql @@ -61,7 +61,8 @@ INSERT INTO service_params (id,service_id,service_item_param_key_id,created_at,u ('c08f7ab1-6c3c-4627-b22f-1e987ba6f4f2'::uuid,'9f3d551a-0725-430e-897e-80ee9add3ae9','1e6257e9-757d-4d59-8846-727dd8a055e7','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), ('4d9ed9b0-957d-4e6a-a3d4-5e2e2784ef62'::uuid,'9f3d551a-0725-430e-897e-80ee9add3ae9','14a93209-370d-42f3-8ca2-479c953be839','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), ('6acb30b9-65a0-4902-85ed-1acb6f4ac930'::uuid,'9f3d551a-0725-430e-897e-80ee9add3ae9','5335e243-ab5b-4906-b84f-bd8c35ba64b3','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), - ('fd83c2ba-0c59-4598-81d6-b56cc8d9979d'::uuid,'9f3d551a-0725-430e-897e-80ee9add3ae9','2cbc2251-eb7d-4c69-a120-9a83785c994b','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false); + ('fd83c2ba-0c59-4598-81d6-b56cc8d9979d'::uuid,'9f3d551a-0725-430e-897e-80ee9add3ae9','2cbc2251-eb7d-4c69-a120-9a83785c994b','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('b370c895-e356-4d2c-a200-c2c67ac51011'::uuid,'9f3d551a-0725-430e-897e-80ee9add3ae9','597bb77e-0ce7-4ba2-9624-24300962625f','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false); -- inserting params fo IOSFSC INSERT INTO service_params (id,service_id,service_item_param_key_id,created_at,updated_at,is_optional) VALUES 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 07670b009fa..a25913f8e2c 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 @@ -38,7 +38,6 @@ func (p PerUnitCentsLookup) lookup(appCtx appcontext.AppContext, s *ServiceItemP } return reIntlOtherPrice.PerUnitCents.ToMillicents().ToCents().String(), nil } - if p.ServiceItem.ReService.Code == models.ReServiceCodeIHUPK { // IHUPK we need the rate area id for the destination address rateAreaID, err := models.FetchRateAreaID(appCtx.DB(), *p.MTOShipment.PickupAddressID, p.ServiceItem.ReServiceID) @@ -57,6 +56,29 @@ func (p PerUnitCentsLookup) lookup(appCtx appcontext.AppContext, s *ServiceItemP return "", fmt.Errorf("error fetching IHUPK per unit cents for contractID: %s, serviceID %s, isPeakPeriod: %t, and rateAreaID: %s: %s", contractID, serviceID, isPeakPeriod, rateAreaID, err) } return reIntlOtherPrice.PerUnitCents.ToMillicents().ToCents().String(), nil + } else if p.ServiceItem.ReService.Code == models.ReServiceCodeISLH { + // IHUPK we need the rate area id for the destination address + originRateAreaID, err := models.FetchRateAreaID(appCtx.DB(), *p.MTOShipment.PickupAddressID, p.ServiceItem.ReServiceID) + if err != nil && err != sql.ErrNoRows { + return "", fmt.Errorf("error fetching rate area id for origina address for shipment ID: %s and service ID %s: %s", p.MTOShipment.ID, p.ServiceItem.ReServiceID, err) + } + destRateAreaID, err := models.FetchRateAreaID(appCtx.DB(), *p.MTOShipment.DestinationAddressID, p.ServiceItem.ReServiceID) + if err != nil && err != sql.ErrNoRows { + return "", fmt.Errorf("error fetching rate area id for destination address for shipment ID: %s and service ID %s: %s", p.MTOShipment.ID, p.ServiceItem.ReServiceID, err) + } + isPeakPeriod = ghcrateengine.IsPeakPeriod(*p.MTOShipment.RequestedPickupDate) + var reIntlPrice models.ReIntlPrice + err = appCtx.DB().Q(). + Where("contract_id = ?", contractID). + Where("service_id = ?", serviceID). + Where("is_peak_period = ?", isPeakPeriod). + Where("origin_rate_area_id = ?", originRateAreaID). + Where("destination_rate_area_id = ?", destRateAreaID). + First(&reIntlPrice) + if err != nil { + return "", fmt.Errorf("error fetching ISLH per unit cents for contractID: %s, serviceID %s, isPeakPeriod: %t, originRateAreaID: %s, and destRateAreaid: %s: %s", contractID, serviceID, isPeakPeriod, originRateAreaID, destRateAreaID, err) + } + return reIntlPrice.PerUnitCents.ToMillicents().ToCents().String(), nil } else { return "", fmt.Errorf("unsupported service code to retrieve service item param PerUnitCents") } diff --git a/pkg/services/ghc_rate_engine.go b/pkg/services/ghc_rate_engine.go index 4a74692a5bb..b024ac1d4e3 100644 --- a/pkg/services/ghc_rate_engine.go +++ b/pkg/services/ghc_rate_engine.go @@ -237,7 +237,7 @@ type DomesticOriginSITFuelSurchargePricer interface { // //go:generate mockery --name IntlShippingAndLinehaulPricer type IntlShippingAndLinehaulPricer interface { - Price(appCtx appcontext.AppContext, contractCode string, requestedPickupDate time.Time, distance unit.Miles, weight unit.Pound, serviceArea string, isPPM bool) (unit.Cents, PricingDisplayParams, error) + Price(appCtx appcontext.AppContext, contractCode string, requestedPickupDate time.Time, distance unit.Miles, weight unit.Pound, perUnitCents int) (unit.Cents, PricingDisplayParams, error) ParamsPricer } diff --git a/pkg/services/ghcrateengine/intl_shipping_and_linehaul_pricer.go b/pkg/services/ghcrateengine/intl_shipping_and_linehaul_pricer.go index 2fec86a9b57..ca0624d75a2 100644 --- a/pkg/services/ghcrateengine/intl_shipping_and_linehaul_pricer.go +++ b/pkg/services/ghcrateengine/intl_shipping_and_linehaul_pricer.go @@ -13,46 +13,40 @@ import ( "github.com/transcom/mymove/pkg/unit" ) +const islhPricerMinimumWeight = unit.Pound(500) + type intlShippingAndLinehaulPricer struct { } -// NewDomesticLinehaulPricer creates a new pricer for domestic linehaul services -func NewIntlShippingAndLinehaulPricer() services.DomesticLinehaulPricer { +func NewIntlShippingAndLinehaulPricer() services.IntlShippingAndLinehaulPricer { return &intlShippingAndLinehaulPricer{} } -// Price determines the price for a domestic linehaul -func (p intlShippingAndLinehaulPricer) Price(appCtx appcontext.AppContext, contractCode string, referenceDate time.Time, distance unit.Miles, weight unit.Pound, serviceArea string, isPPM bool) (unit.Cents, services.PricingDisplayParams, error) { - // Validate parameters +func (p intlShippingAndLinehaulPricer) Price(appCtx appcontext.AppContext, contractCode string, referenceDate time.Time, distance unit.Miles, weight unit.Pound, perUnitCents int) (unit.Cents, services.PricingDisplayParams, error) { if len(contractCode) == 0 { return 0, nil, errors.New("ContractCode is required") } if referenceDate.IsZero() { - return 0, nil, errors.New("ReferenceDate is required") + return 0, nil, errors.New("referenceDate is required") } - if !isPPM && weight < dlhPricerMinimumWeight { - return 0, nil, fmt.Errorf("Weight must be at least %d", dlhPricerMinimumWeight) + if weight < islhPricerMinimumWeight { + return 0, nil, fmt.Errorf("weight must be at least %d", islhPricerMinimumWeight) } - if len(serviceArea) == 0 { - return 0, nil, errors.New("ServiceArea is required") + if perUnitCents == 0 { + return 0, nil, errors.New("PerUnitCents is required") } isPeakPeriod := IsPeakPeriod(referenceDate) - finalWeight := weight - - if isPPM && weight < dlhPricerMinimumWeight { - finalWeight = dlhPricerMinimumWeight - } - domesticLinehaulPrice, err := fetchDomesticLinehaulPrice(appCtx, contractCode, isPeakPeriod, distance, finalWeight, serviceArea) + contract, err := fetchContractsByContractCode(appCtx, contractCode) if err != nil { - return unit.Cents(0), nil, fmt.Errorf("could not fetch domestic linehaul rate: %w", err) + return 0, nil, fmt.Errorf("could not find contract with code: %s: %w", contractCode, err) } - basePrice := domesticLinehaulPrice.PriceMillicents.Float64() / 1000 + basePrice := float64(perUnitCents) escalatedPrice, contractYear, err := escalatePriceForContractYear( appCtx, - domesticLinehaulPrice.ContractID, + contract.ID, referenceDate, true, basePrice) @@ -60,26 +54,30 @@ func (p intlShippingAndLinehaulPricer) Price(appCtx appcontext.AppContext, contr return 0, nil, fmt.Errorf("could not calculate escalated price: %w", err) } - totalPrice := finalWeight.ToCWTFloat64() * distance.Float64() * escalatedPrice - totalPriceCents := unit.Cents(math.Round(totalPrice)) + escalatedPrice = escalatedPrice * weight.ToCWTFloat64() + totalPriceCents := unit.Cents(math.Round(escalatedPrice)) params := services.PricingDisplayParams{ - {Key: models.ServiceItemParamNameContractYearName, Value: contractYear.Name}, - {Key: models.ServiceItemParamNameEscalationCompounded, Value: FormatEscalation(contractYear.EscalationCompounded)}, - {Key: models.ServiceItemParamNameIsPeak, Value: FormatBool(isPeakPeriod)}, - {Key: models.ServiceItemParamNamePriceRateOrFactor, Value: FormatFloat(domesticLinehaulPrice.PriceMillicents.ToDollarFloatNoRound(), 3)}, - } - - if isPPM && weight < dlhPricerMinimumWeight { - weightFactor := float64(weight) / float64(dlhPricerMinimumWeight) - cost := float64(weightFactor) * float64(totalPriceCents) - return unit.Cents(cost), params, nil - } + { + Key: models.ServiceItemParamNameContractYearName, + Value: contractYear.Name, + }, + { + Key: models.ServiceItemParamNameEscalationCompounded, + Value: FormatEscalation(contractYear.EscalationCompounded), + }, + { + Key: models.ServiceItemParamNameIsPeak, + Value: FormatBool(isPeakPeriod), + }, + { + Key: models.ServiceItemParamNamePriceRateOrFactor, + Value: FormatCents(unit.Cents(perUnitCents)), + }} return totalPriceCents, params, nil } -// PriceUsingParams determines the price for a domestic linehaul given PaymentServiceItemParams func (p intlShippingAndLinehaulPricer) PriceUsingParams(appCtx appcontext.AppContext, params models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error) { contractCode, err := getParamString(params, models.ServiceItemParamNameContractCode) if err != nil { @@ -96,42 +94,15 @@ func (p intlShippingAndLinehaulPricer) PriceUsingParams(appCtx appcontext.AppCon return unit.Cents(0), nil, err } - serviceAreaOrigin, err := getParamString(params, models.ServiceItemParamNameServiceAreaOrigin) + weightBilled, err := getParamInt(params, models.ServiceItemParamNameWeightBilled) if err != nil { return unit.Cents(0), nil, err } - weightBilled, err := getParamInt(params, models.ServiceItemParamNameWeightBilled) + perUnitCents, err := getParamInt(params, models.ServiceItemParamNamePerUnitCents) 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 or distance - // this flag is passed into the Price function to ensure the weight and distance mins - // are not enforced for PPMs - isPPM = true - } - - return p.Price(appCtx, contractCode, referenceDate, unit.Miles(distance), unit.Pound(weightBilled), serviceAreaOrigin, isPPM) + return p.Price(appCtx, contractCode, referenceDate, unit.Miles(distance), unit.Pound(weightBilled), perUnitCents) } - -// func fetchDomesticLinehaulPrice(appCtx appcontext.AppContext, contractCode string, isPeakPeriod bool, distance unit.Miles, weight unit.Pound, serviceArea string) (models.ReDomesticLinehaulPrice, error) { -// var domesticLinehaulPrice models.ReDomesticLinehaulPrice -// err := appCtx.DB().Q(). -// Join("re_domestic_service_areas sa", "domestic_service_area_id = sa.id"). -// Join("re_contracts c", "re_domestic_linehaul_prices.contract_id = c.id"). -// Where("c.code = $1", contractCode). -// Where("re_domestic_linehaul_prices.is_peak_period = $2", isPeakPeriod). -// Where("$3 between weight_lower and weight_upper", weight). -// Where("$4 between miles_lower and miles_upper", distance). -// Where("sa.service_area = $5", serviceArea). -// First(&domesticLinehaulPrice) - -// if err != nil { -// return models.ReDomesticLinehaulPrice{}, err -// } - -// return domesticLinehaulPrice, nil -// } diff --git a/pkg/services/ghcrateengine/pricer_helpers_intl.go b/pkg/services/ghcrateengine/pricer_helpers_intl.go index 73428847e51..37f01ee9966 100644 --- a/pkg/services/ghcrateengine/pricer_helpers_intl.go +++ b/pkg/services/ghcrateengine/pricer_helpers_intl.go @@ -38,6 +38,7 @@ func priceIntlPackUnpack(appCtx appcontext.AppContext, packUnpackCode models.ReS } escalatedPrice = escalatedPrice * weight.ToCWTFloat64() + totalCost := unit.Cents(math.Round(escalatedPrice)) displayParams := services.PricingDisplayParams{ { @@ -58,6 +59,5 @@ func priceIntlPackUnpack(appCtx appcontext.AppContext, packUnpackCode models.ReS }, } - totalCost := unit.Cents(math.Round(escalatedPrice)) return totalCost, displayParams, nil } From d43303f650c2ca7a3e6751a6dc29294a2fd9636f Mon Sep 17 00:00:00 2001 From: Daniel Jordan Date: Thu, 2 Jan 2025 19:38:55 +0000 Subject: [PATCH 05/14] test for rate area id --- pkg/factory/address_factory.go | 10 +++++++++ pkg/factory/address_factory_test.go | 1 + pkg/models/re_rate_area.go | 10 ++++----- pkg/models/re_rate_area_test.go | 22 +++++++++++++++++++ .../per_unit_cents_lookup.go | 8 +++---- 5 files changed, 41 insertions(+), 10 deletions(-) diff --git a/pkg/factory/address_factory.go b/pkg/factory/address_factory.go index 91a49da4445..d6b7dff6ce5 100644 --- a/pkg/factory/address_factory.go +++ b/pkg/factory/address_factory.go @@ -72,6 +72,16 @@ func BuildAddress(db *pop.Connection, customs []Customization, traits []Trait) m address.County = models.StringPointer("db nil when created") } + if db != nil { + usprc, err := models.FindByZipCode(db, address.PostalCode) + if err != nil { + return models.Address{} + } else { + address.UsPostRegionCityID = &usprc.ID + address.UsPostRegionCity = usprc + } + } + // If db is false, it's a stub. No need to create in database. if db != nil { mustCreate(db, &address) diff --git a/pkg/factory/address_factory_test.go b/pkg/factory/address_factory_test.go index 2e17e564605..7f72a13e9b6 100644 --- a/pkg/factory/address_factory_test.go +++ b/pkg/factory/address_factory_test.go @@ -40,6 +40,7 @@ func (suite *FactorySuite) TestBuildAddress() { suite.Equal(defaultPostalCode, address.PostalCode) suite.Equal(country.ID, *address.CountryId) suite.Equal(defaultCounty, *address.County) + suite.NotNil(*address.UsPostRegionCityID) }) suite.Run("Successful creation of an address with customization", func() { diff --git a/pkg/models/re_rate_area.go b/pkg/models/re_rate_area.go index 960f4258d8c..7b613b42a28 100644 --- a/pkg/models/re_rate_area.go +++ b/pkg/models/re_rate_area.go @@ -56,14 +56,12 @@ 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) (uuid.UUID, error) { - if addressID != uuid.Nil && serviceID != uuid.Nil { +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 { var rateAreaID uuid.UUID - err := db.RawQuery("SELECT get_rate_area_id($1, $2)", addressID, serviceID). - First(&rateAreaID) - + err := db.RawQuery("SELECT get_rate_area_id($1, $2, $3)", addressID, serviceID, contractID).First(&rateAreaID) if err != nil { - return uuid.Nil, fmt.Errorf("error fetching rate area id for shipment ID: %s and service ID %s: %s", addressID, serviceID, err) + return uuid.Nil, fmt.Errorf("error fetching rate area id for shipment ID: %s, service ID %s, and contract ID: %s: %s", addressID, serviceID, contractID, err) } return rateAreaID, nil } diff --git a/pkg/models/re_rate_area_test.go b/pkg/models/re_rate_area_test.go index a0769056783..87f310c2088 100644 --- a/pkg/models/re_rate_area_test.go +++ b/pkg/models/re_rate_area_test.go @@ -3,7 +3,9 @@ package models_test import ( "github.com/gofrs/uuid" + "github.com/transcom/mymove/pkg/factory" "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/testdatagen" ) func (suite *ModelSuite) TestReRateAreaValidation() { @@ -28,3 +30,23 @@ func (suite *ModelSuite) TestReRateAreaValidation() { suite.verifyValidationErrors(&emptyReRateArea, expErrors) }) } + +func (suite *ModelSuite) TestFetchRateAreaID() { + suite.Run("success - fetching a rate area ID", func() { + 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) + 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) + 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 a25913f8e2c..88a46c98e44 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 @@ -21,7 +21,7 @@ func (p PerUnitCentsLookup) lookup(appCtx appcontext.AppContext, s *ServiceItemP contractID := s.ContractID if p.ServiceItem.ReService.Code == models.ReServiceCodeIHPK { // IHPK we need the rate area id for the pickup address - rateAreaID, err := models.FetchRateAreaID(appCtx.DB(), *p.MTOShipment.PickupAddressID, p.ServiceItem.ReServiceID) + rateAreaID, err := models.FetchRateAreaID(appCtx.DB(), *p.MTOShipment.PickupAddressID, p.ServiceItem.ReServiceID, contractID) if err != nil { return "", fmt.Errorf("error fetching rate area id for shipment ID: %s and service ID %s: %s", p.MTOShipment.ID, p.ServiceItem.ReServiceID, err) } @@ -40,7 +40,7 @@ func (p PerUnitCentsLookup) lookup(appCtx appcontext.AppContext, s *ServiceItemP } if p.ServiceItem.ReService.Code == models.ReServiceCodeIHUPK { // IHUPK we need the rate area id for the destination address - rateAreaID, err := models.FetchRateAreaID(appCtx.DB(), *p.MTOShipment.PickupAddressID, p.ServiceItem.ReServiceID) + rateAreaID, err := models.FetchRateAreaID(appCtx.DB(), *p.MTOShipment.PickupAddressID, p.ServiceItem.ReServiceID, contractID) if err != nil && err != sql.ErrNoRows { return "", fmt.Errorf("error fetching rate area id for shipment ID: %s and service ID %s: %s", p.MTOShipment.ID, p.ServiceItem.ReServiceID, err) } @@ -58,11 +58,11 @@ func (p PerUnitCentsLookup) lookup(appCtx appcontext.AppContext, s *ServiceItemP return reIntlOtherPrice.PerUnitCents.ToMillicents().ToCents().String(), nil } else if p.ServiceItem.ReService.Code == models.ReServiceCodeISLH { // IHUPK we need the rate area id for the destination address - originRateAreaID, err := models.FetchRateAreaID(appCtx.DB(), *p.MTOShipment.PickupAddressID, p.ServiceItem.ReServiceID) + originRateAreaID, err := models.FetchRateAreaID(appCtx.DB(), *p.MTOShipment.PickupAddressID, p.ServiceItem.ReServiceID, contractID) if err != nil && err != sql.ErrNoRows { return "", fmt.Errorf("error fetching rate area id for origina address for shipment ID: %s and service ID %s: %s", p.MTOShipment.ID, p.ServiceItem.ReServiceID, err) } - destRateAreaID, err := models.FetchRateAreaID(appCtx.DB(), *p.MTOShipment.DestinationAddressID, p.ServiceItem.ReServiceID) + destRateAreaID, err := models.FetchRateAreaID(appCtx.DB(), *p.MTOShipment.DestinationAddressID, p.ServiceItem.ReServiceID, contractID) if err != nil && err != sql.ErrNoRows { return "", fmt.Errorf("error fetching rate area id for destination address for shipment ID: %s and service ID %s: %s", p.MTOShipment.ID, p.ServiceItem.ReServiceID, err) } From 776f7307919af7f5d3efeb963f0b812a75402cfb Mon Sep 17 00:00:00 2001 From: Daniel Jordan Date: Thu, 2 Jan 2025 20:34:42 +0000 Subject: [PATCH 06/14] added tests for service param value lookups folder --- .../per_unit_cents_lookup.go | 52 +++---- .../per_unit_cents_lookup_test.go | 132 ++++++++++++++++++ .../port_name_lookup_test.go | 87 ++++++++++++ 3 files changed, 246 insertions(+), 25 deletions(-) create mode 100644 pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup_test.go create mode 100644 pkg/payment_request/service_param_value_lookups/port_name_lookup_test.go 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 88a46c98e44..d2062218620 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 @@ -1,7 +1,6 @@ package serviceparamvaluelookups import ( - "database/sql" "fmt" "github.com/transcom/mymove/pkg/appcontext" @@ -16,16 +15,17 @@ type PerUnitCentsLookup struct { } func (p PerUnitCentsLookup) lookup(appCtx appcontext.AppContext, s *ServiceItemParamKeyData) (string, error) { - var isPeakPeriod bool serviceID := p.ServiceItem.ReServiceID contractID := s.ContractID - if p.ServiceItem.ReService.Code == models.ReServiceCodeIHPK { - // IHPK we need the rate area id for the pickup address - rateAreaID, err := models.FetchRateAreaID(appCtx.DB(), *p.MTOShipment.PickupAddressID, p.ServiceItem.ReServiceID, contractID) + + 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) if err != nil { - return "", fmt.Errorf("error fetching rate area id for shipment ID: %s and service ID %s: %s", p.MTOShipment.ID, p.ServiceItem.ReServiceID, err) + return "", fmt.Errorf("error fetching rate area id for shipment ID: %s and service ID %s: %s", p.MTOShipment.ID, serviceID, err) } - isPeakPeriod = ghcrateengine.IsPeakPeriod(*p.MTOShipment.RequestedPickupDate) + isPeakPeriod := ghcrateengine.IsPeakPeriod(*p.MTOShipment.RequestedPickupDate) var reIntlOtherPrice models.ReIntlOtherPrice err = appCtx.DB().Q(). Where("contract_id = ?", contractID). @@ -37,14 +37,14 @@ func (p PerUnitCentsLookup) lookup(appCtx appcontext.AppContext, s *ServiceItemP return "", fmt.Errorf("error fetching IHPK per unit cents for contractID: %s, serviceID %s, isPeakPeriod: %t, and rateAreaID: %s: %s", contractID, serviceID, isPeakPeriod, rateAreaID, err) } return reIntlOtherPrice.PerUnitCents.ToMillicents().ToCents().String(), nil - } - if p.ServiceItem.ReService.Code == models.ReServiceCodeIHUPK { - // IHUPK we need the rate area id for the destination address - rateAreaID, err := models.FetchRateAreaID(appCtx.DB(), *p.MTOShipment.PickupAddressID, p.ServiceItem.ReServiceID, contractID) - if err != nil && err != sql.ErrNoRows { - return "", fmt.Errorf("error fetching rate area id for shipment ID: %s and service ID %s: %s", p.MTOShipment.ID, p.ServiceItem.ReServiceID, err) + + case models.ReServiceCodeIHUPK: + // IHUPK: Need rate area ID for the destination address + 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) } - isPeakPeriod = ghcrateengine.IsPeakPeriod(*p.MTOShipment.RequestedPickupDate) + isPeakPeriod := ghcrateengine.IsPeakPeriod(*p.MTOShipment.RequestedPickupDate) var reIntlOtherPrice models.ReIntlOtherPrice err = appCtx.DB().Q(). Where("contract_id = ?", contractID). @@ -56,17 +56,18 @@ func (p PerUnitCentsLookup) lookup(appCtx appcontext.AppContext, s *ServiceItemP return "", fmt.Errorf("error fetching IHUPK per unit cents for contractID: %s, serviceID %s, isPeakPeriod: %t, and rateAreaID: %s: %s", contractID, serviceID, isPeakPeriod, rateAreaID, err) } return reIntlOtherPrice.PerUnitCents.ToMillicents().ToCents().String(), nil - } else if p.ServiceItem.ReService.Code == models.ReServiceCodeISLH { - // IHUPK we need the rate area id for the destination address - originRateAreaID, err := models.FetchRateAreaID(appCtx.DB(), *p.MTOShipment.PickupAddressID, p.ServiceItem.ReServiceID, contractID) - if err != nil && err != sql.ErrNoRows { - return "", fmt.Errorf("error fetching rate area id for origina address for shipment ID: %s and service ID %s: %s", p.MTOShipment.ID, p.ServiceItem.ReServiceID, err) + + case models.ReServiceCodeISLH: + // ISLH: Need rate area IDs for origin and destination + 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, p.ServiceItem.ReServiceID, contractID) - if err != nil && err != sql.ErrNoRows { - return "", fmt.Errorf("error fetching rate area id for destination address for shipment ID: %s and service ID %s: %s", p.MTOShipment.ID, p.ServiceItem.ReServiceID, err) + 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) } - isPeakPeriod = ghcrateengine.IsPeakPeriod(*p.MTOShipment.RequestedPickupDate) + isPeakPeriod := ghcrateengine.IsPeakPeriod(*p.MTOShipment.RequestedPickupDate) var reIntlPrice models.ReIntlPrice err = appCtx.DB().Q(). Where("contract_id = ?", contractID). @@ -76,10 +77,11 @@ func (p PerUnitCentsLookup) lookup(appCtx appcontext.AppContext, s *ServiceItemP Where("destination_rate_area_id = ?", destRateAreaID). First(&reIntlPrice) if err != nil { - return "", fmt.Errorf("error fetching ISLH per unit cents for contractID: %s, serviceID %s, isPeakPeriod: %t, originRateAreaID: %s, and destRateAreaid: %s: %s", contractID, serviceID, isPeakPeriod, originRateAreaID, destRateAreaID, err) + return "", fmt.Errorf("error fetching ISLH per unit cents for contractID: %s, serviceID %s, isPeakPeriod: %t, originRateAreaID: %s, and destRateAreaID: %s: %s", contractID, serviceID, isPeakPeriod, originRateAreaID, destRateAreaID, err) } return reIntlPrice.PerUnitCents.ToMillicents().ToCents().String(), nil - } else { + + default: return "", fmt.Errorf("unsupported service code to retrieve service item param PerUnitCents") } } diff --git a/pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup_test.go b/pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup_test.go new file mode 100644 index 00000000000..4d9ceab2512 --- /dev/null +++ b/pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup_test.go @@ -0,0 +1,132 @@ +package serviceparamvaluelookups + +import ( + "time" + + "github.com/gofrs/uuid" + + "github.com/transcom/mymove/pkg/factory" + "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/testdatagen" +) + +func (suite *ServiceParamValueLookupsSuite) TestPerUnitCentsLookup() { + key := models.ServiceItemParamNamePerUnitCents + var mtoServiceItem models.MTOServiceItem + setupTestData := func(serviceCode models.ReServiceCode) { + 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.ReService{ + Code: serviceCode, + }, + }, + }, []factory.Trait{factory.GetTraitAvailableToPrimeMove}) + + } + + suite.Run("success - returns perUnitCent value for IHPK", func() { + setupTestData(models.ReServiceCodeIHPK) + + paramLookup, err := ServiceParamLookupInitialize(suite.AppContextForTest(), suite.planner, mtoServiceItem, uuid.Must(uuid.NewV4()), mtoServiceItem.MoveTaskOrderID, nil) + suite.FatalNoError(err) + + perUnitCents, err := paramLookup.ServiceParamValue(suite.AppContextForTest(), key) + suite.FatalNoError(err) + suite.Equal(perUnitCents, "6997") + }) + + suite.Run("success - returns perUnitCent value for IHUPK", func() { + setupTestData(models.ReServiceCodeIHUPK) + + paramLookup, err := ServiceParamLookupInitialize(suite.AppContextForTest(), suite.planner, mtoServiceItem, uuid.Must(uuid.NewV4()), mtoServiceItem.MoveTaskOrderID, nil) + suite.FatalNoError(err) + + perUnitCents, err := paramLookup.ServiceParamValue(suite.AppContextForTest(), key) + suite.FatalNoError(err) + suite.Equal(perUnitCents, "752") + }) + + suite.Run("success - returns perUnitCent value for ISLH", func() { + testdatagen.MakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + StartDate: time.Now().Add(-24 * time.Hour), + EndDate: time.Now().Add(24 * time.Hour), + }, + }) + move := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) + destinationAddress := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "JBER", + City: "Anchorage", + State: "AK", + PostalCode: "99505", + IsOconus: models.BoolPointer(true), + }, + }, + }, nil) + 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) + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + PickupAddressID: &pickupAddress.ID, + DestinationAddressID: &destinationAddress.ID, + MarketCode: models.MarketCodeInternational, + }, + }, + { + Model: move, + LinkOnly: true, + }, + }, nil) + mtoServiceItem := factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.ReService{ + Code: models.ReServiceCodeISLH, + }, + }, + }, nil) + + paramLookup, err := ServiceParamLookupInitialize(suite.AppContextForTest(), suite.planner, mtoServiceItem, uuid.Must(uuid.NewV4()), mtoServiceItem.MoveTaskOrderID, nil) + suite.FatalNoError(err) + + perUnitCents, err := paramLookup.ServiceParamValue(suite.AppContextForTest(), key) + suite.FatalNoError(err) + suite.Equal(perUnitCents, "1605") + }) + + suite.Run("failure - unauthorized service code", func() { + setupTestData(models.ReServiceCodeDUPK) + + paramLookup, err := ServiceParamLookupInitialize(suite.AppContextForTest(), suite.planner, mtoServiceItem, uuid.Must(uuid.NewV4()), mtoServiceItem.MoveTaskOrderID, nil) + suite.FatalNoError(err) + + perUnitCents, err := paramLookup.ServiceParamValue(suite.AppContextForTest(), key) + suite.Error(err) + suite.Equal(perUnitCents, "") + }) +} diff --git a/pkg/payment_request/service_param_value_lookups/port_name_lookup_test.go b/pkg/payment_request/service_param_value_lookups/port_name_lookup_test.go new file mode 100644 index 00000000000..b0ccb4b2bfe --- /dev/null +++ b/pkg/payment_request/service_param_value_lookups/port_name_lookup_test.go @@ -0,0 +1,87 @@ +package serviceparamvaluelookups + +import ( + "time" + + "github.com/gofrs/uuid" + + "github.com/transcom/mymove/pkg/factory" + "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/testdatagen" +) + +func (suite *ServiceParamValueLookupsSuite) TestPortNameLookup() { + key := models.ServiceItemParamNamePortName + var mtoServiceItem models.MTOServiceItem + setupTestData := func(serviceCode models.ReServiceCode, portID uuid.UUID) { + testdatagen.MakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + StartDate: time.Now().Add(-24 * time.Hour), + EndDate: time.Now().Add(24 * time.Hour), + }, + }) + if serviceCode == models.ReServiceCodePOEFSC { + mtoServiceItem = factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: models.ReService{ + Code: serviceCode, + }, + }, + { + Model: models.MTOServiceItem{ + POELocationID: &portID, + }, + }, + }, []factory.Trait{factory.GetTraitAvailableToPrimeMove}) + } else { + mtoServiceItem = factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: models.ReService{ + Code: serviceCode, + }, + }, + { + Model: models.MTOServiceItem{ + PODLocationID: &portID, + }, + }, + }, []factory.Trait{factory.GetTraitAvailableToPrimeMove}) + } + } + + suite.Run("success - returns PortName value for POEFSC", func() { + port := factory.FetchPortLocation(suite.DB(), []factory.Customization{ + { + Model: models.Port{ + PortCode: "SEA", + }, + }, + }, nil) + setupTestData(models.ReServiceCodePOEFSC, port.ID) + + paramLookup, err := ServiceParamLookupInitialize(suite.AppContextForTest(), suite.planner, mtoServiceItem, uuid.Must(uuid.NewV4()), mtoServiceItem.MoveTaskOrderID, nil) + suite.FatalNoError(err) + + portName, err := paramLookup.ServiceParamValue(suite.AppContextForTest(), key) + suite.FatalNoError(err) + suite.Equal(portName, "SEATTLE TACOMA INTL") + }) + + suite.Run("success - returns PortName value for PODFSC", func() { + port := factory.FetchPortLocation(suite.DB(), []factory.Customization{ + { + Model: models.Port{ + PortCode: "PDX", + }, + }, + }, nil) + setupTestData(models.ReServiceCodePODFSC, port.ID) + + paramLookup, err := ServiceParamLookupInitialize(suite.AppContextForTest(), suite.planner, mtoServiceItem, uuid.Must(uuid.NewV4()), mtoServiceItem.MoveTaskOrderID, nil) + suite.FatalNoError(err) + + portName, err := paramLookup.ServiceParamValue(suite.AppContextForTest(), key) + suite.FatalNoError(err) + suite.Equal(portName, "PORTLAND INTL") + }) +} From 440003f6a5975a15e24d6ce328a0b87b272cac12 Mon Sep 17 00:00:00 2001 From: Daniel Jordan Date: Thu, 2 Jan 2025 20:39:15 +0000 Subject: [PATCH 07/14] mocks --- pkg/services/ghc_rate_engine.go | 6 +- pkg/services/mocks/IntlHHGPackPricer.go | 109 ++++++++++++++++++ pkg/services/mocks/IntlHHGUnpackPricer.go | 109 ++++++++++++++++++ .../mocks/IntlPortFuelSurchargePricer.go | 109 ++++++++++++++++++ .../mocks/IntlShippingAndLinehaulPricer.go | 109 ++++++++++++++++++ 5 files changed, 439 insertions(+), 3 deletions(-) create mode 100644 pkg/services/mocks/IntlHHGPackPricer.go create mode 100644 pkg/services/mocks/IntlHHGUnpackPricer.go create mode 100644 pkg/services/mocks/IntlPortFuelSurchargePricer.go create mode 100644 pkg/services/mocks/IntlShippingAndLinehaulPricer.go diff --git a/pkg/services/ghc_rate_engine.go b/pkg/services/ghc_rate_engine.go index b024ac1d4e3..8b3ef07de0f 100644 --- a/pkg/services/ghc_rate_engine.go +++ b/pkg/services/ghc_rate_engine.go @@ -241,7 +241,7 @@ type IntlShippingAndLinehaulPricer interface { ParamsPricer } -// IntlHHGPackPricer prices international packing for an HHG shipment within a move +// IntlHHGPackPricer prices international packing for an iHHG shipment within a move // //go:generate mockery --name IntlHHGPackPricer type IntlHHGPackPricer interface { @@ -249,7 +249,7 @@ type IntlHHGPackPricer interface { ParamsPricer } -// IntlHHGUnpackPricer prices international unpacking for an HHG shipment within a move +// IntlHHGUnpackPricer prices international unpacking for an iHHG shipment within a move // //go:generate mockery --name IntlHHGUnpackPricer type IntlHHGUnpackPricer interface { @@ -257,7 +257,7 @@ type IntlHHGUnpackPricer interface { ParamsPricer } -// IntlPortFuelSurchargePricer prices the POEFSC/PODFSC service items on a shipment within a move +// IntlPortFuelSurchargePricer prices the POEFSC/PODFSC service items on an iHHG shipment within a move // //go:generate mockery --name IntlPortFuelSurchargePricer type IntlPortFuelSurchargePricer interface { diff --git a/pkg/services/mocks/IntlHHGPackPricer.go b/pkg/services/mocks/IntlHHGPackPricer.go new file mode 100644 index 00000000000..a12bed589ec --- /dev/null +++ b/pkg/services/mocks/IntlHHGPackPricer.go @@ -0,0 +1,109 @@ +// Code generated by mockery. DO NOT EDIT. + +package mocks + +import ( + mock "github.com/stretchr/testify/mock" + appcontext "github.com/transcom/mymove/pkg/appcontext" + + models "github.com/transcom/mymove/pkg/models" + + services "github.com/transcom/mymove/pkg/services" + + time "time" + + unit "github.com/transcom/mymove/pkg/unit" +) + +// IntlHHGPackPricer is an autogenerated mock type for the IntlHHGPackPricer type +type IntlHHGPackPricer struct { + mock.Mock +} + +// Price provides a mock function with given fields: appCtx, contractCode, requestedPickupDate, weight, perUnitCents +func (_m *IntlHHGPackPricer) Price(appCtx appcontext.AppContext, contractCode string, requestedPickupDate time.Time, weight unit.Pound, perUnitCents int) (unit.Cents, services.PricingDisplayParams, error) { + ret := _m.Called(appCtx, contractCode, requestedPickupDate, weight, perUnitCents) + + if len(ret) == 0 { + panic("no return value specified for Price") + } + + var r0 unit.Cents + var r1 services.PricingDisplayParams + var r2 error + if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, time.Time, unit.Pound, int) (unit.Cents, services.PricingDisplayParams, error)); ok { + return rf(appCtx, contractCode, requestedPickupDate, weight, perUnitCents) + } + if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, time.Time, unit.Pound, int) unit.Cents); ok { + r0 = rf(appCtx, contractCode, requestedPickupDate, weight, perUnitCents) + } else { + r0 = ret.Get(0).(unit.Cents) + } + + if rf, ok := ret.Get(1).(func(appcontext.AppContext, string, time.Time, unit.Pound, int) services.PricingDisplayParams); ok { + r1 = rf(appCtx, contractCode, requestedPickupDate, weight, perUnitCents) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(services.PricingDisplayParams) + } + } + + if rf, ok := ret.Get(2).(func(appcontext.AppContext, string, time.Time, unit.Pound, int) error); ok { + r2 = rf(appCtx, contractCode, requestedPickupDate, weight, perUnitCents) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// PriceUsingParams provides a mock function with given fields: appCtx, params +func (_m *IntlHHGPackPricer) PriceUsingParams(appCtx appcontext.AppContext, params models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error) { + ret := _m.Called(appCtx, params) + + if len(ret) == 0 { + panic("no return value specified for PriceUsingParams") + } + + var r0 unit.Cents + var r1 services.PricingDisplayParams + var r2 error + if rf, ok := ret.Get(0).(func(appcontext.AppContext, models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error)); ok { + return rf(appCtx, params) + } + if rf, ok := ret.Get(0).(func(appcontext.AppContext, models.PaymentServiceItemParams) unit.Cents); ok { + r0 = rf(appCtx, params) + } else { + r0 = ret.Get(0).(unit.Cents) + } + + if rf, ok := ret.Get(1).(func(appcontext.AppContext, models.PaymentServiceItemParams) services.PricingDisplayParams); ok { + r1 = rf(appCtx, params) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(services.PricingDisplayParams) + } + } + + if rf, ok := ret.Get(2).(func(appcontext.AppContext, models.PaymentServiceItemParams) error); ok { + r2 = rf(appCtx, params) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// NewIntlHHGPackPricer creates a new instance of IntlHHGPackPricer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewIntlHHGPackPricer(t interface { + mock.TestingT + Cleanup(func()) +}) *IntlHHGPackPricer { + mock := &IntlHHGPackPricer{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/services/mocks/IntlHHGUnpackPricer.go b/pkg/services/mocks/IntlHHGUnpackPricer.go new file mode 100644 index 00000000000..d06f97791d5 --- /dev/null +++ b/pkg/services/mocks/IntlHHGUnpackPricer.go @@ -0,0 +1,109 @@ +// Code generated by mockery. DO NOT EDIT. + +package mocks + +import ( + mock "github.com/stretchr/testify/mock" + appcontext "github.com/transcom/mymove/pkg/appcontext" + + models "github.com/transcom/mymove/pkg/models" + + services "github.com/transcom/mymove/pkg/services" + + time "time" + + unit "github.com/transcom/mymove/pkg/unit" +) + +// IntlHHGUnpackPricer is an autogenerated mock type for the IntlHHGUnpackPricer type +type IntlHHGUnpackPricer struct { + mock.Mock +} + +// Price provides a mock function with given fields: appCtx, contractCode, requestedPickupDate, weight, perUnitCents +func (_m *IntlHHGUnpackPricer) Price(appCtx appcontext.AppContext, contractCode string, requestedPickupDate time.Time, weight unit.Pound, perUnitCents int) (unit.Cents, services.PricingDisplayParams, error) { + ret := _m.Called(appCtx, contractCode, requestedPickupDate, weight, perUnitCents) + + if len(ret) == 0 { + panic("no return value specified for Price") + } + + var r0 unit.Cents + var r1 services.PricingDisplayParams + var r2 error + if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, time.Time, unit.Pound, int) (unit.Cents, services.PricingDisplayParams, error)); ok { + return rf(appCtx, contractCode, requestedPickupDate, weight, perUnitCents) + } + if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, time.Time, unit.Pound, int) unit.Cents); ok { + r0 = rf(appCtx, contractCode, requestedPickupDate, weight, perUnitCents) + } else { + r0 = ret.Get(0).(unit.Cents) + } + + if rf, ok := ret.Get(1).(func(appcontext.AppContext, string, time.Time, unit.Pound, int) services.PricingDisplayParams); ok { + r1 = rf(appCtx, contractCode, requestedPickupDate, weight, perUnitCents) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(services.PricingDisplayParams) + } + } + + if rf, ok := ret.Get(2).(func(appcontext.AppContext, string, time.Time, unit.Pound, int) error); ok { + r2 = rf(appCtx, contractCode, requestedPickupDate, weight, perUnitCents) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// PriceUsingParams provides a mock function with given fields: appCtx, params +func (_m *IntlHHGUnpackPricer) PriceUsingParams(appCtx appcontext.AppContext, params models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error) { + ret := _m.Called(appCtx, params) + + if len(ret) == 0 { + panic("no return value specified for PriceUsingParams") + } + + var r0 unit.Cents + var r1 services.PricingDisplayParams + var r2 error + if rf, ok := ret.Get(0).(func(appcontext.AppContext, models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error)); ok { + return rf(appCtx, params) + } + if rf, ok := ret.Get(0).(func(appcontext.AppContext, models.PaymentServiceItemParams) unit.Cents); ok { + r0 = rf(appCtx, params) + } else { + r0 = ret.Get(0).(unit.Cents) + } + + if rf, ok := ret.Get(1).(func(appcontext.AppContext, models.PaymentServiceItemParams) services.PricingDisplayParams); ok { + r1 = rf(appCtx, params) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(services.PricingDisplayParams) + } + } + + if rf, ok := ret.Get(2).(func(appcontext.AppContext, models.PaymentServiceItemParams) error); ok { + r2 = rf(appCtx, params) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// NewIntlHHGUnpackPricer creates a new instance of IntlHHGUnpackPricer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewIntlHHGUnpackPricer(t interface { + mock.TestingT + Cleanup(func()) +}) *IntlHHGUnpackPricer { + mock := &IntlHHGUnpackPricer{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/services/mocks/IntlPortFuelSurchargePricer.go b/pkg/services/mocks/IntlPortFuelSurchargePricer.go new file mode 100644 index 00000000000..48e396d017e --- /dev/null +++ b/pkg/services/mocks/IntlPortFuelSurchargePricer.go @@ -0,0 +1,109 @@ +// Code generated by mockery. DO NOT EDIT. + +package mocks + +import ( + mock "github.com/stretchr/testify/mock" + appcontext "github.com/transcom/mymove/pkg/appcontext" + + models "github.com/transcom/mymove/pkg/models" + + services "github.com/transcom/mymove/pkg/services" + + time "time" + + unit "github.com/transcom/mymove/pkg/unit" +) + +// IntlPortFuelSurchargePricer is an autogenerated mock type for the IntlPortFuelSurchargePricer type +type IntlPortFuelSurchargePricer struct { + mock.Mock +} + +// Price provides a mock function with given fields: appCtx, actualPickupDate, distance, weight, fscWeightBasedDistanceMultiplier, eiaFuelPrice, portName +func (_m *IntlPortFuelSurchargePricer) Price(appCtx appcontext.AppContext, actualPickupDate time.Time, distance unit.Miles, weight unit.Pound, fscWeightBasedDistanceMultiplier float64, eiaFuelPrice unit.Millicents, portName string) (unit.Cents, services.PricingDisplayParams, error) { + ret := _m.Called(appCtx, actualPickupDate, distance, weight, fscWeightBasedDistanceMultiplier, eiaFuelPrice, portName) + + if len(ret) == 0 { + panic("no return value specified for Price") + } + + var r0 unit.Cents + var r1 services.PricingDisplayParams + var r2 error + if rf, ok := ret.Get(0).(func(appcontext.AppContext, time.Time, unit.Miles, unit.Pound, float64, unit.Millicents, string) (unit.Cents, services.PricingDisplayParams, error)); ok { + return rf(appCtx, actualPickupDate, distance, weight, fscWeightBasedDistanceMultiplier, eiaFuelPrice, portName) + } + if rf, ok := ret.Get(0).(func(appcontext.AppContext, time.Time, unit.Miles, unit.Pound, float64, unit.Millicents, string) unit.Cents); ok { + r0 = rf(appCtx, actualPickupDate, distance, weight, fscWeightBasedDistanceMultiplier, eiaFuelPrice, portName) + } else { + r0 = ret.Get(0).(unit.Cents) + } + + if rf, ok := ret.Get(1).(func(appcontext.AppContext, time.Time, unit.Miles, unit.Pound, float64, unit.Millicents, string) services.PricingDisplayParams); ok { + r1 = rf(appCtx, actualPickupDate, distance, weight, fscWeightBasedDistanceMultiplier, eiaFuelPrice, portName) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(services.PricingDisplayParams) + } + } + + if rf, ok := ret.Get(2).(func(appcontext.AppContext, time.Time, unit.Miles, unit.Pound, float64, unit.Millicents, string) error); ok { + r2 = rf(appCtx, actualPickupDate, distance, weight, fscWeightBasedDistanceMultiplier, eiaFuelPrice, portName) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// PriceUsingParams provides a mock function with given fields: appCtx, params +func (_m *IntlPortFuelSurchargePricer) PriceUsingParams(appCtx appcontext.AppContext, params models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error) { + ret := _m.Called(appCtx, params) + + if len(ret) == 0 { + panic("no return value specified for PriceUsingParams") + } + + var r0 unit.Cents + var r1 services.PricingDisplayParams + var r2 error + if rf, ok := ret.Get(0).(func(appcontext.AppContext, models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error)); ok { + return rf(appCtx, params) + } + if rf, ok := ret.Get(0).(func(appcontext.AppContext, models.PaymentServiceItemParams) unit.Cents); ok { + r0 = rf(appCtx, params) + } else { + r0 = ret.Get(0).(unit.Cents) + } + + if rf, ok := ret.Get(1).(func(appcontext.AppContext, models.PaymentServiceItemParams) services.PricingDisplayParams); ok { + r1 = rf(appCtx, params) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(services.PricingDisplayParams) + } + } + + if rf, ok := ret.Get(2).(func(appcontext.AppContext, models.PaymentServiceItemParams) error); ok { + r2 = rf(appCtx, params) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// NewIntlPortFuelSurchargePricer creates a new instance of IntlPortFuelSurchargePricer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewIntlPortFuelSurchargePricer(t interface { + mock.TestingT + Cleanup(func()) +}) *IntlPortFuelSurchargePricer { + mock := &IntlPortFuelSurchargePricer{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/services/mocks/IntlShippingAndLinehaulPricer.go b/pkg/services/mocks/IntlShippingAndLinehaulPricer.go new file mode 100644 index 00000000000..c6e4ca2b557 --- /dev/null +++ b/pkg/services/mocks/IntlShippingAndLinehaulPricer.go @@ -0,0 +1,109 @@ +// Code generated by mockery. DO NOT EDIT. + +package mocks + +import ( + mock "github.com/stretchr/testify/mock" + appcontext "github.com/transcom/mymove/pkg/appcontext" + + models "github.com/transcom/mymove/pkg/models" + + services "github.com/transcom/mymove/pkg/services" + + time "time" + + unit "github.com/transcom/mymove/pkg/unit" +) + +// IntlShippingAndLinehaulPricer is an autogenerated mock type for the IntlShippingAndLinehaulPricer type +type IntlShippingAndLinehaulPricer struct { + mock.Mock +} + +// Price provides a mock function with given fields: appCtx, contractCode, requestedPickupDate, distance, weight, perUnitCents +func (_m *IntlShippingAndLinehaulPricer) Price(appCtx appcontext.AppContext, contractCode string, requestedPickupDate time.Time, distance unit.Miles, weight unit.Pound, perUnitCents int) (unit.Cents, services.PricingDisplayParams, error) { + ret := _m.Called(appCtx, contractCode, requestedPickupDate, distance, weight, perUnitCents) + + if len(ret) == 0 { + panic("no return value specified for Price") + } + + var r0 unit.Cents + var r1 services.PricingDisplayParams + var r2 error + if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, time.Time, unit.Miles, unit.Pound, int) (unit.Cents, services.PricingDisplayParams, error)); ok { + return rf(appCtx, contractCode, requestedPickupDate, distance, weight, perUnitCents) + } + if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, time.Time, unit.Miles, unit.Pound, int) unit.Cents); ok { + r0 = rf(appCtx, contractCode, requestedPickupDate, distance, weight, perUnitCents) + } else { + r0 = ret.Get(0).(unit.Cents) + } + + if rf, ok := ret.Get(1).(func(appcontext.AppContext, string, time.Time, unit.Miles, unit.Pound, int) services.PricingDisplayParams); ok { + r1 = rf(appCtx, contractCode, requestedPickupDate, distance, weight, perUnitCents) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(services.PricingDisplayParams) + } + } + + if rf, ok := ret.Get(2).(func(appcontext.AppContext, string, time.Time, unit.Miles, unit.Pound, int) error); ok { + r2 = rf(appCtx, contractCode, requestedPickupDate, distance, weight, perUnitCents) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// PriceUsingParams provides a mock function with given fields: appCtx, params +func (_m *IntlShippingAndLinehaulPricer) PriceUsingParams(appCtx appcontext.AppContext, params models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error) { + ret := _m.Called(appCtx, params) + + if len(ret) == 0 { + panic("no return value specified for PriceUsingParams") + } + + var r0 unit.Cents + var r1 services.PricingDisplayParams + var r2 error + if rf, ok := ret.Get(0).(func(appcontext.AppContext, models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error)); ok { + return rf(appCtx, params) + } + if rf, ok := ret.Get(0).(func(appcontext.AppContext, models.PaymentServiceItemParams) unit.Cents); ok { + r0 = rf(appCtx, params) + } else { + r0 = ret.Get(0).(unit.Cents) + } + + if rf, ok := ret.Get(1).(func(appcontext.AppContext, models.PaymentServiceItemParams) services.PricingDisplayParams); ok { + r1 = rf(appCtx, params) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(services.PricingDisplayParams) + } + } + + if rf, ok := ret.Get(2).(func(appcontext.AppContext, models.PaymentServiceItemParams) error); ok { + r2 = rf(appCtx, params) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// NewIntlShippingAndLinehaulPricer creates a new instance of IntlShippingAndLinehaulPricer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewIntlShippingAndLinehaulPricer(t interface { + mock.TestingT + Cleanup(func()) +}) *IntlShippingAndLinehaulPricer { + mock := &IntlShippingAndLinehaulPricer{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} From 88cba28f91b0d613609c6028c8e90c9d85944f7d Mon Sep 17 00:00:00 2001 From: Daniel Jordan Date: Thu, 2 Jan 2025 22:17:22 +0000 Subject: [PATCH 08/14] tests on tests on tests --- .../intl_hhg_pack_pricer_test.go | 115 ++++++++++ .../intl_hhg_unpack_pricer_test.go | 114 ++++++++++ .../intl_port_fuel_surcharge_pricer_test.go | 209 ++++++++++++++++++ .../intl_shipping_and_linehaul_pricer.go | 2 +- .../intl_shipping_and_linehaul_pricer_test.go | 144 ++++++++++++ .../ghcrateengine/pricer_helpers_intl.go | 5 +- .../ghcrateengine/pricer_helpers_intl_test.go | 46 ++++ .../ghcrateengine/pricer_helpers_test.go | 2 +- .../ghcrateengine/pricer_query_helpers.go | 2 +- .../CreatePaymentRequestForm.jsx | 5 +- .../CreatePaymentRequestForm.test.jsx | 6 + 11 files changed, 645 insertions(+), 5 deletions(-) create mode 100644 pkg/services/ghcrateengine/intl_hhg_pack_pricer_test.go create mode 100644 pkg/services/ghcrateengine/intl_hhg_unpack_pricer_test.go create mode 100644 pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer_test.go create mode 100644 pkg/services/ghcrateengine/intl_shipping_and_linehaul_pricer_test.go create mode 100644 pkg/services/ghcrateengine/pricer_helpers_intl_test.go diff --git a/pkg/services/ghcrateengine/intl_hhg_pack_pricer_test.go b/pkg/services/ghcrateengine/intl_hhg_pack_pricer_test.go new file mode 100644 index 00000000000..9674775b906 --- /dev/null +++ b/pkg/services/ghcrateengine/intl_hhg_pack_pricer_test.go @@ -0,0 +1,115 @@ +package ghcrateengine + +import ( + "fmt" + "strconv" + "time" + + "github.com/transcom/mymove/pkg/factory" + "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/testdatagen" + "github.com/transcom/mymove/pkg/unit" +) + +const ( + ihpkTestContractYearName = "Base Period Year 1" + ihpkTestPerUnitCents = unit.Cents(15000) + ihpkTestTotalCost = unit.Cents(315000) + ihpkTestIsPeakPeriod = true + ihpkTestEscalationCompounded = 1.0000 + ihpkTestWeight = unit.Pound(2100) + ihpkTestPriceCents = unit.Cents(193064) +) + +var ihpkTestRequestedPickupDate = time.Date(testdatagen.TestYear, peakStart.month, peakStart.day, 0, 0, 0, 0, time.UTC) + +func (suite *GHCRateEngineServiceSuite) TestIntlHHGPackPricer() { + pricer := NewIntlHHGPackPricer() + + suite.Run("success using PaymentServiceItemParams", func() { + paymentServiceItem := suite.setupIntlPackServiceItem() + + totalCost, displayParams, err := pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.NoError(err) + suite.Equal(ihpkTestTotalCost, totalCost) + + expectedParams := services.PricingDisplayParams{ + {Key: models.ServiceItemParamNameContractYearName, Value: ihpkTestContractYearName}, + {Key: models.ServiceItemParamNameEscalationCompounded, Value: FormatEscalation(ihpkTestEscalationCompounded)}, + {Key: models.ServiceItemParamNameIsPeak, Value: FormatBool(ihpkTestIsPeakPeriod)}, + {Key: models.ServiceItemParamNamePriceRateOrFactor, Value: FormatCents(ihpkTestPerUnitCents)}, + } + suite.validatePricerCreatedParams(expectedParams, displayParams) + }) + + suite.Run("invalid parameters to PriceUsingParams", func() { + paymentServiceItem := suite.setupIntlPackServiceItem() + + // WeightBilled + paymentServiceItem.PaymentServiceItemParams[3].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean + _, _, err := pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.Error(err) + suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to an int", models.ServiceItemParamNameWeightBilled)) + + // PerUnitCents + paymentServiceItem.PaymentServiceItemParams[2].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean + _, _, err = pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.Error(err) + suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to an int", models.ServiceItemParamNamePerUnitCents)) + + // ReferenceDate + paymentServiceItem.PaymentServiceItemParams[1].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean + _, _, err = pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.Error(err) + suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to a time", models.ServiceItemParamNameReferenceDate)) + + // ContractCode + paymentServiceItem.PaymentServiceItemParams[0].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean + _, _, err = pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.Error(err) + suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to a string", models.ServiceItemParamNameContractCode)) + }) +} + +func (suite *GHCRateEngineServiceSuite) setupIntlPackServiceItem() models.PaymentServiceItem { + contract := testdatagen.FetchOrMakeReContract(suite.DB(), testdatagen.Assertions{}) + startDate := time.Date(2018, time.January, 1, 12, 0, 0, 0, time.UTC) + endDate := time.Date(2018, time.December, 31, 12, 0, 0, 0, time.UTC) + testdatagen.FetchOrMakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + Contract: contract, + ContractID: contract.ID, + StartDate: startDate, + EndDate: endDate, + Escalation: 1.0, + EscalationCompounded: 1.0, + }, + }) + return factory.BuildPaymentServiceItemWithParams( + suite.DB(), + models.ReServiceCodeIHPK, + []factory.CreatePaymentServiceItemParams{ + { + Key: models.ServiceItemParamNameContractCode, + KeyType: models.ServiceItemParamTypeString, + Value: contract.Code, + }, + { + Key: models.ServiceItemParamNameReferenceDate, + KeyType: models.ServiceItemParamTypeDate, + Value: ihpkTestRequestedPickupDate.Format(DateParamFormat), + }, + { + Key: models.ServiceItemParamNamePerUnitCents, + KeyType: models.ServiceItemParamTypeInteger, + Value: fmt.Sprintf("%d", int(ihpkTestPerUnitCents)), + }, + { + Key: models.ServiceItemParamNameWeightBilled, + KeyType: models.ServiceItemParamTypeInteger, + Value: strconv.Itoa(ihpkTestWeight.Int()), + }, + }, nil, nil, + ) +} diff --git a/pkg/services/ghcrateengine/intl_hhg_unpack_pricer_test.go b/pkg/services/ghcrateengine/intl_hhg_unpack_pricer_test.go new file mode 100644 index 00000000000..322027b37b5 --- /dev/null +++ b/pkg/services/ghcrateengine/intl_hhg_unpack_pricer_test.go @@ -0,0 +1,114 @@ +package ghcrateengine + +import ( + "fmt" + "strconv" + "time" + + "github.com/transcom/mymove/pkg/factory" + "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/testdatagen" + "github.com/transcom/mymove/pkg/unit" +) + +const ( + ihupkTestContractYearName = "Base Period Year 1" + ihupkTestPerUnitCents = unit.Cents(1200) + ihupkTestTotalCost = unit.Cents(25200) + ihupkTestIsPeakPeriod = true + ihupkTestEscalationCompounded = 1.0000 + ihpukTestWeight = unit.Pound(2100) +) + +var ihupkTestRequestedPickupDate = time.Date(testdatagen.TestYear, peakStart.month, peakStart.day, 0, 0, 0, 0, time.UTC) + +func (suite *GHCRateEngineServiceSuite) TestIntlHHGUnpackPricer() { + pricer := NewIntlHHGUnpackPricer() + + suite.Run("success using PaymentServiceItemParams", func() { + paymentServiceItem := suite.setupIntlUnpackServiceItem() + + totalCost, displayParams, err := pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.NoError(err) + suite.Equal(ihupkTestTotalCost, totalCost) + + expectedParams := services.PricingDisplayParams{ + {Key: models.ServiceItemParamNameContractYearName, Value: ihupkTestContractYearName}, + {Key: models.ServiceItemParamNameEscalationCompounded, Value: FormatEscalation(ihupkTestEscalationCompounded)}, + {Key: models.ServiceItemParamNameIsPeak, Value: FormatBool(ihupkTestIsPeakPeriod)}, + {Key: models.ServiceItemParamNamePriceRateOrFactor, Value: FormatCents(ihupkTestPerUnitCents)}, + } + suite.validatePricerCreatedParams(expectedParams, displayParams) + }) + + suite.Run("invalid parameters to PriceUsingParams", func() { + paymentServiceItem := suite.setupIntlPackServiceItem() + + // WeightBilled + paymentServiceItem.PaymentServiceItemParams[3].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean + _, _, err := pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.Error(err) + suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to an int", models.ServiceItemParamNameWeightBilled)) + + // PerUnitCents + paymentServiceItem.PaymentServiceItemParams[2].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean + _, _, err = pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.Error(err) + suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to an int", models.ServiceItemParamNamePerUnitCents)) + + // ReferenceDate + paymentServiceItem.PaymentServiceItemParams[1].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean + _, _, err = pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.Error(err) + suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to a time", models.ServiceItemParamNameReferenceDate)) + + // ContractCode + paymentServiceItem.PaymentServiceItemParams[0].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean + _, _, err = pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.Error(err) + suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to a string", models.ServiceItemParamNameContractCode)) + }) +} + +func (suite *GHCRateEngineServiceSuite) setupIntlUnpackServiceItem() models.PaymentServiceItem { + contract := testdatagen.FetchOrMakeReContract(suite.DB(), testdatagen.Assertions{}) + startDate := time.Date(2018, time.January, 1, 12, 0, 0, 0, time.UTC) + endDate := time.Date(2018, time.December, 31, 12, 0, 0, 0, time.UTC) + testdatagen.FetchOrMakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + Contract: contract, + ContractID: contract.ID, + StartDate: startDate, + EndDate: endDate, + Escalation: 1.0, + EscalationCompounded: 1.0, + }, + }) + return factory.BuildPaymentServiceItemWithParams( + suite.DB(), + models.ReServiceCodeIHUPK, + []factory.CreatePaymentServiceItemParams{ + { + Key: models.ServiceItemParamNameContractCode, + KeyType: models.ServiceItemParamTypeString, + Value: contract.Code, + }, + { + Key: models.ServiceItemParamNameReferenceDate, + KeyType: models.ServiceItemParamTypeDate, + Value: ihupkTestRequestedPickupDate.Format(DateParamFormat), + }, + { + Key: models.ServiceItemParamNamePerUnitCents, + KeyType: models.ServiceItemParamTypeInteger, + Value: fmt.Sprintf("%d", int(ihupkTestPerUnitCents)), + }, + { + Key: models.ServiceItemParamNameWeightBilled, + KeyType: models.ServiceItemParamTypeInteger, + Value: strconv.Itoa(ihpukTestWeight.Int()), + }, + }, nil, nil, + ) +} diff --git a/pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer_test.go b/pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer_test.go new file mode 100644 index 00000000000..9c07773bdb8 --- /dev/null +++ b/pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer_test.go @@ -0,0 +1,209 @@ +package ghcrateengine + +import ( + "fmt" + "time" + + "github.com/gofrs/uuid" + + "github.com/transcom/mymove/pkg/apperror" + "github.com/transcom/mymove/pkg/factory" + "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/testdatagen" + "github.com/transcom/mymove/pkg/unit" +) + +const ( + intlPortFscTestDistance = unit.Miles(2276) + intlPortFscTestWeight = unit.Pound(4025) + intlPortFscWeightDistanceMultiplier = float64(0.000417) + intlPortFscFuelPrice = unit.Millicents(281400) + intlPortFscPriceCents = unit.Cents(2980) + intlPortFscPortName = "PORTLAND INTL" +) + +var intlPortFscActualPickupDate = time.Date(testdatagen.TestYear, time.June, 5, 7, 33, 11, 456, time.UTC) + +func (suite *GHCRateEngineServiceSuite) TestIntlPortFuelSurchargePricer() { + intlPortFuelSurchargePricer := NewPortFuelSurchargePricer() + + intlPortFscPriceDifferenceInCents := (intlPortFscFuelPrice - baseGHCDieselFuelPrice).Float64() / 1000.0 + intlPortFscMultiplier := intlPortFscWeightDistanceMultiplier * intlPortFscTestDistance.Float64() + + suite.Run("success using PaymentServiceItemParams", func() { + paymentServiceItem := suite.setupPortFuelSurchargeServiceItem() + priceCents, displayParams, err := intlPortFuelSurchargePricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.NoError(err) + suite.Equal(intlPortFscPriceCents, priceCents) + + expectedParams := services.PricingDisplayParams{ + {Key: models.ServiceItemParamNameFSCPriceDifferenceInCents, Value: FormatFloat(intlPortFscPriceDifferenceInCents, 1)}, + {Key: models.ServiceItemParamNameFSCMultiplier, Value: FormatFloat(intlPortFscMultiplier, 7)}, + } + suite.validatePricerCreatedParams(expectedParams, displayParams) + }) + + suite.Run("success without PaymentServiceItemParams", func() { + priceCents, _, err := intlPortFuelSurchargePricer.Price(suite.AppContextForTest(), intlPortFscActualPickupDate, intlPortFscTestDistance, intlPortFscTestWeight, intlPortFscWeightDistanceMultiplier, intlPortFscFuelPrice, intlPortFscPortName) + suite.NoError(err) + suite.Equal(intlPortFscPriceCents, priceCents) + }) + + suite.Run("sending PaymentServiceItemParams without expected param", func() { + _, _, err := intlPortFuelSurchargePricer.PriceUsingParams(suite.AppContextForTest(), models.PaymentServiceItemParams{}) + suite.Error(err) + }) + + suite.Run("fails using PaymentServiceItemParams with below minimum weight for WeightBilled", func() { + paymentServiceItem := suite.setupPortFuelSurchargeServiceItem() + paramsWithBelowMinimumWeight := paymentServiceItem.PaymentServiceItemParams + weightBilledIndex := 2 + if paramsWithBelowMinimumWeight[weightBilledIndex].ServiceItemParamKey.Key != models.ServiceItemParamNameWeightBilled { + suite.FailNow("failed", "Test needs to adjust the weight of %s but the index is pointing to %s ", models.ServiceItemParamNameWeightBilled, paramsWithBelowMinimumWeight[4].ServiceItemParamKey.Key) + } + paramsWithBelowMinimumWeight[weightBilledIndex].Value = "200" + priceCents, _, err := intlPortFuelSurchargePricer.PriceUsingParams(suite.AppContextForTest(), paramsWithBelowMinimumWeight) + if suite.Error(err) { + suite.Equal("weight must be a minimum of 500", err.Error()) + suite.Equal(unit.Cents(0), priceCents) + } + }) + + suite.Run("FSC is negative if fuel price from EIA is below $2.50", func() { + priceCents, _, err := intlPortFuelSurchargePricer.Price(suite.AppContextForTest(), intlPortFscActualPickupDate, intlPortFscTestDistance, intlPortFscTestWeight, intlPortFscWeightDistanceMultiplier, 242400, intlPortFscPortName) + suite.NoError(err) + suite.Equal(unit.Cents(-721), priceCents) + }) + + suite.Run("Price validation errors", func() { + + // No actual pickup date + _, _, err := intlPortFuelSurchargePricer.Price(suite.AppContextForTest(), time.Time{}, intlPortFscTestDistance, intlPortFscTestWeight, intlPortFscWeightDistanceMultiplier, intlPortFscFuelPrice, intlPortFscPortName) + suite.Error(err) + suite.Equal("ActualPickupDate is required", err.Error()) + + // No distance + _, _, err = intlPortFuelSurchargePricer.Price(suite.AppContextForTest(), intlPortFscActualPickupDate, unit.Miles(0), intlPortFscTestWeight, intlPortFscWeightDistanceMultiplier, intlPortFscFuelPrice, intlPortFscPortName) + suite.Error(err) + suite.Equal("Distance must be greater than 0", err.Error()) + + // No weight + _, _, err = intlPortFuelSurchargePricer.Price(suite.AppContextForTest(), intlPortFscActualPickupDate, intlPortFscTestDistance, unit.Pound(0), intlPortFscWeightDistanceMultiplier, intlPortFscFuelPrice, intlPortFscPortName) + suite.Error(err) + suite.Equal(fmt.Sprintf("weight must be a minimum of %d", minDomesticWeight), err.Error()) + + // No weight based distance multiplier + _, _, err = intlPortFuelSurchargePricer.Price(suite.AppContextForTest(), intlPortFscActualPickupDate, intlPortFscTestDistance, intlPortFscTestWeight, 0, intlPortFscFuelPrice, intlPortFscPortName) + suite.Error(err) + suite.Equal("WeightBasedDistanceMultiplier is required", err.Error()) + + // No EIA fuel price + _, _, err = intlPortFuelSurchargePricer.Price(suite.AppContextForTest(), intlPortFscActualPickupDate, intlPortFscTestDistance, intlPortFscTestWeight, intlPortFscWeightDistanceMultiplier, 0, intlPortFscPortName) + suite.Error(err) + suite.Equal("EIAFuelPrice is required", err.Error()) + }) + + suite.Run("PriceUsingParams validation errors", func() { + paymentServiceItem := suite.setupPortFuelSurchargeServiceItem() + paramsWithBelowMinimumWeight := paymentServiceItem.PaymentServiceItemParams + weightBilledIndex := 2 + if paramsWithBelowMinimumWeight[weightBilledIndex].ServiceItemParamKey.Key != models.ServiceItemParamNameWeightBilled { + suite.FailNow("failed", "Test needs to adjust the weight of %s but the index is pointing to %s ", models.ServiceItemParamNameWeightBilled, paramsWithBelowMinimumWeight[4].ServiceItemParamKey.Key) + } + paramsWithBelowMinimumWeight[weightBilledIndex].Value = "200" + + // No ActualPickupDate + missingActualPickupDate := suite.removeOnePaymentServiceItem(paymentServiceItem.PaymentServiceItemParams, models.ServiceItemParamNameActualPickupDate) + _, _, err := intlPortFuelSurchargePricer.PriceUsingParams(suite.AppContextForTest(), missingActualPickupDate) + suite.Error(err) + suite.Equal("could not find param with key ActualPickupDate", err.Error()) + + // No WeightBilled + missingWeightBilled := suite.removeOnePaymentServiceItem(paymentServiceItem.PaymentServiceItemParams, models.ServiceItemParamNameWeightBilled) + _, _, err = intlPortFuelSurchargePricer.PriceUsingParams(suite.AppContextForTest(), missingWeightBilled) + suite.Error(err) + suite.Equal("could not find param with key WeightBilled", err.Error()) + + // No FSCWeightBasedDistanceMultiplier + missingFSCWeightBasedDistanceMultiplier := suite.removeOnePaymentServiceItem(paymentServiceItem.PaymentServiceItemParams, models.ServiceItemParamNameFSCWeightBasedDistanceMultiplier) + _, _, err = intlPortFuelSurchargePricer.PriceUsingParams(suite.AppContextForTest(), missingFSCWeightBasedDistanceMultiplier) + suite.Error(err) + suite.Equal("could not find param with key FSCWeightBasedDistanceMultiplier", err.Error()) + + // No EIAFuelPrice + missingEIAFuelPrice := suite.removeOnePaymentServiceItem(paymentServiceItem.PaymentServiceItemParams, models.ServiceItemParamNameEIAFuelPrice) + _, _, err = intlPortFuelSurchargePricer.PriceUsingParams(suite.AppContextForTest(), missingEIAFuelPrice) + suite.Error(err) + suite.Equal("could not find param with key EIAFuelPrice", err.Error()) + }) + + suite.Run("can't find distance", func() { + paymentServiceItem := suite.setupPortFuelSurchargeServiceItem() + paramsWithBelowMinimumWeight := paymentServiceItem.PaymentServiceItemParams + weightBilledIndex := 2 + if paramsWithBelowMinimumWeight[weightBilledIndex].ServiceItemParamKey.Key != models.ServiceItemParamNameWeightBilled { + suite.FailNow("failed", "Test needs to adjust the weight of %s but the index is pointing to %s ", models.ServiceItemParamNameWeightBilled, paramsWithBelowMinimumWeight[4].ServiceItemParamKey.Key) + } + paramsWithBelowMinimumWeight[weightBilledIndex].Value = "200" + + paramsWithBadReference := paymentServiceItem.PaymentServiceItemParams + paramsWithBadReference[0].PaymentServiceItemID = uuid.Nil + _, _, err := intlPortFuelSurchargePricer.PriceUsingParams(suite.AppContextForTest(), paramsWithBadReference) + suite.Error(err) + suite.IsType(apperror.NotFoundError{}, err) + }) +} + +func (suite *GHCRateEngineServiceSuite) setupPortFuelSurchargeServiceItem() models.PaymentServiceItem { + model := factory.BuildPaymentServiceItemWithParams( + suite.DB(), + models.ReServiceCodePOEFSC, + []factory.CreatePaymentServiceItemParams{ + { + Key: models.ServiceItemParamNameActualPickupDate, + KeyType: models.ServiceItemParamTypeDate, + Value: intlPortFscActualPickupDate.Format(DateParamFormat), + }, + { + Key: models.ServiceItemParamNameDistanceZip, + KeyType: models.ServiceItemParamTypeInteger, + Value: fmt.Sprintf("%d", int(intlPortFscTestDistance)), + }, + { + Key: models.ServiceItemParamNameWeightBilled, + KeyType: models.ServiceItemParamTypeInteger, + Value: fmt.Sprintf("%d", int(intlPortFscTestWeight)), + }, + { + Key: models.ServiceItemParamNameFSCWeightBasedDistanceMultiplier, + KeyType: models.ServiceItemParamTypeDecimal, + Value: fmt.Sprintf("%.7f", intlPortFscWeightDistanceMultiplier), + }, + { + Key: models.ServiceItemParamNameEIAFuelPrice, + KeyType: models.ServiceItemParamTypeInteger, + Value: fmt.Sprintf("%d", int(intlPortFscFuelPrice)), + }, + { + Key: models.ServiceItemParamNamePortName, + KeyType: models.ServiceItemParamTypeString, + Value: intlPortFscPortName, + }, + }, nil, nil, + ) + + var mtoServiceItem models.MTOServiceItem + err := suite.DB().Eager("MTOShipment").Find(&mtoServiceItem, model.MTOServiceItemID) + suite.NoError(err) + + distance := intlPortFscTestDistance + mtoServiceItem.MTOShipment.Distance = &distance + err = suite.DB().Save(&mtoServiceItem.MTOShipment) + suite.NoError(err) + + // the testdatagen factory has some dirty shipment data that we don't want to pass through to the pricer in the test + model.PaymentServiceItemParams[0].PaymentServiceItem.MTOServiceItem = models.MTOServiceItem{} + + return model +} diff --git a/pkg/services/ghcrateengine/intl_shipping_and_linehaul_pricer.go b/pkg/services/ghcrateengine/intl_shipping_and_linehaul_pricer.go index ca0624d75a2..3d0ea35c3ba 100644 --- a/pkg/services/ghcrateengine/intl_shipping_and_linehaul_pricer.go +++ b/pkg/services/ghcrateengine/intl_shipping_and_linehaul_pricer.go @@ -38,7 +38,7 @@ func (p intlShippingAndLinehaulPricer) Price(appCtx appcontext.AppContext, contr isPeakPeriod := IsPeakPeriod(referenceDate) - contract, err := fetchContractsByContractCode(appCtx, contractCode) + contract, err := fetchContractByContractCode(appCtx, contractCode) if err != nil { return 0, nil, fmt.Errorf("could not find contract with code: %s: %w", contractCode, err) } diff --git a/pkg/services/ghcrateengine/intl_shipping_and_linehaul_pricer_test.go b/pkg/services/ghcrateengine/intl_shipping_and_linehaul_pricer_test.go new file mode 100644 index 00000000000..ef3407b04f0 --- /dev/null +++ b/pkg/services/ghcrateengine/intl_shipping_and_linehaul_pricer_test.go @@ -0,0 +1,144 @@ +package ghcrateengine + +import ( + "fmt" + "strconv" + "time" + + "github.com/transcom/mymove/pkg/factory" + "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/testdatagen" + "github.com/transcom/mymove/pkg/unit" +) + +const ( + islhTestContractYearName = "Base Period Year 1" + islhTestPerUnitCents = unit.Cents(15000) + islhTestTotalCost = unit.Cents(315000) + islhTestIsPeakPeriod = true + islhTestEscalationCompounded = 1.0000 + islhTestWeight = unit.Pound(2100) + islhTestDistance = unit.Miles(1201) +) + +var islhTestRequestedPickupDate = time.Date(testdatagen.TestYear, peakStart.month, peakStart.day, 0, 0, 0, 0, time.UTC) + +func (suite *GHCRateEngineServiceSuite) TestIntlShippingAndLinehaulPricer() { + pricer := NewIntlShippingAndLinehaulPricer() + + suite.Run("success using PaymentServiceItemParams", func() { + paymentServiceItem := suite.setupIntlShippingAndLinehaulServiceItem() + + totalCost, displayParams, err := pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.NoError(err) + suite.Equal(islhTestTotalCost, totalCost) + + expectedParams := services.PricingDisplayParams{ + {Key: models.ServiceItemParamNameContractYearName, Value: islhTestContractYearName}, + {Key: models.ServiceItemParamNameEscalationCompounded, Value: FormatEscalation(islhTestEscalationCompounded)}, + {Key: models.ServiceItemParamNameIsPeak, Value: FormatBool(islhTestIsPeakPeriod)}, + {Key: models.ServiceItemParamNamePriceRateOrFactor, Value: FormatCents(islhTestPerUnitCents)}, + } + suite.validatePricerCreatedParams(expectedParams, displayParams) + }) + + suite.Run("invalid parameters to PriceUsingParams", func() { + paymentServiceItem := suite.setupIntlShippingAndLinehaulServiceItem() + + // PerUnitCents + paymentServiceItem.PaymentServiceItemParams[3].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean + _, _, err := pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.Error(err) + suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to an int", models.ServiceItemParamNamePerUnitCents)) + + // ReferenceDate + paymentServiceItem.PaymentServiceItemParams[2].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean + _, _, err = pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.Error(err) + suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to a time", models.ServiceItemParamNameReferenceDate)) + + // DistanceZip + paymentServiceItem.PaymentServiceItemParams[1].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean + _, _, err = pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.Error(err) + suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to an int", models.ServiceItemParamNameDistanceZip)) + + // ContractCode + paymentServiceItem.PaymentServiceItemParams[0].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean + _, _, err = pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.Error(err) + suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to a string", models.ServiceItemParamNameContractCode)) + }) + + suite.Run("Price validation errors", func() { + + // No contract code + _, _, err := pricer.Price(suite.AppContextForTest(), "", islhTestRequestedPickupDate, islhTestDistance, islhTestWeight, islhTestPerUnitCents.Int()) + suite.Error(err) + suite.Equal("ContractCode is required", err.Error()) + + // No reference date + _, _, err = pricer.Price(suite.AppContextForTest(), testdatagen.DefaultContractCode, time.Time{}, islhTestDistance, islhTestWeight, islhTestPerUnitCents.Int()) + suite.Error(err) + suite.Equal("referenceDate is required", err.Error()) + + // No weight + _, _, err = pricer.Price(suite.AppContextForTest(), testdatagen.DefaultContractCode, islhTestRequestedPickupDate, islhTestDistance, 0, islhTestPerUnitCents.Int()) + suite.Error(err) + suite.Equal(fmt.Sprintf("weight must be at least %d", minIntlWeightHHG), err.Error()) + + // No per unit cents + _, _, err = pricer.Price(suite.AppContextForTest(), testdatagen.DefaultContractCode, islhTestRequestedPickupDate, islhTestDistance, islhTestWeight, 0) + suite.Error(err) + suite.Equal("PerUnitCents is required", err.Error()) + + }) +} + +func (suite *GHCRateEngineServiceSuite) setupIntlShippingAndLinehaulServiceItem() models.PaymentServiceItem { + contract := testdatagen.FetchOrMakeReContract(suite.DB(), testdatagen.Assertions{}) + startDate := time.Date(2018, time.January, 1, 12, 0, 0, 0, time.UTC) + endDate := time.Date(2018, time.December, 31, 12, 0, 0, 0, time.UTC) + testdatagen.FetchOrMakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + Contract: contract, + ContractID: contract.ID, + StartDate: startDate, + EndDate: endDate, + Escalation: 1.0, + EscalationCompounded: 1.0, + }, + }) + return factory.BuildPaymentServiceItemWithParams( + suite.DB(), + models.ReServiceCodeISLH, + []factory.CreatePaymentServiceItemParams{ + { + Key: models.ServiceItemParamNameContractCode, + KeyType: models.ServiceItemParamTypeString, + Value: contract.Code, + }, + { + Key: models.ServiceItemParamNameDistanceZip, + KeyType: models.ServiceItemParamTypeInteger, + Value: fmt.Sprintf("%d", int(islhTestDistance)), + }, + { + Key: models.ServiceItemParamNameReferenceDate, + KeyType: models.ServiceItemParamTypeDate, + Value: islhTestRequestedPickupDate.Format(DateParamFormat), + }, + { + Key: models.ServiceItemParamNamePerUnitCents, + KeyType: models.ServiceItemParamTypeInteger, + Value: fmt.Sprintf("%d", int(islhTestPerUnitCents)), + }, + { + Key: models.ServiceItemParamNameWeightBilled, + KeyType: models.ServiceItemParamTypeInteger, + Value: strconv.Itoa(islhTestWeight.Int()), + }, + }, nil, nil, + ) +} diff --git a/pkg/services/ghcrateengine/pricer_helpers_intl.go b/pkg/services/ghcrateengine/pricer_helpers_intl.go index 37f01ee9966..924dad55537 100644 --- a/pkg/services/ghcrateengine/pricer_helpers_intl.go +++ b/pkg/services/ghcrateengine/pricer_helpers_intl.go @@ -23,10 +23,13 @@ func priceIntlPackUnpack(appCtx appcontext.AppContext, packUnpackCode models.ReS if referenceDate.IsZero() { return 0, nil, errors.New("ReferenceDate is required") } + if perUnitCents == 0 { + return 0, nil, errors.New("PerUnitCents is required") + } isPeakPeriod := IsPeakPeriod(referenceDate) - contract, err := fetchContractsByContractCode(appCtx, contractCode) + contract, err := fetchContractByContractCode(appCtx, contractCode) if err != nil { return 0, nil, fmt.Errorf("could not find contract with code: %s: %w", contractCode, err) } diff --git a/pkg/services/ghcrateengine/pricer_helpers_intl_test.go b/pkg/services/ghcrateengine/pricer_helpers_intl_test.go new file mode 100644 index 00000000000..19539e4c976 --- /dev/null +++ b/pkg/services/ghcrateengine/pricer_helpers_intl_test.go @@ -0,0 +1,46 @@ +package ghcrateengine + +import ( + "time" + + "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/testdatagen" +) + +func (suite *GHCRateEngineServiceSuite) TestPriceIntlPackUnpack() { + suite.Run("success with IHPK", func() { + suite.setupIntlPackServiceItem() + totalCost, displayParams, err := priceIntlPackUnpack(suite.AppContextForTest(), models.ReServiceCodeIHPK, testdatagen.DefaultContractCode, ihpkTestRequestedPickupDate, ihpkTestWeight, ihpkTestPerUnitCents.Int()) + suite.NoError(err) + suite.Equal(ihpkTestTotalCost, totalCost) + + expectedParams := services.PricingDisplayParams{ + {Key: models.ServiceItemParamNameContractYearName, Value: ihpkTestContractYearName}, + {Key: models.ServiceItemParamNameEscalationCompounded, Value: FormatEscalation(ihpkTestEscalationCompounded)}, + {Key: models.ServiceItemParamNameIsPeak, Value: FormatBool(ihpkTestIsPeakPeriod)}, + {Key: models.ServiceItemParamNamePriceRateOrFactor, Value: FormatCents(ihpkTestPerUnitCents)}, + } + suite.validatePricerCreatedParams(expectedParams, displayParams) + }) + + suite.Run("Invalid parameters to Price", func() { + suite.setupIntlPackServiceItem() + _, _, err := priceIntlPackUnpack(suite.AppContextForTest(), models.ReServiceCodeDLH, testdatagen.DefaultContractCode, ihpkTestRequestedPickupDate, ihpkTestWeight, ihpkTestPerUnitCents.Int()) + suite.Error(err) + suite.Contains(err.Error(), "unsupported pack/unpack code") + + _, _, err = priceIntlPackUnpack(suite.AppContextForTest(), models.ReServiceCodeIHPK, "", ihpkTestRequestedPickupDate, ihpkTestWeight, ihpkTestPerUnitCents.Int()) + suite.Error(err) + suite.Contains(err.Error(), "ContractCode is required") + + _, _, err = priceIntlPackUnpack(suite.AppContextForTest(), models.ReServiceCodeIHPK, testdatagen.DefaultContractCode, time.Time{}, ihpkTestWeight, ihpkTestPerUnitCents.Int()) + suite.Error(err) + suite.Contains(err.Error(), "ReferenceDate is required") + + _, _, err = priceIntlPackUnpack(suite.AppContextForTest(), models.ReServiceCodeIHPK, testdatagen.DefaultContractCode, ihpkTestRequestedPickupDate, ihpkTestWeight, 0) + suite.Error(err) + suite.Contains(err.Error(), "PerUnitCents is required") + }) + +} diff --git a/pkg/services/ghcrateengine/pricer_helpers_test.go b/pkg/services/ghcrateengine/pricer_helpers_test.go index be3648927e2..06b9ec30044 100644 --- a/pkg/services/ghcrateengine/pricer_helpers_test.go +++ b/pkg/services/ghcrateengine/pricer_helpers_test.go @@ -529,7 +529,7 @@ func (suite *GHCRateEngineServiceSuite) Test_createPricerGeneratedParams() { _, err := createPricerGeneratedParams(suite.AppContextForTest(), subtestData.paymentServiceItem.ID, invalidParam) suite.Error(err) - suite.Contains(err.Error(), "Service item param key is not a pricer param") + suite.Contains(err.Error(), "service item param key is not a pricer param") }) suite.Run("errors if no PricingParms passed from the Pricer", func() { diff --git a/pkg/services/ghcrateengine/pricer_query_helpers.go b/pkg/services/ghcrateengine/pricer_query_helpers.go index 51acb06f9bd..84cde4fc64c 100644 --- a/pkg/services/ghcrateengine/pricer_query_helpers.go +++ b/pkg/services/ghcrateengine/pricer_query_helpers.go @@ -103,7 +103,7 @@ func fetchContractsByContractId(appCtx appcontext.AppContext, contractID uuid.UU return contracts, nil } -func fetchContractsByContractCode(appCtx appcontext.AppContext, contractCode string) (models.ReContract, error) { +func fetchContractByContractCode(appCtx appcontext.AppContext, contractCode string) (models.ReContract, error) { var contract models.ReContract err := appCtx.DB().Where("code = $1", contractCode).First(&contract) if err != nil { diff --git a/src/components/PrimeUI/CreatePaymentRequestForm/CreatePaymentRequestForm.jsx b/src/components/PrimeUI/CreatePaymentRequestForm/CreatePaymentRequestForm.jsx index 5aa3fdc0187..410e65506f1 100644 --- a/src/components/PrimeUI/CreatePaymentRequestForm/CreatePaymentRequestForm.jsx +++ b/src/components/PrimeUI/CreatePaymentRequestForm/CreatePaymentRequestForm.jsx @@ -145,7 +145,10 @@ const CreatePaymentRequestForm = ({ mtoServiceItem.reServiceCode === 'DOSFSC' || mtoServiceItem.reServiceCode === 'DDSHUT' || mtoServiceItem.reServiceCode === 'IHPK' || - mtoServiceItem.reServiceCode === 'IHUPK') && ( + mtoServiceItem.reServiceCode === 'IHUPK' || + mtoServiceItem.reServiceCode === 'ISLH' || + mtoServiceItem.reServiceCode === 'POEFSC' || + mtoServiceItem.reServiceCode === 'PODFSC') && ( { { id: '6', reServiceCode: 'DDFSIT', reServiceName: 'Domestic destination 1st day SIT' }, ], 2: [{ id: '5', reServiceCode: 'FSC' }], + 3: [ + { id: '7', reServiceCode: 'IHPK' }, + { id: '8', reServiceCode: 'IHUPK' }, + { id: '8', reServiceCode: 'ISLH' }, + { id: '8', reServiceCode: 'POEFSC' }, + ], }; it('renders the form', async () => { From c82e97c82e93477123ccaaa649cd42ee1956d85b Mon Sep 17 00:00:00 2001 From: Daniel Jordan Date: Thu, 2 Jan 2025 22:28:29 +0000 Subject: [PATCH 09/14] updating address facotry to handle bogus postal code values when getting post region city ids --- pkg/factory/address_factory.go | 40 +++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/pkg/factory/address_factory.go b/pkg/factory/address_factory.go index d6b7dff6ce5..1ef93086ad8 100644 --- a/pkg/factory/address_factory.go +++ b/pkg/factory/address_factory.go @@ -1,7 +1,10 @@ package factory import ( + "database/sql" + "github.com/gobuffalo/pop/v6" + "github.com/gofrs/uuid" "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/testdatagen" @@ -24,15 +27,17 @@ func BuildAddress(db *pop.Connection, customs []Customization, traits []Trait) m } // Create default Address + beverlyHillsUsprc := uuid.FromStringOrNil("3b9f0ae6-3b2b-44a6-9fcd-8ead346648c4") address := models.Address{ - StreetAddress1: "123 Any Street", - StreetAddress2: models.StringPointer("P.O. Box 12345"), - StreetAddress3: models.StringPointer("c/o Some Person"), - City: "Beverly Hills", - State: "CA", - PostalCode: "90210", - County: models.StringPointer("LOS ANGELES"), - IsOconus: models.BoolPointer(false), + StreetAddress1: "123 Any Street", + StreetAddress2: models.StringPointer("P.O. Box 12345"), + StreetAddress3: models.StringPointer("c/o Some Person"), + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + County: models.StringPointer("LOS ANGELES"), + IsOconus: models.BoolPointer(false), + UsPostRegionCityID: &beverlyHillsUsprc, } // Find/create the Country if customization is provided @@ -56,7 +61,7 @@ func BuildAddress(db *pop.Connection, customs []Customization, traits []Trait) m // Overwrite values with those from customizations testdatagen.MergeModels(&address, cAddress) - // This helps assign counties when the factory is called for seed data or tests + // This helps assign counties & us_post_region_cities_id values when the factory is called for seed data or tests // Additionally, also only run if not 90210. 90210's county is by default populated if db != nil && address.PostalCode != "90210" { county, err := models.FindCountyByZipCode(db, address.PostalCode) @@ -67,19 +72,18 @@ func BuildAddress(db *pop.Connection, customs []Customization, traits []Trait) m // The zip code successfully found a county address.County = county } - } else if db == nil && address.PostalCode != "90210" { - // If no db supplied, mark that - address.County = models.StringPointer("db nil when created") - } - - if db != nil { + // seeing if the postal code provided has a related us_post_region_cities_id usprc, err := models.FindByZipCode(db, address.PostalCode) - if err != nil { - return models.Address{} - } else { + if err != nil && err != sql.ErrNoRows { + address.UsPostRegionCityID = nil + address.UsPostRegionCity = nil + } else if usprc.ID != uuid.Nil { address.UsPostRegionCityID = &usprc.ID address.UsPostRegionCity = usprc } + } else if db == nil && address.PostalCode != "90210" { + // If no db supplied, mark that + address.County = models.StringPointer("db nil when created") } // If db is false, it's a stub. No need to create in database. From 5c81091f67693ad0c2199595f24b3cddfc9f82c9 Mon Sep 17 00:00:00 2001 From: Daniel Jordan Date: Fri, 3 Jan 2025 00:06:46 +0000 Subject: [PATCH 10/14] fixing some issues with tests and address factory --- pkg/factory/address_factory.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pkg/factory/address_factory.go b/pkg/factory/address_factory.go index 1ef93086ad8..27d92999d00 100644 --- a/pkg/factory/address_factory.go +++ b/pkg/factory/address_factory.go @@ -72,7 +72,12 @@ func BuildAddress(db *pop.Connection, customs []Customization, traits []Trait) m // The zip code successfully found a county address.County = county } - // seeing if the postal code provided has a related us_post_region_cities_id + } else if db == nil && address.PostalCode != "90210" { + // If no db supplied, mark that + address.County = models.StringPointer("db nil when created") + } + + if db != nil && address.PostalCode != "90210" && cAddress.UsPostRegionCityID == nil { usprc, err := models.FindByZipCode(db, address.PostalCode) if err != nil && err != sql.ErrNoRows { address.UsPostRegionCityID = nil @@ -81,9 +86,6 @@ func BuildAddress(db *pop.Connection, customs []Customization, traits []Trait) m address.UsPostRegionCityID = &usprc.ID address.UsPostRegionCity = usprc } - } else if db == nil && address.PostalCode != "90210" { - // If no db supplied, mark that - address.County = models.StringPointer("db nil when created") } // If db is false, it's a stub. No need to create in database. From f39bfa372574e0e052ba52f32191c30c7a6c1740 Mon Sep 17 00:00:00 2001 From: Daniel Jordan Date: Fri, 3 Jan 2025 19:19:22 +0000 Subject: [PATCH 11/14] PR fix feedback --- ...aram_values_to_service_params_table.up.sql | 3 ++ .../per_unit_cents_lookup.go | 3 ++ .../per_unit_cents_lookup_test.go | 31 +++++++++++++ .../port_name_lookup.go | 2 +- .../port_name_lookup_test.go | 31 ++++++++++++- pkg/services/ghc_rate_engine.go | 2 +- .../intl_port_fuel_surcharge_pricer.go | 9 ++-- .../intl_port_fuel_surcharge_pricer_test.go | 14 +++--- .../CreatePaymentRequestForm.jsx | 43 ++++++++++--------- src/constants/serviceItems.js | 3 ++ 10 files changed, 103 insertions(+), 38 deletions(-) diff --git a/migrations/app/schema/20241226173330_add_intl_param_values_to_service_params_table.up.sql b/migrations/app/schema/20241226173330_add_intl_param_values_to_service_params_table.up.sql index 63c1e404e50..859bbcb47b6 100644 --- a/migrations/app/schema/20241226173330_add_intl_param_values_to_service_params_table.up.sql +++ b/migrations/app/schema/20241226173330_add_intl_param_values_to_service_params_table.up.sql @@ -1,3 +1,6 @@ +-- dropping function that is not needed anymore +DROP FUNCTION IF EXISTS get_rate_area_id(UUID, UUID); + -- need to add in param keys for international shipments, this will be used to show breakdowns to the TIO INSERT INTO service_item_param_keys (id, key,description,type,origin,created_at,updated_at) VALUES ('d9ad3878-4b94-4722-bbaf-d4b8080f339d','PortName','Name of the port for an international shipment','STRING','SYSTEM','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957'), 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 d2062218620..b339fbf43dd 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 @@ -17,6 +17,9 @@ type PerUnitCentsLookup struct { func (p PerUnitCentsLookup) lookup(appCtx appcontext.AppContext, s *ServiceItemParamKeyData) (string, error) { serviceID := p.ServiceItem.ReServiceID contractID := s.ContractID + if p.MTOShipment.RequestedPickupDate == nil { + return "", fmt.Errorf("requested pickup date is required for shipment with id: %s", p.MTOShipment.ID) + } switch p.ServiceItem.ReService.Code { case models.ReServiceCodeIHPK: diff --git a/pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup_test.go b/pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup_test.go index 4d9ceab2512..9937f86217b 100644 --- a/pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup_test.go +++ b/pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup_test.go @@ -129,4 +129,35 @@ func (suite *ServiceParamValueLookupsSuite) TestPerUnitCentsLookup() { suite.Error(err) suite.Equal(perUnitCents, "") }) + + suite.Run("failure - no requested pickup date on shipment", 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.ReService{ + Code: models.ReServiceCodeIHPK, + }, + }, + { + Model: models.MTOShipment{ + RequestedPickupDate: nil, + }, + }, + }, []factory.Trait{factory.GetTraitAvailableToPrimeMove}) + + mtoServiceItem.MTOShipment.RequestedPickupDate = nil + suite.MustSave(&mtoServiceItem.MTOShipment) + + paramLookup, err := ServiceParamLookupInitialize(suite.AppContextForTest(), suite.planner, mtoServiceItem, uuid.Must(uuid.NewV4()), mtoServiceItem.MoveTaskOrderID, nil) + suite.FatalNoError(err) + + perUnitCents, err := paramLookup.ServiceParamValue(suite.AppContextForTest(), key) + suite.Error(err) + suite.Equal(perUnitCents, "") + }) } diff --git a/pkg/payment_request/service_param_value_lookups/port_name_lookup.go b/pkg/payment_request/service_param_value_lookups/port_name_lookup.go index 5013d3ae2c8..a925ceaa099 100644 --- a/pkg/payment_request/service_param_value_lookups/port_name_lookup.go +++ b/pkg/payment_request/service_param_value_lookups/port_name_lookup.go @@ -21,7 +21,7 @@ func (p PortNameLookup) lookup(appCtx appcontext.AppContext, _ *ServiceItemParam } else if p.ServiceItem.POELocationID != nil { portLocationID = p.ServiceItem.POELocationID } else { - return "", nil + return "", fmt.Errorf("unable to find port location for service item id: %s", p.ServiceItem.ID) } var portLocation models.PortLocation err := appCtx.DB().Q(). diff --git a/pkg/payment_request/service_param_value_lookups/port_name_lookup_test.go b/pkg/payment_request/service_param_value_lookups/port_name_lookup_test.go index b0ccb4b2bfe..a16b174dc1c 100644 --- a/pkg/payment_request/service_param_value_lookups/port_name_lookup_test.go +++ b/pkg/payment_request/service_param_value_lookups/port_name_lookup_test.go @@ -64,7 +64,7 @@ func (suite *ServiceParamValueLookupsSuite) TestPortNameLookup() { portName, err := paramLookup.ServiceParamValue(suite.AppContextForTest(), key) suite.FatalNoError(err) - suite.Equal(portName, "SEATTLE TACOMA INTL") + suite.Equal(portName, port.Port.PortName) }) suite.Run("success - returns PortName value for PODFSC", func() { @@ -82,6 +82,33 @@ func (suite *ServiceParamValueLookupsSuite) TestPortNameLookup() { portName, err := paramLookup.ServiceParamValue(suite.AppContextForTest(), key) suite.FatalNoError(err) - suite.Equal(portName, "PORTLAND INTL") + suite.Equal(portName, port.Port.PortName) + }) + + suite.Run("failure - no port value on service item", 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.ReService{ + Code: models.ReServiceCodePOEFSC, + }, + }, + { + Model: models.MTOServiceItem{ + POELocationID: nil, + }, + }, + }, []factory.Trait{factory.GetTraitAvailableToPrimeMove}) + + paramLookup, err := ServiceParamLookupInitialize(suite.AppContextForTest(), suite.planner, mtoServiceItem, uuid.Must(uuid.NewV4()), mtoServiceItem.MoveTaskOrderID, nil) + suite.FatalNoError(err) + + _, err = paramLookup.ServiceParamValue(suite.AppContextForTest(), key) + suite.Error(err) }) } diff --git a/pkg/services/ghc_rate_engine.go b/pkg/services/ghc_rate_engine.go index 8b3ef07de0f..2247e3d7426 100644 --- a/pkg/services/ghc_rate_engine.go +++ b/pkg/services/ghc_rate_engine.go @@ -261,6 +261,6 @@ type IntlHHGUnpackPricer interface { // //go:generate mockery --name IntlPortFuelSurchargePricer type IntlPortFuelSurchargePricer interface { - Price(appCtx appcontext.AppContext, actualPickupDate time.Time, distance unit.Miles, weight unit.Pound, fscWeightBasedDistanceMultiplier float64, eiaFuelPrice unit.Millicents, portName string) (unit.Cents, PricingDisplayParams, error) + Price(appCtx appcontext.AppContext, actualPickupDate time.Time, distance unit.Miles, weight unit.Pound, fscWeightBasedDistanceMultiplier float64, eiaFuelPrice unit.Millicents) (unit.Cents, PricingDisplayParams, error) ParamsPricer } diff --git a/pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer.go b/pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer.go index 7d2c94e7096..3e6dcfe1bdd 100644 --- a/pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer.go +++ b/pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer.go @@ -23,7 +23,7 @@ func NewPortFuelSurchargePricer() services.IntlPortFuelSurchargePricer { return &portFuelSurchargePricer{} } -func (p portFuelSurchargePricer) Price(_ appcontext.AppContext, actualPickupDate time.Time, distance unit.Miles, weight unit.Pound, fscWeightBasedDistanceMultiplier float64, eiaFuelPrice unit.Millicents, portName string) (unit.Cents, services.PricingDisplayParams, error) { +func (p portFuelSurchargePricer) Price(_ appcontext.AppContext, actualPickupDate time.Time, distance unit.Miles, weight unit.Pound, fscWeightBasedDistanceMultiplier float64, eiaFuelPrice unit.Millicents) (unit.Cents, services.PricingDisplayParams, error) { // Validate parameters if actualPickupDate.IsZero() { return 0, nil, errors.New("ActualPickupDate is required") @@ -40,9 +40,6 @@ func (p portFuelSurchargePricer) Price(_ appcontext.AppContext, actualPickupDate if eiaFuelPrice == 0 { return 0, nil, errors.New("EIAFuelPrice is required") } - if portName == "" { - return 0, nil, errors.New("PortName is required") - } fscPriceDifferenceInCents := (eiaFuelPrice - baseGHCDieselFuelPrice).Float64() / 1000.0 fscMultiplier := fscWeightBasedDistanceMultiplier * distance.Float64() @@ -99,10 +96,10 @@ func (p portFuelSurchargePricer) PriceUsingParams(appCtx appcontext.AppContext, return unit.Cents(0), nil, err } - portName, err := getParamString(params, models.ServiceItemParamNamePortName) + _, err = getParamString(params, models.ServiceItemParamNamePortName) if err != nil { return unit.Cents(0), nil, err } - return p.Price(appCtx, actualPickupDate, unit.Miles(distance), unit.Pound(weightBilled), fscWeightBasedDistanceMultiplier, unit.Millicents(eiaFuelPrice), portName) + return p.Price(appCtx, actualPickupDate, unit.Miles(distance), unit.Pound(weightBilled), fscWeightBasedDistanceMultiplier, unit.Millicents(eiaFuelPrice)) } diff --git a/pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer_test.go b/pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer_test.go index 9c07773bdb8..fa660bd796d 100644 --- a/pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer_test.go +++ b/pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer_test.go @@ -45,7 +45,7 @@ func (suite *GHCRateEngineServiceSuite) TestIntlPortFuelSurchargePricer() { }) suite.Run("success without PaymentServiceItemParams", func() { - priceCents, _, err := intlPortFuelSurchargePricer.Price(suite.AppContextForTest(), intlPortFscActualPickupDate, intlPortFscTestDistance, intlPortFscTestWeight, intlPortFscWeightDistanceMultiplier, intlPortFscFuelPrice, intlPortFscPortName) + priceCents, _, err := intlPortFuelSurchargePricer.Price(suite.AppContextForTest(), intlPortFscActualPickupDate, intlPortFscTestDistance, intlPortFscTestWeight, intlPortFscWeightDistanceMultiplier, intlPortFscFuelPrice) suite.NoError(err) suite.Equal(intlPortFscPriceCents, priceCents) }) @@ -71,7 +71,7 @@ func (suite *GHCRateEngineServiceSuite) TestIntlPortFuelSurchargePricer() { }) suite.Run("FSC is negative if fuel price from EIA is below $2.50", func() { - priceCents, _, err := intlPortFuelSurchargePricer.Price(suite.AppContextForTest(), intlPortFscActualPickupDate, intlPortFscTestDistance, intlPortFscTestWeight, intlPortFscWeightDistanceMultiplier, 242400, intlPortFscPortName) + priceCents, _, err := intlPortFuelSurchargePricer.Price(suite.AppContextForTest(), intlPortFscActualPickupDate, intlPortFscTestDistance, intlPortFscTestWeight, intlPortFscWeightDistanceMultiplier, 242400) suite.NoError(err) suite.Equal(unit.Cents(-721), priceCents) }) @@ -79,27 +79,27 @@ func (suite *GHCRateEngineServiceSuite) TestIntlPortFuelSurchargePricer() { suite.Run("Price validation errors", func() { // No actual pickup date - _, _, err := intlPortFuelSurchargePricer.Price(suite.AppContextForTest(), time.Time{}, intlPortFscTestDistance, intlPortFscTestWeight, intlPortFscWeightDistanceMultiplier, intlPortFscFuelPrice, intlPortFscPortName) + _, _, err := intlPortFuelSurchargePricer.Price(suite.AppContextForTest(), time.Time{}, intlPortFscTestDistance, intlPortFscTestWeight, intlPortFscWeightDistanceMultiplier, intlPortFscFuelPrice) suite.Error(err) suite.Equal("ActualPickupDate is required", err.Error()) // No distance - _, _, err = intlPortFuelSurchargePricer.Price(suite.AppContextForTest(), intlPortFscActualPickupDate, unit.Miles(0), intlPortFscTestWeight, intlPortFscWeightDistanceMultiplier, intlPortFscFuelPrice, intlPortFscPortName) + _, _, err = intlPortFuelSurchargePricer.Price(suite.AppContextForTest(), intlPortFscActualPickupDate, unit.Miles(0), intlPortFscTestWeight, intlPortFscWeightDistanceMultiplier, intlPortFscFuelPrice) suite.Error(err) suite.Equal("Distance must be greater than 0", err.Error()) // No weight - _, _, err = intlPortFuelSurchargePricer.Price(suite.AppContextForTest(), intlPortFscActualPickupDate, intlPortFscTestDistance, unit.Pound(0), intlPortFscWeightDistanceMultiplier, intlPortFscFuelPrice, intlPortFscPortName) + _, _, err = intlPortFuelSurchargePricer.Price(suite.AppContextForTest(), intlPortFscActualPickupDate, intlPortFscTestDistance, unit.Pound(0), intlPortFscWeightDistanceMultiplier, intlPortFscFuelPrice) suite.Error(err) suite.Equal(fmt.Sprintf("weight must be a minimum of %d", minDomesticWeight), err.Error()) // No weight based distance multiplier - _, _, err = intlPortFuelSurchargePricer.Price(suite.AppContextForTest(), intlPortFscActualPickupDate, intlPortFscTestDistance, intlPortFscTestWeight, 0, intlPortFscFuelPrice, intlPortFscPortName) + _, _, err = intlPortFuelSurchargePricer.Price(suite.AppContextForTest(), intlPortFscActualPickupDate, intlPortFscTestDistance, intlPortFscTestWeight, 0, intlPortFscFuelPrice) suite.Error(err) suite.Equal("WeightBasedDistanceMultiplier is required", err.Error()) // No EIA fuel price - _, _, err = intlPortFuelSurchargePricer.Price(suite.AppContextForTest(), intlPortFscActualPickupDate, intlPortFscTestDistance, intlPortFscTestWeight, intlPortFscWeightDistanceMultiplier, 0, intlPortFscPortName) + _, _, err = intlPortFuelSurchargePricer.Price(suite.AppContextForTest(), intlPortFscActualPickupDate, intlPortFscTestDistance, intlPortFscTestWeight, intlPortFscWeightDistanceMultiplier, 0) suite.Error(err) suite.Equal("EIAFuelPrice is required", err.Error()) }) diff --git a/src/components/PrimeUI/CreatePaymentRequestForm/CreatePaymentRequestForm.jsx b/src/components/PrimeUI/CreatePaymentRequestForm/CreatePaymentRequestForm.jsx index 410e65506f1..d5b3f283829 100644 --- a/src/components/PrimeUI/CreatePaymentRequestForm/CreatePaymentRequestForm.jsx +++ b/src/components/PrimeUI/CreatePaymentRequestForm/CreatePaymentRequestForm.jsx @@ -18,6 +18,7 @@ import ServiceItem from 'components/PrimeUI/ServiceItem/ServiceItem'; import Shipment from 'components/PrimeUI/Shipment/Shipment'; import { DatePickerInput } from 'components/form/fields'; import TextField from 'components/form/fields/TextField/TextField'; +import { SERVICE_ITEM_CODES } from 'constants/serviceItems'; const CreatePaymentRequestForm = ({ initialValues, @@ -128,27 +129,27 @@ const CreatePaymentRequestForm = ({ /> )} - {(mtoServiceItem.reServiceCode === 'DLH' || - mtoServiceItem.reServiceCode === 'DSH' || - mtoServiceItem.reServiceCode === 'FSC' || - mtoServiceItem.reServiceCode === 'DUPK' || - mtoServiceItem.reServiceCode === 'DNPK' || - mtoServiceItem.reServiceCode === 'DOFSIT' || - mtoServiceItem.reServiceCode === 'DOPSIT' || - mtoServiceItem.reServiceCode === 'DOSHUT' || - mtoServiceItem.reServiceCode === 'DDFSIT' || - mtoServiceItem.reServiceCode === 'DDDSIT' || - mtoServiceItem.reServiceCode === 'DOP' || - mtoServiceItem.reServiceCode === 'DDP' || - mtoServiceItem.reServiceCode === 'DPK' || - mtoServiceItem.reServiceCode === 'DDSFSC' || - mtoServiceItem.reServiceCode === 'DOSFSC' || - mtoServiceItem.reServiceCode === 'DDSHUT' || - mtoServiceItem.reServiceCode === 'IHPK' || - mtoServiceItem.reServiceCode === 'IHUPK' || - mtoServiceItem.reServiceCode === 'ISLH' || - mtoServiceItem.reServiceCode === 'POEFSC' || - mtoServiceItem.reServiceCode === 'PODFSC') && ( + {(mtoServiceItem.reServiceCode === SERVICE_ITEM_CODES.DLH || + mtoServiceItem.reServiceCode === SERVICE_ITEM_CODES.DSH || + mtoServiceItem.reServiceCode === SERVICE_ITEM_CODES.FSC || + mtoServiceItem.reServiceCode === SERVICE_ITEM_CODES.DUPK || + mtoServiceItem.reServiceCode === SERVICE_ITEM_CODES.DNPK || + mtoServiceItem.reServiceCode === SERVICE_ITEM_CODES.DOFSIT || + mtoServiceItem.reServiceCode === SERVICE_ITEM_CODES.DOPSIT || + mtoServiceItem.reServiceCode === SERVICE_ITEM_CODES.DOSHUT || + mtoServiceItem.reServiceCode === SERVICE_ITEM_CODES.DDFSIT || + mtoServiceItem.reServiceCode === SERVICE_ITEM_CODES.DDDSIT || + mtoServiceItem.reServiceCode === SERVICE_ITEM_CODES.DOP || + mtoServiceItem.reServiceCode === SERVICE_ITEM_CODES.DDP || + mtoServiceItem.reServiceCode === SERVICE_ITEM_CODES.DPK || + mtoServiceItem.reServiceCode === SERVICE_ITEM_CODES.DDSFSC || + mtoServiceItem.reServiceCode === SERVICE_ITEM_CODES.DOSFSC || + mtoServiceItem.reServiceCode === SERVICE_ITEM_CODES.DDSHUT || + mtoServiceItem.reServiceCode === SERVICE_ITEM_CODES.IHPK || + mtoServiceItem.reServiceCode === SERVICE_ITEM_CODES.IHUPK || + mtoServiceItem.reServiceCode === SERVICE_ITEM_CODES.ISLH || + mtoServiceItem.reServiceCode === SERVICE_ITEM_CODES.POEFSC || + mtoServiceItem.reServiceCode === SERVICE_ITEM_CODES.PODFSC) && ( Date: Fri, 3 Jan 2025 20:21:47 +0000 Subject: [PATCH 12/14] mocks again --- .../mocks/IntlPortFuelSurchargePricer.go | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/pkg/services/mocks/IntlPortFuelSurchargePricer.go b/pkg/services/mocks/IntlPortFuelSurchargePricer.go index 48e396d017e..1780857c419 100644 --- a/pkg/services/mocks/IntlPortFuelSurchargePricer.go +++ b/pkg/services/mocks/IntlPortFuelSurchargePricer.go @@ -20,9 +20,9 @@ type IntlPortFuelSurchargePricer struct { mock.Mock } -// Price provides a mock function with given fields: appCtx, actualPickupDate, distance, weight, fscWeightBasedDistanceMultiplier, eiaFuelPrice, portName -func (_m *IntlPortFuelSurchargePricer) Price(appCtx appcontext.AppContext, actualPickupDate time.Time, distance unit.Miles, weight unit.Pound, fscWeightBasedDistanceMultiplier float64, eiaFuelPrice unit.Millicents, portName string) (unit.Cents, services.PricingDisplayParams, error) { - ret := _m.Called(appCtx, actualPickupDate, distance, weight, fscWeightBasedDistanceMultiplier, eiaFuelPrice, portName) +// Price provides a mock function with given fields: appCtx, actualPickupDate, distance, weight, fscWeightBasedDistanceMultiplier, eiaFuelPrice +func (_m *IntlPortFuelSurchargePricer) Price(appCtx appcontext.AppContext, actualPickupDate time.Time, distance unit.Miles, weight unit.Pound, fscWeightBasedDistanceMultiplier float64, eiaFuelPrice unit.Millicents) (unit.Cents, services.PricingDisplayParams, error) { + ret := _m.Called(appCtx, actualPickupDate, distance, weight, fscWeightBasedDistanceMultiplier, eiaFuelPrice) if len(ret) == 0 { panic("no return value specified for Price") @@ -31,25 +31,25 @@ func (_m *IntlPortFuelSurchargePricer) Price(appCtx appcontext.AppContext, actua var r0 unit.Cents var r1 services.PricingDisplayParams var r2 error - if rf, ok := ret.Get(0).(func(appcontext.AppContext, time.Time, unit.Miles, unit.Pound, float64, unit.Millicents, string) (unit.Cents, services.PricingDisplayParams, error)); ok { - return rf(appCtx, actualPickupDate, distance, weight, fscWeightBasedDistanceMultiplier, eiaFuelPrice, portName) + if rf, ok := ret.Get(0).(func(appcontext.AppContext, time.Time, unit.Miles, unit.Pound, float64, unit.Millicents) (unit.Cents, services.PricingDisplayParams, error)); ok { + return rf(appCtx, actualPickupDate, distance, weight, fscWeightBasedDistanceMultiplier, eiaFuelPrice) } - if rf, ok := ret.Get(0).(func(appcontext.AppContext, time.Time, unit.Miles, unit.Pound, float64, unit.Millicents, string) unit.Cents); ok { - r0 = rf(appCtx, actualPickupDate, distance, weight, fscWeightBasedDistanceMultiplier, eiaFuelPrice, portName) + if rf, ok := ret.Get(0).(func(appcontext.AppContext, time.Time, unit.Miles, unit.Pound, float64, unit.Millicents) unit.Cents); ok { + r0 = rf(appCtx, actualPickupDate, distance, weight, fscWeightBasedDistanceMultiplier, eiaFuelPrice) } else { r0 = ret.Get(0).(unit.Cents) } - if rf, ok := ret.Get(1).(func(appcontext.AppContext, time.Time, unit.Miles, unit.Pound, float64, unit.Millicents, string) services.PricingDisplayParams); ok { - r1 = rf(appCtx, actualPickupDate, distance, weight, fscWeightBasedDistanceMultiplier, eiaFuelPrice, portName) + if rf, ok := ret.Get(1).(func(appcontext.AppContext, time.Time, unit.Miles, unit.Pound, float64, unit.Millicents) services.PricingDisplayParams); ok { + r1 = rf(appCtx, actualPickupDate, distance, weight, fscWeightBasedDistanceMultiplier, eiaFuelPrice) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(services.PricingDisplayParams) } } - if rf, ok := ret.Get(2).(func(appcontext.AppContext, time.Time, unit.Miles, unit.Pound, float64, unit.Millicents, string) error); ok { - r2 = rf(appCtx, actualPickupDate, distance, weight, fscWeightBasedDistanceMultiplier, eiaFuelPrice, portName) + if rf, ok := ret.Get(2).(func(appcontext.AppContext, time.Time, unit.Miles, unit.Pound, float64, unit.Millicents) error); ok { + r2 = rf(appCtx, actualPickupDate, distance, weight, fscWeightBasedDistanceMultiplier, eiaFuelPrice) } else { r2 = ret.Error(2) } From 114ee8e5c6974ec5c6bb2dce739b8eba00ad89d3 Mon Sep 17 00:00:00 2001 From: Daniel Jordan Date: Fri, 3 Jan 2025 21:49:10 +0000 Subject: [PATCH 13/14] updating prime docs --- pkg/gen/primeapi/embedded_spec.go | 4 +- .../payment_request/create_payment_request.go | 201 ++++++++++++++- .../payment_request/payment_request_client.go | 201 ++++++++++++++- swagger-def/prime.yaml | 171 ++++++++++++- swagger/prime.yaml | 233 ++++++++++++++++-- 5 files changed, 757 insertions(+), 53 deletions(-) diff --git a/pkg/gen/primeapi/embedded_spec.go b/pkg/gen/primeapi/embedded_spec.go index 52039d783cc..69ed189d17f 100644 --- a/pkg/gen/primeapi/embedded_spec.go +++ b/pkg/gen/primeapi/embedded_spec.go @@ -1063,7 +1063,7 @@ func init() { }, "/payment-requests": { "post": { - "description": "Creates a new instance of a paymentRequest and is assigned the status ` + "`" + `PENDING` + "`" + `.\nA move task order can have multiple payment requests, and\na final payment request can be marked using boolean ` + "`" + `isFinal` + "`" + `.\n\nIf a ` + "`" + `PENDING` + "`" + ` payment request is recalculated,\na new payment request is created and the original request is\nmarked with the status ` + "`" + `DEPRECATED` + "`" + `.\n\n**NOTE**: In order to create a payment request for most service items, the shipment *must*\nbe updated with the ` + "`" + `PrimeActualWeight` + "`" + ` value via [updateMTOShipment](#operation/updateMTOShipment).\n\n**FSC - Fuel Surcharge** service items require ` + "`" + `ActualPickupDate` + "`" + ` to be updated on the shipment.\n\nA service item can be on several payment requests in the case of partial payment requests and payments.\n\nIn the request, if no params are necessary, then just the ` + "`" + `serviceItem` + "`" + ` ` + "`" + `id` + "`" + ` is required. For example:\n` + "`" + `` + "`" + `` + "`" + `json\n{\n \"isFinal\": false,\n \"moveTaskOrderID\": \"uuid\",\n \"serviceItems\": [\n {\n \"id\": \"uuid\",\n },\n {\n \"id\": \"uuid\",\n \"params\": [\n {\n \"key\": \"Service Item Parameter Name\",\n \"value\": \"Service Item Parameter Value\"\n }\n ]\n }\n ],\n \"pointOfContact\": \"string\"\n}\n` + "`" + `` + "`" + `` + "`" + `\n\nSIT Service Items \u0026 Accepted Payment Request Parameters:\n---\nIf ` + "`" + `WeightBilled` + "`" + ` is not provided then the full shipment weight (` + "`" + `PrimeActualWeight` + "`" + `) will be considered in the calculation.\n\n**NOTE**: Diversions have a unique calcuation for payment requests without a ` + "`" + `WeightBilled` + "`" + ` parameter.\n\nIf you created a payment request for a diversion and ` + "`" + `WeightBilled` + "`" + ` is not provided, then the following will be used in the calculation:\n- The lowest shipment weight (` + "`" + `PrimeActualWeight` + "`" + `) found in the diverted shipment chain.\n- The lowest reweigh weight found in the diverted shipment chain.\n\nThe diverted shipment chain is created by referencing the ` + "`" + `diversion` + "`" + ` boolean, ` + "`" + `divertedFromShipmentId` + "`" + ` UUID, and matching destination to pickup addresses.\nIf the chain cannot be established it will fall back to the ` + "`" + `PrimeActualWeight` + "`" + ` of the current shipment. This is utilized because diverted shipments are all one single shipment, but going to different locations.\nThe lowest weight found is the true shipment weight, and thus we search the chain of shipments for the lowest weight found.\n\n**DOFSIT - Domestic origin 1st day SIT**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DOASIT - Domestic origin add'l SIT** *(SITPaymentRequestStart \u0026 SITPaymentRequestEnd are **REQUIRED**)*\n*To create a paymentRequest for this service item, the ` + "`" + `SITPaymentRequestStart` + "`" + ` and ` + "`" + `SITPaymentRequestEnd` + "`" + ` dates must not overlap previously requested SIT dates.*\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n },\n {\n \"key\": \"SITPaymentRequestStart\",\n \"value\": \"date\"\n },\n {\n \"key\": \"SITPaymentRequestEnd\",\n \"value\": \"date\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DOPSIT - Domestic origin SIT pickup**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DOSHUT - Domestic origin shuttle service**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDFSIT - Domestic destination 1st day SIT**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDASIT - Domestic destination add'l SIT** *(SITPaymentRequestStart \u0026 SITPaymentRequestEnd are **REQUIRED**)*\n*To create a paymentRequest for this service item, the ` + "`" + `SITPaymentRequestStart` + "`" + ` and ` + "`" + `SITPaymentRequestEnd` + "`" + ` dates must not overlap previously requested SIT dates.*\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n },\n {\n \"key\": \"SITPaymentRequestStart\",\n \"value\": \"date\"\n },\n {\n \"key\": \"SITPaymentRequestEnd\",\n \"value\": \"date\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDDSIT - Domestic destination SIT delivery**\n*To create a paymentRequest for this service item, it must first have a final address set via [updateMTOServiceItem](#operation/updateMTOServiceItem).*\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDSHUT - Domestic destination shuttle service**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n---\n", + "description": "Creates a new instance of a paymentRequest and is assigned the status ` + "`" + `PENDING` + "`" + `.\nA move task order can have multiple payment requests, and\na final payment request can be marked using boolean ` + "`" + `isFinal` + "`" + `.\n\nIf a ` + "`" + `PENDING` + "`" + ` payment request is recalculated,\na new payment request is created and the original request is\nmarked with the status ` + "`" + `DEPRECATED` + "`" + `.\n\n**NOTE**: In order to create a payment request for most service items, the shipment *must*\nbe updated with the ` + "`" + `PrimeActualWeight` + "`" + ` value via [updateMTOShipment](#operation/updateMTOShipment).\n\nIf ` + "`" + `WeightBilled` + "`" + ` is not provided then the full shipment weight (` + "`" + `PrimeActualWeight` + "`" + `) will be considered in the calculation.\n\n**NOTE**: Diversions have a unique calcuation for payment requests without a ` + "`" + `WeightBilled` + "`" + ` parameter.\n\nIf you created a payment request for a diversion and ` + "`" + `WeightBilled` + "`" + ` is not provided, then the following will be used in the calculation:\n- The lowest shipment weight (` + "`" + `PrimeActualWeight` + "`" + `) found in the diverted shipment chain.\n- The lowest reweigh weight found in the diverted shipment chain.\n\nThe diverted shipment chain is created by referencing the ` + "`" + `diversion` + "`" + ` boolean, ` + "`" + `divertedFromShipmentId` + "`" + ` UUID, and matching destination to pickup addresses.\nIf the chain cannot be established it will fall back to the ` + "`" + `PrimeActualWeight` + "`" + ` of the current shipment. This is utilized because diverted shipments are all one single shipment, but going to different locations.\nThe lowest weight found is the true shipment weight, and thus we search the chain of shipments for the lowest weight found.\n\nA service item can be on several payment requests in the case of partial payment requests and payments.\n\nIn the request, if no params are necessary, then just the ` + "`" + `serviceItem` + "`" + ` ` + "`" + `id` + "`" + ` is required. For example:\n` + "`" + `` + "`" + `` + "`" + `json\n{\n \"isFinal\": false,\n \"moveTaskOrderID\": \"uuid\",\n \"serviceItems\": [\n {\n \"id\": \"uuid\",\n },\n {\n \"id\": \"uuid\",\n \"params\": [\n {\n \"key\": \"Service Item Parameter Name\",\n \"value\": \"Service Item Parameter Value\"\n }\n ]\n }\n ],\n \"pointOfContact\": \"string\"\n}\n` + "`" + `` + "`" + `` + "`" + `\n\nDomestic Basic Service Items \u0026 Accepted Payment Request Parameters:\n---\n\n**DLH - Domestic Linehaul**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DSH - Domestic Shorthaul**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**FSC - Fuel Surcharge**\n**NOTE**: FSC requires ` + "`" + `ActualPickupDate` + "`" + ` to be updated on the shipment.\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DUPK - Domestic Unpacking**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DPK - Domestic Packing**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DNPK - Domestic NTS Packing**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DPK - Domestic Packing**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DOP - Domestic Origin Price**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDP - Domestic Destination Price**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\nDomestic SIT Service Items \u0026 Accepted Payment Request Parameters:\n---\n\n**DOFSIT - Domestic origin 1st day SIT**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DOASIT - Domestic origin add'l SIT** *(SITPaymentRequestStart \u0026 SITPaymentRequestEnd are **REQUIRED**)*\n*To create a paymentRequest for this service item, the ` + "`" + `SITPaymentRequestStart` + "`" + ` and ` + "`" + `SITPaymentRequestEnd` + "`" + ` dates must not overlap previously requested SIT dates.*\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n },\n {\n \"key\": \"SITPaymentRequestStart\",\n \"value\": \"date\"\n },\n {\n \"key\": \"SITPaymentRequestEnd\",\n \"value\": \"date\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DOPSIT - Domestic origin SIT pickup**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DOSHUT - Domestic origin shuttle service**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDFSIT - Domestic destination 1st day SIT**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDASIT - Domestic destination add'l SIT** *(SITPaymentRequestStart \u0026 SITPaymentRequestEnd are **REQUIRED**)*\n*To create a paymentRequest for this service item, the ` + "`" + `SITPaymentRequestStart` + "`" + ` and ` + "`" + `SITPaymentRequestEnd` + "`" + ` dates must not overlap previously requested SIT dates.*\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n },\n {\n \"key\": \"SITPaymentRequestStart\",\n \"value\": \"date\"\n },\n {\n \"key\": \"SITPaymentRequestEnd\",\n \"value\": \"date\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDDSIT - Domestic destination SIT delivery**\n*To create a paymentRequest for this service item, it must first have a final address set via [updateMTOServiceItem](#operation/updateMTOServiceItem).*\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDSHUT - Domestic destination shuttle service**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n---\n\nInternational Basic Service Items \u0026 Accepted Payment Request Parameters:\n---\nJust like domestic shipments \u0026 service items, if ` + "`" + `WeightBilled` + "`" + ` is not provided then the full shipment weight (` + "`" + `PrimeActualWeight` + "`" + `) will be considered in the calculation.\n**NOTE**: ` + "`" + `POEFSC` + "`" + ` \u0026 ` + "`" + `PODFSC` + "`" + ` service items must have a port associated on the service item in order to successfully add it to a payment request. To update the port of a service item, you must use the (#operation/updateMTOServiceItem) endpoint.\n\n**ISLH - International Shipping \u0026 Linehaul**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**IHPK - International HHG Pack**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**IHUPK - International HHG Unpack**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**POEFSC - International Port of Embarkation Fuel Surcharge**\n **NOTE**: POEFSC requires ` + "`" + `ActualPickupDate` + "`" + ` to be updated on the shipment \u0026 ` + "`" + `POELocation` + "`" + ` on the service item.\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**PODFSC - International Port of Debarkation Fuel Surcharge**\n**NOTE**: PODFSC requires ` + "`" + `ActualPickupDate` + "`" + ` to be updated on the shipment \u0026 ` + "`" + `PODLocation` + "`" + ` on the service item.\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n---\n", "consumes": [ "application/json" ], @@ -5921,7 +5921,7 @@ func init() { }, "/payment-requests": { "post": { - "description": "Creates a new instance of a paymentRequest and is assigned the status ` + "`" + `PENDING` + "`" + `.\nA move task order can have multiple payment requests, and\na final payment request can be marked using boolean ` + "`" + `isFinal` + "`" + `.\n\nIf a ` + "`" + `PENDING` + "`" + ` payment request is recalculated,\na new payment request is created and the original request is\nmarked with the status ` + "`" + `DEPRECATED` + "`" + `.\n\n**NOTE**: In order to create a payment request for most service items, the shipment *must*\nbe updated with the ` + "`" + `PrimeActualWeight` + "`" + ` value via [updateMTOShipment](#operation/updateMTOShipment).\n\n**FSC - Fuel Surcharge** service items require ` + "`" + `ActualPickupDate` + "`" + ` to be updated on the shipment.\n\nA service item can be on several payment requests in the case of partial payment requests and payments.\n\nIn the request, if no params are necessary, then just the ` + "`" + `serviceItem` + "`" + ` ` + "`" + `id` + "`" + ` is required. For example:\n` + "`" + `` + "`" + `` + "`" + `json\n{\n \"isFinal\": false,\n \"moveTaskOrderID\": \"uuid\",\n \"serviceItems\": [\n {\n \"id\": \"uuid\",\n },\n {\n \"id\": \"uuid\",\n \"params\": [\n {\n \"key\": \"Service Item Parameter Name\",\n \"value\": \"Service Item Parameter Value\"\n }\n ]\n }\n ],\n \"pointOfContact\": \"string\"\n}\n` + "`" + `` + "`" + `` + "`" + `\n\nSIT Service Items \u0026 Accepted Payment Request Parameters:\n---\nIf ` + "`" + `WeightBilled` + "`" + ` is not provided then the full shipment weight (` + "`" + `PrimeActualWeight` + "`" + `) will be considered in the calculation.\n\n**NOTE**: Diversions have a unique calcuation for payment requests without a ` + "`" + `WeightBilled` + "`" + ` parameter.\n\nIf you created a payment request for a diversion and ` + "`" + `WeightBilled` + "`" + ` is not provided, then the following will be used in the calculation:\n- The lowest shipment weight (` + "`" + `PrimeActualWeight` + "`" + `) found in the diverted shipment chain.\n- The lowest reweigh weight found in the diverted shipment chain.\n\nThe diverted shipment chain is created by referencing the ` + "`" + `diversion` + "`" + ` boolean, ` + "`" + `divertedFromShipmentId` + "`" + ` UUID, and matching destination to pickup addresses.\nIf the chain cannot be established it will fall back to the ` + "`" + `PrimeActualWeight` + "`" + ` of the current shipment. This is utilized because diverted shipments are all one single shipment, but going to different locations.\nThe lowest weight found is the true shipment weight, and thus we search the chain of shipments for the lowest weight found.\n\n**DOFSIT - Domestic origin 1st day SIT**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DOASIT - Domestic origin add'l SIT** *(SITPaymentRequestStart \u0026 SITPaymentRequestEnd are **REQUIRED**)*\n*To create a paymentRequest for this service item, the ` + "`" + `SITPaymentRequestStart` + "`" + ` and ` + "`" + `SITPaymentRequestEnd` + "`" + ` dates must not overlap previously requested SIT dates.*\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n },\n {\n \"key\": \"SITPaymentRequestStart\",\n \"value\": \"date\"\n },\n {\n \"key\": \"SITPaymentRequestEnd\",\n \"value\": \"date\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DOPSIT - Domestic origin SIT pickup**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DOSHUT - Domestic origin shuttle service**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDFSIT - Domestic destination 1st day SIT**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDASIT - Domestic destination add'l SIT** *(SITPaymentRequestStart \u0026 SITPaymentRequestEnd are **REQUIRED**)*\n*To create a paymentRequest for this service item, the ` + "`" + `SITPaymentRequestStart` + "`" + ` and ` + "`" + `SITPaymentRequestEnd` + "`" + ` dates must not overlap previously requested SIT dates.*\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n },\n {\n \"key\": \"SITPaymentRequestStart\",\n \"value\": \"date\"\n },\n {\n \"key\": \"SITPaymentRequestEnd\",\n \"value\": \"date\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDDSIT - Domestic destination SIT delivery**\n*To create a paymentRequest for this service item, it must first have a final address set via [updateMTOServiceItem](#operation/updateMTOServiceItem).*\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDSHUT - Domestic destination shuttle service**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n---\n", + "description": "Creates a new instance of a paymentRequest and is assigned the status ` + "`" + `PENDING` + "`" + `.\nA move task order can have multiple payment requests, and\na final payment request can be marked using boolean ` + "`" + `isFinal` + "`" + `.\n\nIf a ` + "`" + `PENDING` + "`" + ` payment request is recalculated,\na new payment request is created and the original request is\nmarked with the status ` + "`" + `DEPRECATED` + "`" + `.\n\n**NOTE**: In order to create a payment request for most service items, the shipment *must*\nbe updated with the ` + "`" + `PrimeActualWeight` + "`" + ` value via [updateMTOShipment](#operation/updateMTOShipment).\n\nIf ` + "`" + `WeightBilled` + "`" + ` is not provided then the full shipment weight (` + "`" + `PrimeActualWeight` + "`" + `) will be considered in the calculation.\n\n**NOTE**: Diversions have a unique calcuation for payment requests without a ` + "`" + `WeightBilled` + "`" + ` parameter.\n\nIf you created a payment request for a diversion and ` + "`" + `WeightBilled` + "`" + ` is not provided, then the following will be used in the calculation:\n- The lowest shipment weight (` + "`" + `PrimeActualWeight` + "`" + `) found in the diverted shipment chain.\n- The lowest reweigh weight found in the diverted shipment chain.\n\nThe diverted shipment chain is created by referencing the ` + "`" + `diversion` + "`" + ` boolean, ` + "`" + `divertedFromShipmentId` + "`" + ` UUID, and matching destination to pickup addresses.\nIf the chain cannot be established it will fall back to the ` + "`" + `PrimeActualWeight` + "`" + ` of the current shipment. This is utilized because diverted shipments are all one single shipment, but going to different locations.\nThe lowest weight found is the true shipment weight, and thus we search the chain of shipments for the lowest weight found.\n\nA service item can be on several payment requests in the case of partial payment requests and payments.\n\nIn the request, if no params are necessary, then just the ` + "`" + `serviceItem` + "`" + ` ` + "`" + `id` + "`" + ` is required. For example:\n` + "`" + `` + "`" + `` + "`" + `json\n{\n \"isFinal\": false,\n \"moveTaskOrderID\": \"uuid\",\n \"serviceItems\": [\n {\n \"id\": \"uuid\",\n },\n {\n \"id\": \"uuid\",\n \"params\": [\n {\n \"key\": \"Service Item Parameter Name\",\n \"value\": \"Service Item Parameter Value\"\n }\n ]\n }\n ],\n \"pointOfContact\": \"string\"\n}\n` + "`" + `` + "`" + `` + "`" + `\n\nDomestic Basic Service Items \u0026 Accepted Payment Request Parameters:\n---\n\n**DLH - Domestic Linehaul**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DSH - Domestic Shorthaul**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**FSC - Fuel Surcharge**\n**NOTE**: FSC requires ` + "`" + `ActualPickupDate` + "`" + ` to be updated on the shipment.\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DUPK - Domestic Unpacking**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DPK - Domestic Packing**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DNPK - Domestic NTS Packing**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DPK - Domestic Packing**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DOP - Domestic Origin Price**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDP - Domestic Destination Price**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\nDomestic SIT Service Items \u0026 Accepted Payment Request Parameters:\n---\n\n**DOFSIT - Domestic origin 1st day SIT**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DOASIT - Domestic origin add'l SIT** *(SITPaymentRequestStart \u0026 SITPaymentRequestEnd are **REQUIRED**)*\n*To create a paymentRequest for this service item, the ` + "`" + `SITPaymentRequestStart` + "`" + ` and ` + "`" + `SITPaymentRequestEnd` + "`" + ` dates must not overlap previously requested SIT dates.*\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n },\n {\n \"key\": \"SITPaymentRequestStart\",\n \"value\": \"date\"\n },\n {\n \"key\": \"SITPaymentRequestEnd\",\n \"value\": \"date\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DOPSIT - Domestic origin SIT pickup**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DOSHUT - Domestic origin shuttle service**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDFSIT - Domestic destination 1st day SIT**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDASIT - Domestic destination add'l SIT** *(SITPaymentRequestStart \u0026 SITPaymentRequestEnd are **REQUIRED**)*\n*To create a paymentRequest for this service item, the ` + "`" + `SITPaymentRequestStart` + "`" + ` and ` + "`" + `SITPaymentRequestEnd` + "`" + ` dates must not overlap previously requested SIT dates.*\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n },\n {\n \"key\": \"SITPaymentRequestStart\",\n \"value\": \"date\"\n },\n {\n \"key\": \"SITPaymentRequestEnd\",\n \"value\": \"date\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDDSIT - Domestic destination SIT delivery**\n*To create a paymentRequest for this service item, it must first have a final address set via [updateMTOServiceItem](#operation/updateMTOServiceItem).*\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDSHUT - Domestic destination shuttle service**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n---\n\nInternational Basic Service Items \u0026 Accepted Payment Request Parameters:\n---\nJust like domestic shipments \u0026 service items, if ` + "`" + `WeightBilled` + "`" + ` is not provided then the full shipment weight (` + "`" + `PrimeActualWeight` + "`" + `) will be considered in the calculation.\n**NOTE**: ` + "`" + `POEFSC` + "`" + ` \u0026 ` + "`" + `PODFSC` + "`" + ` service items must have a port associated on the service item in order to successfully add it to a payment request. To update the port of a service item, you must use the (#operation/updateMTOServiceItem) endpoint.\n\n**ISLH - International Shipping \u0026 Linehaul**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**IHPK - International HHG Pack**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**IHUPK - International HHG Unpack**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**POEFSC - International Port of Embarkation Fuel Surcharge**\n **NOTE**: POEFSC requires ` + "`" + `ActualPickupDate` + "`" + ` to be updated on the shipment \u0026 ` + "`" + `POELocation` + "`" + ` on the service item.\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**PODFSC - International Port of Debarkation Fuel Surcharge**\n**NOTE**: PODFSC requires ` + "`" + `ActualPickupDate` + "`" + ` to be updated on the shipment \u0026 ` + "`" + `PODLocation` + "`" + ` on the service item.\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n---\n", "consumes": [ "application/json" ], diff --git a/pkg/gen/primeapi/primeoperations/payment_request/create_payment_request.go b/pkg/gen/primeapi/primeoperations/payment_request/create_payment_request.go index 1e98d93abe7..d1ca6a38ab8 100644 --- a/pkg/gen/primeapi/primeoperations/payment_request/create_payment_request.go +++ b/pkg/gen/primeapi/primeoperations/payment_request/create_payment_request.go @@ -45,7 +45,17 @@ marked with the status `DEPRECATED`. **NOTE**: In order to create a payment request for most service items, the shipment *must* be updated with the `PrimeActualWeight` value via [updateMTOShipment](#operation/updateMTOShipment). -**FSC - Fuel Surcharge** service items require `ActualPickupDate` to be updated on the shipment. +If `WeightBilled` is not provided then the full shipment weight (`PrimeActualWeight`) will be considered in the calculation. + +**NOTE**: Diversions have a unique calcuation for payment requests without a `WeightBilled` parameter. + +If you created a payment request for a diversion and `WeightBilled` is not provided, then the following will be used in the calculation: +- The lowest shipment weight (`PrimeActualWeight`) found in the diverted shipment chain. +- The lowest reweigh weight found in the diverted shipment chain. + +The diverted shipment chain is created by referencing the `diversion` boolean, `divertedFromShipmentId` UUID, and matching destination to pickup addresses. +If the chain cannot be established it will fall back to the `PrimeActualWeight` of the current shipment. This is utilized because diverted shipments are all one single shipment, but going to different locations. +The lowest weight found is the true shipment weight, and thus we search the chain of shipments for the lowest weight found. A service item can be on several payment requests in the case of partial payment requests and payments. @@ -74,19 +84,120 @@ In the request, if no params are necessary, then just the `serviceItem` `id` is ``` -SIT Service Items & Accepted Payment Request Parameters: +Domestic Basic Service Items & Accepted Payment Request Parameters: --- -If `WeightBilled` is not provided then the full shipment weight (`PrimeActualWeight`) will be considered in the calculation. -**NOTE**: Diversions have a unique calcuation for payment requests without a `WeightBilled` parameter. +**DLH - Domestic Linehaul** +```json -If you created a payment request for a diversion and `WeightBilled` is not provided, then the following will be used in the calculation: -- The lowest shipment weight (`PrimeActualWeight`) found in the diverted shipment chain. -- The lowest reweigh weight found in the diverted shipment chain. + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] -The diverted shipment chain is created by referencing the `diversion` boolean, `divertedFromShipmentId` UUID, and matching destination to pickup addresses. -If the chain cannot be established it will fall back to the `PrimeActualWeight` of the current shipment. This is utilized because diverted shipments are all one single shipment, but going to different locations. -The lowest weight found is the true shipment weight, and thus we search the chain of shipments for the lowest weight found. +``` + +**DSH - Domestic Shorthaul** +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + +``` + +**FSC - Fuel Surcharge** +**NOTE**: FSC requires `ActualPickupDate` to be updated on the shipment. +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + +``` + +**DUPK - Domestic Unpacking** +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + +``` + +**DPK - Domestic Packing** +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + +``` + +**DNPK - Domestic NTS Packing** +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + +``` + +**DPK - Domestic Packing** +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + +``` + +**DOP - Domestic Origin Price** +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + +``` + +**DDP - Domestic Destination Price** +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + +``` + +Domestic SIT Service Items & Accepted Payment Request Parameters: +--- **DOFSIT - Domestic origin 1st day SIT** ```json @@ -201,6 +312,76 @@ The lowest weight found is the true shipment weight, and thus we search the chai } ] +``` +--- + +International Basic Service Items & Accepted Payment Request Parameters: +--- +Just like domestic shipments & service items, if `WeightBilled` is not provided then the full shipment weight (`PrimeActualWeight`) will be considered in the calculation. +**NOTE**: `POEFSC` & `PODFSC` service items must have a port associated on the service item in order to successfully add it to a payment request. To update the port of a service item, you must use the (#operation/updateMTOServiceItem) endpoint. + +**ISLH - International Shipping & Linehaul** +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + +``` + +**IHPK - International HHG Pack** +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + +``` + +**IHUPK - International HHG Unpack** +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + +``` + +**POEFSC - International Port of Embarkation Fuel Surcharge** + + **NOTE**: POEFSC requires `ActualPickupDate` to be updated on the shipment & `POELocation` on the service item. + +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + +``` + +**PODFSC - International Port of Debarkation Fuel Surcharge** +**NOTE**: PODFSC requires `ActualPickupDate` to be updated on the shipment & `PODLocation` on the service item. +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` --- */ diff --git a/pkg/gen/primeclient/payment_request/payment_request_client.go b/pkg/gen/primeclient/payment_request/payment_request_client.go index 82eae72610e..b10b46d87cf 100644 --- a/pkg/gen/primeclient/payment_request/payment_request_client.go +++ b/pkg/gen/primeclient/payment_request/payment_request_client.go @@ -52,7 +52,17 @@ marked with the status `DEPRECATED`. **NOTE**: In order to create a payment request for most service items, the shipment *must* be updated with the `PrimeActualWeight` value via [updateMTOShipment](#operation/updateMTOShipment). -**FSC - Fuel Surcharge** service items require `ActualPickupDate` to be updated on the shipment. +If `WeightBilled` is not provided then the full shipment weight (`PrimeActualWeight`) will be considered in the calculation. + +**NOTE**: Diversions have a unique calcuation for payment requests without a `WeightBilled` parameter. + +If you created a payment request for a diversion and `WeightBilled` is not provided, then the following will be used in the calculation: +- The lowest shipment weight (`PrimeActualWeight`) found in the diverted shipment chain. +- The lowest reweigh weight found in the diverted shipment chain. + +The diverted shipment chain is created by referencing the `diversion` boolean, `divertedFromShipmentId` UUID, and matching destination to pickup addresses. +If the chain cannot be established it will fall back to the `PrimeActualWeight` of the current shipment. This is utilized because diverted shipments are all one single shipment, but going to different locations. +The lowest weight found is the true shipment weight, and thus we search the chain of shipments for the lowest weight found. A service item can be on several payment requests in the case of partial payment requests and payments. @@ -81,19 +91,120 @@ In the request, if no params are necessary, then just the `serviceItem` `id` is ``` -SIT Service Items & Accepted Payment Request Parameters: +Domestic Basic Service Items & Accepted Payment Request Parameters: --- -If `WeightBilled` is not provided then the full shipment weight (`PrimeActualWeight`) will be considered in the calculation. -**NOTE**: Diversions have a unique calcuation for payment requests without a `WeightBilled` parameter. +**DLH - Domestic Linehaul** +```json -If you created a payment request for a diversion and `WeightBilled` is not provided, then the following will be used in the calculation: -- The lowest shipment weight (`PrimeActualWeight`) found in the diverted shipment chain. -- The lowest reweigh weight found in the diverted shipment chain. + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] -The diverted shipment chain is created by referencing the `diversion` boolean, `divertedFromShipmentId` UUID, and matching destination to pickup addresses. -If the chain cannot be established it will fall back to the `PrimeActualWeight` of the current shipment. This is utilized because diverted shipments are all one single shipment, but going to different locations. -The lowest weight found is the true shipment weight, and thus we search the chain of shipments for the lowest weight found. +``` + +**DSH - Domestic Shorthaul** +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + +``` + +**FSC - Fuel Surcharge** +**NOTE**: FSC requires `ActualPickupDate` to be updated on the shipment. +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + +``` + +**DUPK - Domestic Unpacking** +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + +``` + +**DPK - Domestic Packing** +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + +``` + +**DNPK - Domestic NTS Packing** +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + +``` + +**DPK - Domestic Packing** +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + +``` + +**DOP - Domestic Origin Price** +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + +``` + +**DDP - Domestic Destination Price** +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + +``` + +Domestic SIT Service Items & Accepted Payment Request Parameters: +--- **DOFSIT - Domestic origin 1st day SIT** ```json @@ -208,6 +319,76 @@ The lowest weight found is the true shipment weight, and thus we search the chai } ] +``` +--- + +International Basic Service Items & Accepted Payment Request Parameters: +--- +Just like domestic shipments & service items, if `WeightBilled` is not provided then the full shipment weight (`PrimeActualWeight`) will be considered in the calculation. +**NOTE**: `POEFSC` & `PODFSC` service items must have a port associated on the service item in order to successfully add it to a payment request. To update the port of a service item, you must use the (#operation/updateMTOServiceItem) endpoint. + +**ISLH - International Shipping & Linehaul** +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + +``` + +**IHPK - International HHG Pack** +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + +``` + +**IHUPK - International HHG Unpack** +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + +``` + +**POEFSC - International Port of Embarkation Fuel Surcharge** + + **NOTE**: POEFSC requires `ActualPickupDate` to be updated on the shipment & `POELocation` on the service item. + +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + +``` + +**PODFSC - International Port of Debarkation Fuel Surcharge** +**NOTE**: PODFSC requires `ActualPickupDate` to be updated on the shipment & `PODLocation` on the service item. +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` --- */ diff --git a/swagger-def/prime.yaml b/swagger-def/prime.yaml index 5fcb6cdd5e3..61f19f25a93 100644 --- a/swagger-def/prime.yaml +++ b/swagger-def/prime.yaml @@ -952,7 +952,17 @@ paths: **NOTE**: In order to create a payment request for most service items, the shipment *must* be updated with the `PrimeActualWeight` value via [updateMTOShipment](#operation/updateMTOShipment). - **FSC - Fuel Surcharge** service items require `ActualPickupDate` to be updated on the shipment. + If `WeightBilled` is not provided then the full shipment weight (`PrimeActualWeight`) will be considered in the calculation. + + **NOTE**: Diversions have a unique calcuation for payment requests without a `WeightBilled` parameter. + + If you created a payment request for a diversion and `WeightBilled` is not provided, then the following will be used in the calculation: + - The lowest shipment weight (`PrimeActualWeight`) found in the diverted shipment chain. + - The lowest reweigh weight found in the diverted shipment chain. + + The diverted shipment chain is created by referencing the `diversion` boolean, `divertedFromShipmentId` UUID, and matching destination to pickup addresses. + If the chain cannot be established it will fall back to the `PrimeActualWeight` of the current shipment. This is utilized because diverted shipments are all one single shipment, but going to different locations. + The lowest weight found is the true shipment weight, and thus we search the chain of shipments for the lowest weight found. A service item can be on several payment requests in the case of partial payment requests and payments. @@ -979,19 +989,102 @@ paths: } ``` - SIT Service Items & Accepted Payment Request Parameters: + Domestic Basic Service Items & Accepted Payment Request Parameters: --- - If `WeightBilled` is not provided then the full shipment weight (`PrimeActualWeight`) will be considered in the calculation. - **NOTE**: Diversions have a unique calcuation for payment requests without a `WeightBilled` parameter. + **DLH - Domestic Linehaul** + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` - If you created a payment request for a diversion and `WeightBilled` is not provided, then the following will be used in the calculation: - - The lowest shipment weight (`PrimeActualWeight`) found in the diverted shipment chain. - - The lowest reweigh weight found in the diverted shipment chain. + **DSH - Domestic Shorthaul** + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` - The diverted shipment chain is created by referencing the `diversion` boolean, `divertedFromShipmentId` UUID, and matching destination to pickup addresses. - If the chain cannot be established it will fall back to the `PrimeActualWeight` of the current shipment. This is utilized because diverted shipments are all one single shipment, but going to different locations. - The lowest weight found is the true shipment weight, and thus we search the chain of shipments for the lowest weight found. + **FSC - Fuel Surcharge** + **NOTE**: FSC requires `ActualPickupDate` to be updated on the shipment. + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` + + **DUPK - Domestic Unpacking** + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` + + **DPK - Domestic Packing** + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` + + **DNPK - Domestic NTS Packing** + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` + + **DPK - Domestic Packing** + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` + + **DOP - Domestic Origin Price** + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` + + **DDP - Domestic Destination Price** + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` + + Domestic SIT Service Items & Accepted Payment Request Parameters: + --- **DOFSIT - Domestic origin 1st day SIT** ```json @@ -1092,6 +1185,64 @@ paths: ] ``` --- + + International Basic Service Items & Accepted Payment Request Parameters: + --- + Just like domestic shipments & service items, if `WeightBilled` is not provided then the full shipment weight (`PrimeActualWeight`) will be considered in the calculation. + **NOTE**: `POEFSC` & `PODFSC` service items must have a port associated on the service item in order to successfully add it to a payment request. To update the port of a service item, you must use the (#operation/updateMTOServiceItem) endpoint. + + **ISLH - International Shipping & Linehaul** + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` + + **IHPK - International HHG Pack** + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` + + **IHUPK - International HHG Unpack** + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` + + **POEFSC - International Port of Embarkation Fuel Surcharge** + **NOTE**: POEFSC requires `ActualPickupDate` to be updated on the shipment & `POELocation` on the service item. + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` + + **PODFSC - International Port of Debarkation Fuel Surcharge** + **NOTE**: PODFSC requires `ActualPickupDate` to be updated on the shipment & `PODLocation` on the service item. + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` + --- operationId: createPaymentRequest tags: - paymentRequest diff --git a/swagger/prime.yaml b/swagger/prime.yaml index 83ef52b06c7..2f7082e307b 100644 --- a/swagger/prime.yaml +++ b/swagger/prime.yaml @@ -1214,8 +1214,34 @@ paths: [updateMTOShipment](#operation/updateMTOShipment). - **FSC - Fuel Surcharge** service items require `ActualPickupDate` to be - updated on the shipment. + If `WeightBilled` is not provided then the full shipment weight + (`PrimeActualWeight`) will be considered in the calculation. + + + **NOTE**: Diversions have a unique calcuation for payment requests + without a `WeightBilled` parameter. + + + If you created a payment request for a diversion and `WeightBilled` is + not provided, then the following will be used in the calculation: + + - The lowest shipment weight (`PrimeActualWeight`) found in the diverted + shipment chain. + + - The lowest reweigh weight found in the diverted shipment chain. + + + The diverted shipment chain is created by referencing the `diversion` + boolean, `divertedFromShipmentId` UUID, and matching destination to + pickup addresses. + + If the chain cannot be established it will fall back to the + `PrimeActualWeight` of the current shipment. This is utilized because + diverted shipments are all one single shipment, but going to different + locations. + + The lowest weight found is the true shipment weight, and thus we search + the chain of shipments for the lowest weight found. A service item can be on several payment requests in the case of partial @@ -1250,38 +1276,124 @@ paths: ``` - SIT Service Items & Accepted Payment Request Parameters: + Domestic Basic Service Items & Accepted Payment Request Parameters: --- - If `WeightBilled` is not provided then the full shipment weight - (`PrimeActualWeight`) will be considered in the calculation. + **DLH - Domestic Linehaul** - **NOTE**: Diversions have a unique calcuation for payment requests - without a `WeightBilled` parameter. + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` - If you created a payment request for a diversion and `WeightBilled` is - not provided, then the following will be used in the calculation: + **DSH - Domestic Shorthaul** - - The lowest shipment weight (`PrimeActualWeight`) found in the diverted - shipment chain. + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` - - The lowest reweigh weight found in the diverted shipment chain. + **FSC - Fuel Surcharge** - The diverted shipment chain is created by referencing the `diversion` - boolean, `divertedFromShipmentId` UUID, and matching destination to - pickup addresses. + **NOTE**: FSC requires `ActualPickupDate` to be updated on the shipment. - If the chain cannot be established it will fall back to the - `PrimeActualWeight` of the current shipment. This is utilized because - diverted shipments are all one single shipment, but going to different - locations. + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` + + + **DUPK - Domestic Unpacking** + + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` + + + **DPK - Domestic Packing** + + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` + + + **DNPK - Domestic NTS Packing** + + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` + + + **DPK - Domestic Packing** + + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` - The lowest weight found is the true shipment weight, and thus we search - the chain of shipments for the lowest weight found. + + **DOP - Domestic Origin Price** + + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` + + + **DDP - Domestic Destination Price** + + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` + + + Domestic SIT Service Items & Accepted Payment Request Parameters: + + --- **DOFSIT - Domestic origin 1st day SIT** @@ -1410,6 +1522,85 @@ paths: ``` --- + + + International Basic Service Items & Accepted Payment Request Parameters: + + --- + + Just like domestic shipments & service items, if `WeightBilled` is not + provided then the full shipment weight (`PrimeActualWeight`) will be + considered in the calculation. + + **NOTE**: `POEFSC` & `PODFSC` service items must have a port associated + on the service item in order to successfully add it to a payment + request. To update the port of a service item, you must use the + (#operation/updateMTOServiceItem) endpoint. + + + **ISLH - International Shipping & Linehaul** + + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` + + + **IHPK - International HHG Pack** + + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` + + + **IHUPK - International HHG Unpack** + + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` + + + **POEFSC - International Port of Embarkation Fuel Surcharge** + **NOTE**: POEFSC requires `ActualPickupDate` to be updated on the shipment & `POELocation` on the service item. + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` + + + **PODFSC - International Port of Debarkation Fuel Surcharge** + + **NOTE**: PODFSC requires `ActualPickupDate` to be updated on the + shipment & `PODLocation` on the service item. + + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` + + --- operationId: createPaymentRequest tags: - paymentRequest From 4890576da4e3179d2e4073491ef8ca81e2869374 Mon Sep 17 00:00:00 2001 From: Daniel Jordan Date: Mon, 6 Jan 2025 16:56:13 +0000 Subject: [PATCH 14/14] adding logic to use port ZIP for international shipments and ZIP lookups --- ...aram_values_to_service_params_table.up.sql | 2 +- pkg/models/service_item_param_key.go | 8 +-- .../distance_zip_lookup.go | 24 ++++++- .../distance_zip_lookup_test.go | 62 +++++++++++++++++++ ...port_name_lookup.go => port_zip_lookup.go} | 15 ++--- ...lookup_test.go => port_zip_lookup_test.go} | 18 +++--- .../service_param_value_lookups.go | 4 +- .../service_param_value_lookups_test.go | 8 +++ .../intl_port_fuel_surcharge_pricer.go | 2 +- .../intl_port_fuel_surcharge_pricer_test.go | 6 +- 10 files changed, 120 insertions(+), 29 deletions(-) rename pkg/payment_request/service_param_value_lookups/{port_name_lookup.go => port_zip_lookup.go} (50%) rename pkg/payment_request/service_param_value_lookups/{port_name_lookup_test.go => port_zip_lookup_test.go} (83%) diff --git a/migrations/app/schema/20241226173330_add_intl_param_values_to_service_params_table.up.sql b/migrations/app/schema/20241226173330_add_intl_param_values_to_service_params_table.up.sql index 859bbcb47b6..1186b75a7b2 100644 --- a/migrations/app/schema/20241226173330_add_intl_param_values_to_service_params_table.up.sql +++ b/migrations/app/schema/20241226173330_add_intl_param_values_to_service_params_table.up.sql @@ -3,7 +3,7 @@ DROP FUNCTION IF EXISTS get_rate_area_id(UUID, UUID); -- need to add in param keys for international shipments, this will be used to show breakdowns to the TIO INSERT INTO service_item_param_keys (id, key,description,type,origin,created_at,updated_at) VALUES - ('d9ad3878-4b94-4722-bbaf-d4b8080f339d','PortName','Name of the port for an international shipment','STRING','SYSTEM','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957'), + ('d9ad3878-4b94-4722-bbaf-d4b8080f339d','PortZip','ZIP of the port for an international shipment pickup or destination port','STRING','SYSTEM','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957'), ('597bb77e-0ce7-4ba2-9624-24300962625f','PerUnitCents','Per unit cents for a service item','INTEGER','SYSTEM','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957'); -- inserting params for PODFSC diff --git a/pkg/models/service_item_param_key.go b/pkg/models/service_item_param_key.go index 868c6785b55..0c637cc7d92 100644 --- a/pkg/models/service_item_param_key.go +++ b/pkg/models/service_item_param_key.go @@ -63,8 +63,8 @@ const ( ServiceItemParamNameNumberDaysSIT ServiceItemParamName = "NumberDaysSIT" // ServiceItemParamNamePerUnitCents is the param key name PerUnitCents ServiceItemParamNamePerUnitCents ServiceItemParamName = "PerUnitCents" - // ServiceItemParamNamePortName is the param key name PortName - ServiceItemParamNamePortName ServiceItemParamName = "PortName" + // ServiceItemParamNamePortZip is the param key name PortZip + ServiceItemParamNamePortZip ServiceItemParamName = "PortZip" // ServiceItemParamNamePriceAreaDest is the param key name PriceAreaDest ServiceItemParamNamePriceAreaDest ServiceItemParamName = "PriceAreaDest" // ServiceItemParamNamePriceAreaIntlDest is the param key name PriceAreaIntlDest @@ -280,7 +280,7 @@ var ValidServiceItemParamNames = []ServiceItemParamName{ ServiceItemParamNameUncappedRequestTotal, ServiceItemParamNameLockedPriceCents, ServiceItemParamNamePerUnitCents, - ServiceItemParamNamePortName, + ServiceItemParamNamePortZip, } // ValidServiceItemParamNameStrings lists all valid service item param key names @@ -356,7 +356,7 @@ var ValidServiceItemParamNameStrings = []string{ string(ServiceItemParamNameUncappedRequestTotal), string(ServiceItemParamNameLockedPriceCents), string(ServiceItemParamNamePerUnitCents), - string(ServiceItemParamNamePortName), + string(ServiceItemParamNamePortZip), } // ValidServiceItemParamTypes lists all valid service item param types diff --git a/pkg/payment_request/service_param_value_lookups/distance_zip_lookup.go b/pkg/payment_request/service_param_value_lookups/distance_zip_lookup.go index 7b70c559d1d..7f05aa59ee9 100644 --- a/pkg/payment_request/service_param_value_lookups/distance_zip_lookup.go +++ b/pkg/payment_request/service_param_value_lookups/distance_zip_lookup.go @@ -49,6 +49,25 @@ func (r DistanceZipLookup) lookup(appCtx appcontext.AppContext, keyData *Service // Now calculate the distance between zips pickupZip := r.PickupAddress.PostalCode destinationZip := r.DestinationAddress.PostalCode + + // if the shipment is international, we need to change the respective ZIP to use the port ZIP and not the address ZIP + if mtoShipment.MarketCode == models.MarketCodeInternational { + portZip, portType, err := models.GetPortLocationInfoForShipment(appCtx.DB(), *mtoShipmentID) + if err != nil { + return "", err + } + if portZip != nil && portType != nil { + // if the port type is POEFSC this means the shipment is CONUS -> OCONUS (pickup -> port) + // if the port type is PODFSC this means the shipment is OCONUS -> CONUS (port -> destination) + if *portType == models.ReServiceCodePOEFSC.String() { + destinationZip = *portZip + } else if *portType == models.ReServiceCodePODFSC.String() { + pickupZip = *portZip + } + } else { + return "", apperror.NewNotFoundError(*mtoShipmentID, "looking for port ZIP for shipment") + } + } errorMsgForPickupZip := fmt.Sprintf("Shipment must have valid pickup zipcode. Received: %s", pickupZip) errorMsgForDestinationZip := fmt.Sprintf("Shipment must have valid destination zipcode. Received: %s", destinationZip) if len(pickupZip) < 5 { @@ -91,14 +110,15 @@ func (r DistanceZipLookup) lookup(appCtx appcontext.AppContext, keyData *Service } } - if mtoShipment.Distance != nil && mtoShipment.ShipmentType != models.MTOShipmentTypePPM { + internationalShipment := mtoShipment.MarketCode == models.MarketCodeInternational + if mtoShipment.Distance != nil && mtoShipment.ShipmentType != models.MTOShipmentTypePPM && !internationalShipment { return strconv.Itoa(mtoShipment.Distance.Int()), nil } if pickupZip == destinationZip { distanceMiles = 1 } else { - distanceMiles, err = planner.ZipTransitDistance(appCtx, pickupZip, destinationZip, false, false) + distanceMiles, err = planner.ZipTransitDistance(appCtx, pickupZip, destinationZip, false, internationalShipment) if err != nil { return "", err } diff --git a/pkg/payment_request/service_param_value_lookups/distance_zip_lookup_test.go b/pkg/payment_request/service_param_value_lookups/distance_zip_lookup_test.go index f40692f2f1c..f4fa18c8790 100644 --- a/pkg/payment_request/service_param_value_lookups/distance_zip_lookup_test.go +++ b/pkg/payment_request/service_param_value_lookups/distance_zip_lookup_test.go @@ -63,6 +63,68 @@ func (suite *ServiceParamValueLookupsSuite) TestDistanceLookup() { suite.Equal(unit.Miles(defaultZipDistance), *mtoShipment.Distance) }) + suite.Run("Calculate transit zip distance for international shipment with port data", func() { + testdatagen.MakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + StartDate: time.Now().Add(-24 * time.Hour), + EndDate: time.Now().Add(24 * time.Hour), + }, + }) + portLocation := factory.FetchPortLocation(suite.DB(), []factory.Customization{ + { + Model: models.Port{ + PortCode: "SEA", + }, + }, + }, nil) + mtoServiceItem := factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: models.ReService{ + Code: models.ReServiceCodePOEFSC, + }, + }, + { + Model: models.Address{ + PostalCode: "74133", + }, + Type: &factory.Addresses.PickupAddress, + }, + { + Model: models.MTOServiceItem{ + POELocationID: &portLocation.ID, + }, + }, + { + Model: models.MTOShipment{ + MarketCode: models.MarketCodeInternational, + }, + }, + }, []factory.Trait{ + factory.GetTraitAvailableToPrimeMove, + }) + + paymentRequest := factory.BuildPaymentRequest(suite.DB(), []factory.Customization{ + { + Model: mtoServiceItem.MoveTaskOrder, + LinkOnly: true, + }, + }, nil) + + paramLookup, err := ServiceParamLookupInitialize(suite.AppContextForTest(), suite.planner, mtoServiceItem, paymentRequest.ID, paymentRequest.MoveTaskOrderID, nil) + suite.FatalNoError(err) + + distanceStr, err := paramLookup.ServiceParamValue(suite.AppContextForTest(), key) + suite.FatalNoError(err) + expected := strconv.Itoa(defaultInternationalZipDistance) + suite.Equal(expected, distanceStr) + + var mtoShipment models.MTOShipment + err = suite.DB().Find(&mtoShipment, mtoServiceItem.MTOShipmentID) + suite.NoError(err) + + suite.Equal(unit.Miles(defaultInternationalZipDistance), *mtoShipment.Distance) + }) + suite.Run("Calculate zip distance lookup without a saved service item", func() { ppmShipment := factory.BuildPPMShipment(suite.DB(), nil, nil) diff --git a/pkg/payment_request/service_param_value_lookups/port_name_lookup.go b/pkg/payment_request/service_param_value_lookups/port_zip_lookup.go similarity index 50% rename from pkg/payment_request/service_param_value_lookups/port_name_lookup.go rename to pkg/payment_request/service_param_value_lookups/port_zip_lookup.go index a925ceaa099..3ea8be94315 100644 --- a/pkg/payment_request/service_param_value_lookups/port_name_lookup.go +++ b/pkg/payment_request/service_param_value_lookups/port_zip_lookup.go @@ -9,26 +9,27 @@ import ( "github.com/transcom/mymove/pkg/models" ) -// PortNameLookup does lookup on the shipment and finds the port name -type PortNameLookup struct { +// PortZipLookup does lookup on the shipment and finds the port zip +// The mileage calculated is from port <-> pickup/destination so this value is important +type PortZipLookup struct { ServiceItem models.MTOServiceItem } -func (p PortNameLookup) lookup(appCtx appcontext.AppContext, _ *ServiceItemParamKeyData) (string, error) { +func (p PortZipLookup) lookup(appCtx appcontext.AppContext, _ *ServiceItemParamKeyData) (string, error) { var portLocationID *uuid.UUID if p.ServiceItem.PODLocationID != nil { portLocationID = p.ServiceItem.PODLocationID } else if p.ServiceItem.POELocationID != nil { portLocationID = p.ServiceItem.POELocationID } else { - return "", fmt.Errorf("unable to find port location for service item id: %s", p.ServiceItem.ID) + return "", fmt.Errorf("unable to find port zip for service item id: %s", p.ServiceItem.ID) } var portLocation models.PortLocation err := appCtx.DB().Q(). - EagerPreload("Port"). + EagerPreload("UsPostRegionCity"). Where("id = $1", portLocationID).First(&portLocation) if err != nil { - return "", fmt.Errorf("unable to find port location with id %s", portLocationID) + return "", fmt.Errorf("unable to find port zip with id %s", portLocationID) } - return portLocation.Port.PortName, nil + return portLocation.UsPostRegionCity.UsprZipID, nil } diff --git a/pkg/payment_request/service_param_value_lookups/port_name_lookup_test.go b/pkg/payment_request/service_param_value_lookups/port_zip_lookup_test.go similarity index 83% rename from pkg/payment_request/service_param_value_lookups/port_name_lookup_test.go rename to pkg/payment_request/service_param_value_lookups/port_zip_lookup_test.go index a16b174dc1c..4410ba8e198 100644 --- a/pkg/payment_request/service_param_value_lookups/port_name_lookup_test.go +++ b/pkg/payment_request/service_param_value_lookups/port_zip_lookup_test.go @@ -10,8 +10,8 @@ import ( "github.com/transcom/mymove/pkg/testdatagen" ) -func (suite *ServiceParamValueLookupsSuite) TestPortNameLookup() { - key := models.ServiceItemParamNamePortName +func (suite *ServiceParamValueLookupsSuite) TestPortZipLookup() { + key := models.ServiceItemParamNamePortZip var mtoServiceItem models.MTOServiceItem setupTestData := func(serviceCode models.ReServiceCode, portID uuid.UUID) { testdatagen.MakeReContractYear(suite.DB(), testdatagen.Assertions{ @@ -49,7 +49,7 @@ func (suite *ServiceParamValueLookupsSuite) TestPortNameLookup() { } } - suite.Run("success - returns PortName value for POEFSC", func() { + suite.Run("success - returns PortZip value for POEFSC", func() { port := factory.FetchPortLocation(suite.DB(), []factory.Customization{ { Model: models.Port{ @@ -62,12 +62,12 @@ func (suite *ServiceParamValueLookupsSuite) TestPortNameLookup() { paramLookup, err := ServiceParamLookupInitialize(suite.AppContextForTest(), suite.planner, mtoServiceItem, uuid.Must(uuid.NewV4()), mtoServiceItem.MoveTaskOrderID, nil) suite.FatalNoError(err) - portName, err := paramLookup.ServiceParamValue(suite.AppContextForTest(), key) + portZip, err := paramLookup.ServiceParamValue(suite.AppContextForTest(), key) suite.FatalNoError(err) - suite.Equal(portName, port.Port.PortName) + suite.Equal(portZip, port.UsPostRegionCity.UsprZipID) }) - suite.Run("success - returns PortName value for PODFSC", func() { + suite.Run("success - returns PortZip value for PODFSC", func() { port := factory.FetchPortLocation(suite.DB(), []factory.Customization{ { Model: models.Port{ @@ -80,12 +80,12 @@ func (suite *ServiceParamValueLookupsSuite) TestPortNameLookup() { paramLookup, err := ServiceParamLookupInitialize(suite.AppContextForTest(), suite.planner, mtoServiceItem, uuid.Must(uuid.NewV4()), mtoServiceItem.MoveTaskOrderID, nil) suite.FatalNoError(err) - portName, err := paramLookup.ServiceParamValue(suite.AppContextForTest(), key) + portZip, err := paramLookup.ServiceParamValue(suite.AppContextForTest(), key) suite.FatalNoError(err) - suite.Equal(portName, port.Port.PortName) + suite.Equal(portZip, port.UsPostRegionCity.UsprZipID) }) - suite.Run("failure - no port value on service item", func() { + suite.Run("failure - no port zip on service item", func() { testdatagen.MakeReContractYear(suite.DB(), testdatagen.Assertions{ ReContractYear: models.ReContractYear{ StartDate: time.Now().Add(-24 * time.Hour), diff --git a/pkg/payment_request/service_param_value_lookups/service_param_value_lookups.go b/pkg/payment_request/service_param_value_lookups/service_param_value_lookups.go index c3669e5cb41..33775af842b 100644 --- a/pkg/payment_request/service_param_value_lookups/service_param_value_lookups.go +++ b/pkg/payment_request/service_param_value_lookups/service_param_value_lookups.go @@ -87,7 +87,7 @@ var ServiceItemParamsWithLookups = []models.ServiceItemParamName{ models.ServiceItemParamNameStandaloneCrateCap, models.ServiceItemParamNameLockedPriceCents, models.ServiceItemParamNamePerUnitCents, - models.ServiceItemParamNamePortName, + models.ServiceItemParamNamePortZip, } // ServiceParamLookupInitialize initializes service parameter lookup @@ -439,7 +439,7 @@ func InitializeLookups(appCtx appcontext.AppContext, shipment models.MTOShipment MTOShipment: shipment, } - lookups[models.ServiceItemParamNamePortName] = PortNameLookup{ + lookups[models.ServiceItemParamNamePortZip] = PortZipLookup{ ServiceItem: serviceItem, } diff --git a/pkg/payment_request/service_param_value_lookups/service_param_value_lookups_test.go b/pkg/payment_request/service_param_value_lookups/service_param_value_lookups_test.go index 1c9138e51ee..7b8307f147c 100644 --- a/pkg/payment_request/service_param_value_lookups/service_param_value_lookups_test.go +++ b/pkg/payment_request/service_param_value_lookups/service_param_value_lookups_test.go @@ -29,6 +29,7 @@ import ( ) const defaultZipDistance = 1234 +const defaultInternationalZipDistance = 1800 type ServiceParamValueLookupsSuite struct { *testingsuite.PopTestSuite @@ -49,6 +50,13 @@ func TestServiceParamValueLookupsSuite(t *testing.T) { false, false, ).Return(defaultZipDistance, nil) + planner.On("ZipTransitDistance", + mock.AnythingOfType("*appcontext.appContext"), + mock.Anything, + mock.Anything, + false, + true, + ).Return(defaultInternationalZipDistance, nil) ts := &ServiceParamValueLookupsSuite{ PopTestSuite: testingsuite.NewPopTestSuite(testingsuite.CurrentPackage(), testingsuite.WithPerTestTransaction()), diff --git a/pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer.go b/pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer.go index 3e6dcfe1bdd..e970f29a1c0 100644 --- a/pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer.go +++ b/pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer.go @@ -96,7 +96,7 @@ func (p portFuelSurchargePricer) PriceUsingParams(appCtx appcontext.AppContext, return unit.Cents(0), nil, err } - _, err = getParamString(params, models.ServiceItemParamNamePortName) + _, err = getParamString(params, models.ServiceItemParamNamePortZip) if err != nil { return unit.Cents(0), nil, err } diff --git a/pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer_test.go b/pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer_test.go index fa660bd796d..ce64f248c22 100644 --- a/pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer_test.go +++ b/pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer_test.go @@ -20,7 +20,7 @@ const ( intlPortFscWeightDistanceMultiplier = float64(0.000417) intlPortFscFuelPrice = unit.Millicents(281400) intlPortFscPriceCents = unit.Cents(2980) - intlPortFscPortName = "PORTLAND INTL" + intlPortFscPortZip = "99505" ) var intlPortFscActualPickupDate = time.Date(testdatagen.TestYear, time.June, 5, 7, 33, 11, 456, time.UTC) @@ -186,9 +186,9 @@ func (suite *GHCRateEngineServiceSuite) setupPortFuelSurchargeServiceItem() mode Value: fmt.Sprintf("%d", int(intlPortFscFuelPrice)), }, { - Key: models.ServiceItemParamNamePortName, + Key: models.ServiceItemParamNamePortZip, KeyType: models.ServiceItemParamTypeString, - Value: intlPortFscPortName, + Value: intlPortFscPortZip, }, }, nil, nil, )