From b9d4e5fef2c507801c2d412fabe92ab1ae36171e Mon Sep 17 00:00:00 2001 From: Alex Lusk Date: Fri, 2 Aug 2024 17:24:17 +0000 Subject: [PATCH 01/68] fix queue csv export for HQ role when viewing GBLOCs other than default --- src/components/Table/TableCSVExportButton.jsx | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/components/Table/TableCSVExportButton.jsx b/src/components/Table/TableCSVExportButton.jsx index 813f5b7a50d..9b5937c3642 100644 --- a/src/components/Table/TableCSVExportButton.jsx +++ b/src/components/Table/TableCSVExportButton.jsx @@ -1,10 +1,12 @@ -import React, { useState, useRef } from 'react'; +import React, { useState, useRef, useContext } from 'react'; import { CSVLink } from 'react-csv'; -import { Link } from '@trussworks/react-uswds'; +import { Button } from '@trussworks/react-uswds'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import moment from 'moment'; import PropTypes from 'prop-types'; +import SelectedGblocContext from 'components/Office/GblocSwitcher/SelectedGblocContext'; + const TableCSVExportButton = ({ labelText, filePrefix, @@ -22,6 +24,9 @@ const TableCSVExportButton = ({ const csvLinkRef = useRef(null); const { id: sortColumn, desc: sortOrder } = paramSort.length ? paramSort[0] : {}; + const gblocContext = useContext(SelectedGblocContext); + const { selectedGbloc } = gblocContext || { selectedGbloc: undefined }; + const formatDataForExport = (data, columns = tableColumns) => { const formattedData = []; data.forEach((row) => { @@ -50,6 +55,7 @@ const TableCSVExportButton = ({ order: sortOrder ? 'desc' : 'asc', filters: paramFilters, currentPageSize: totalCount, + viewAsGBLOC: selectedGbloc, }); const formattedData = formatDataForExport(response[queueFetcherKey]); @@ -61,15 +67,23 @@ const TableCSVExportButton = ({ return (

- + From 44907d71794eadd3136611b5717ff809b84ff63c Mon Sep 17 00:00:00 2001 From: Alex Lusk Date: Fri, 2 Aug 2024 17:25:22 +0000 Subject: [PATCH 02/68] test for disabling CSV export when there is nothing to export --- .../Table/TableCSVExportButton.test.jsx | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/components/Table/TableCSVExportButton.test.jsx b/src/components/Table/TableCSVExportButton.test.jsx index a3847752000..da8b2102f13 100644 --- a/src/components/Table/TableCSVExportButton.test.jsx +++ b/src/components/Table/TableCSVExportButton.test.jsx @@ -55,6 +55,11 @@ const paymentRequestsResponse = { ], }; +const paymentRequestsNoResultsResponse = { + page: 1, + perPage: 10, +}; + const paymentRequestColumns = [ { Header: ' ', @@ -129,6 +134,9 @@ const paymentRequestColumns = [ jest.mock('services/ghcApi', () => ({ getPaymentRequestsQueue: jest.fn().mockImplementation(() => Promise.resolve(paymentRequestsResponse)), + getPaymentRequestsNoResultsQueue: jest + .fn() + .mockImplementation(() => Promise.resolve(paymentRequestsNoResultsResponse)), })); describe('TableCSVExportButton', () => { @@ -155,4 +163,22 @@ describe('TableCSVExportButton', () => { expect(getPaymentRequestsQueue).toBeCalled(); }); + + const noResultsProps = { + tableColumns: paymentRequestColumns, + queueFetcher: () => Promise.resolve(paymentRequestsNoResultsResponse), + queueFetcherKey: 'queuePaymentRequests', + totalCount: 0, + }; + + it('is diabled when there is nothing to export', () => { + act(() => { + const wrapper = mount(); + const exportButton = wrapper.find('span[data-test-id="csv-export-btn-text"]'); + exportButton.simulate('click'); + wrapper.update(); + }); + + expect(getPaymentRequestsQueue).toBeCalled(); + }); }); From 6cab2b50ddcd364145e04a65e1bed8a350cb92d5 Mon Sep 17 00:00:00 2001 From: Cory Kleinjan Date: Thu, 22 Aug 2024 14:46:48 +0000 Subject: [PATCH 03/68] B-20441 --- migrations/app/migrations_manifest.txt | 1 + ...ng_locked_price_cents_for_ms_and_cs.up.sql | 4 ++ pkg/handlers/ghcapi/mto_service_items.go | 6 +-- pkg/services/ghc_rate_engine.go | 4 +- .../counseling_services_pricer.go | 37 +++++++++++-------- .../counseling_services_pricer_test.go | 10 ++++- .../management_services_pricer.go | 35 ++++++++++-------- .../management_services_pricer_test.go | 4 +- .../mocks/CounselingServicesPricer.go | 24 ++++++------ .../mocks/ManagementServicesPricer.go | 24 ++++++------ 10 files changed, 82 insertions(+), 67 deletions(-) create mode 100644 migrations/app/schema/20240821180447_populating_locked_price_cents_for_ms_and_cs.up.sql diff --git a/migrations/app/migrations_manifest.txt b/migrations/app/migrations_manifest.txt index 5f6577ab2fd..bb5958dd21a 100644 --- a/migrations/app/migrations_manifest.txt +++ b/migrations/app/migrations_manifest.txt @@ -979,3 +979,4 @@ 20240807140736_add_locked_price_cents_to_mto_service_items.up.sql 20240814144527_remove_allow_pptas_client.up.sql 20240820125856_allow_pptas_migration.up.sql +20240821180447_populating_locked_price_cents_for_ms_and_cs.up.sql diff --git a/migrations/app/schema/20240821180447_populating_locked_price_cents_for_ms_and_cs.up.sql b/migrations/app/schema/20240821180447_populating_locked_price_cents_for_ms_and_cs.up.sql new file mode 100644 index 00000000000..08b89a8baf8 --- /dev/null +++ b/migrations/app/schema/20240821180447_populating_locked_price_cents_for_ms_and_cs.up.sql @@ -0,0 +1,4 @@ +UPDATE mto_service_items AS ms +SET locked_price_cents = pricing_estimate +FROM re_services AS r +WHERE ms.re_service_id = r.id AND r.code = 'MS' OR r.code = 'CS' \ No newline at end of file diff --git a/pkg/handlers/ghcapi/mto_service_items.go b/pkg/handlers/ghcapi/mto_service_items.go index 160f96db12b..910df4569ee 100644 --- a/pkg/handlers/ghcapi/mto_service_items.go +++ b/pkg/handlers/ghcapi/mto_service_items.go @@ -17,7 +17,6 @@ import ( "github.com/transcom/mymove/pkg/handlers" "github.com/transcom/mymove/pkg/handlers/ghcapi/internal/payloads" "github.com/transcom/mymove/pkg/models" - serviceparamlookups "github.com/transcom/mymove/pkg/payment_request/service_param_value_lookups" "github.com/transcom/mymove/pkg/services" "github.com/transcom/mymove/pkg/services/audit" "github.com/transcom/mymove/pkg/services/event" @@ -384,7 +383,6 @@ func (h ListMTOServiceItemsHandler) Handle(params mtoserviceitemop.ListMTOServic } if len(indices) > 0 { - contract, err := serviceparamlookups.FetchContract(appCtx, *moveTaskOrder.AvailableToPrimeAt) if err != nil { return mtoserviceitemop.NewListMTOServiceItemsInternalServerError(), err } @@ -394,9 +392,9 @@ func (h ListMTOServiceItemsHandler) Handle(params mtoserviceitemop.ListMTOServic var displayParams services.PricingDisplayParams var err error if serviceItems[index].ReService.Code == "CS" { - price, displayParams, err = h.counselingPricer.Price(appCtx, contract.Code, *moveTaskOrder.AvailableToPrimeAt) + price, displayParams, err = h.counselingPricer.Price(appCtx, serviceItems[index]) } else if serviceItems[index].ReService.Code == "MS" { - price, displayParams, err = h.moveManagementPricer.Price(appCtx, contract.Code, *moveTaskOrder.AvailableToPrimeAt) + price, displayParams, err = h.moveManagementPricer.Price(appCtx, serviceItems[index]) } for _, param := range displayParams { diff --git a/pkg/services/ghc_rate_engine.go b/pkg/services/ghc_rate_engine.go index 51bd1d29d5e..5d9d2737bba 100644 --- a/pkg/services/ghc_rate_engine.go +++ b/pkg/services/ghc_rate_engine.go @@ -33,7 +33,7 @@ type ParamsPricer interface { // //go:generate mockery --name ManagementServicesPricer type ManagementServicesPricer interface { - Price(appCtx appcontext.AppContext, contractCode string, mtoAvailableToPrimeAt time.Time) (unit.Cents, PricingDisplayParams, error) + Price(appCtx appcontext.AppContext, serviceItem models.MTOServiceItem) (unit.Cents, PricingDisplayParams, error) ParamsPricer } @@ -41,7 +41,7 @@ type ManagementServicesPricer interface { // //go:generate mockery --name CounselingServicesPricer type CounselingServicesPricer interface { - Price(appCtx appcontext.AppContext, contractCode string, mtoAvailableToPrimeAt time.Time) (unit.Cents, PricingDisplayParams, error) + Price(appCtx appcontext.AppContext, serviceItem models.MTOServiceItem) (unit.Cents, PricingDisplayParams, error) ParamsPricer } diff --git a/pkg/services/ghcrateengine/counseling_services_pricer.go b/pkg/services/ghcrateengine/counseling_services_pricer.go index e2c3869474f..4ab2a67cbc1 100644 --- a/pkg/services/ghcrateengine/counseling_services_pricer.go +++ b/pkg/services/ghcrateengine/counseling_services_pricer.go @@ -2,7 +2,8 @@ package ghcrateengine import ( "fmt" - "time" + + "github.com/gofrs/uuid" "github.com/transcom/mymove/pkg/appcontext" "github.com/transcom/mymove/pkg/models" @@ -19,32 +20,36 @@ func NewCounselingServicesPricer() services.CounselingServicesPricer { } // Price determines the price for a counseling service -func (p counselingServicesPricer) Price(appCtx appcontext.AppContext, contractCode string, mtoAvailableToPrimeAt time.Time) (unit.Cents, services.PricingDisplayParams, error) { - taskOrderFee, err := fetchTaskOrderFee(appCtx, contractCode, models.ReServiceCodeCS, mtoAvailableToPrimeAt) - if err != nil { - return unit.Cents(0), nil, fmt.Errorf("could not fetch task order fee: %w", err) +func (p managementServicesPricer) Price(appCtx appcontext.AppContext, serviceItem models.MTOServiceItem) (unit.Cents, services.PricingDisplayParams, error) { + + if serviceItem.LockedPriceCents == nil { + return unit.Cents(0), nil, fmt.Errorf("could not find locked price cents: %s", serviceItem.ID) } - displayPriceParams := services.PricingDisplayParams{ + params := services.PricingDisplayParams{ { Key: models.ServiceItemParamNamePriceRateOrFactor, - Value: FormatCents(taskOrderFee.PriceCents), + Value: FormatCents(*serviceItem.LockedPriceCents), }, } - return taskOrderFee.PriceCents, displayPriceParams, nil + + return *serviceItem.LockedPriceCents, params, nil } // PriceUsingParams determines the price for a counseling service given PaymentServiceItemParams -func (p counselingServicesPricer) 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 +func (p managementServicesPricer) PriceUsingParams(appCtx appcontext.AppContext, params models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error) { + + var serviceItem models.MTOServiceItem + for _, param := range params { + if param.PaymentServiceItem.MTOServiceItem.LockedPriceCents != nil { + serviceItem = param.PaymentServiceItem.MTOServiceItem + break + } } - mtoAvailableToPrimeAt, err := getParamTime(params, models.ServiceItemParamNameMTOAvailableToPrimeAt) - if err != nil { - return unit.Cents(0), nil, err + if serviceItem.ID == uuid.Nil { + return unit.Cents(0), nil, fmt.Errorf("could not find id for shipment") } - return p.Price(appCtx, contractCode, mtoAvailableToPrimeAt) + return p.Price(appCtx, serviceItem) } diff --git a/pkg/services/ghcrateengine/counseling_services_pricer_test.go b/pkg/services/ghcrateengine/counseling_services_pricer_test.go index e59368337ac..c0098764a82 100644 --- a/pkg/services/ghcrateengine/counseling_services_pricer_test.go +++ b/pkg/services/ghcrateengine/counseling_services_pricer_test.go @@ -16,6 +16,12 @@ const ( var csAvailableToPrimeAt = time.Date(testdatagen.TestYear, time.June, 5, 7, 33, 11, 456, time.UTC) +var lockedPriceCents = unit.Cents(8327) + +var mtoServiceItem = models.MTOServiceItem{ + LockedPriceCents: &lockedPriceCents, +} + func (suite *GHCRateEngineServiceSuite) TestPriceCounselingServices() { counselingServicesPricer := NewCounselingServicesPricer() @@ -37,7 +43,7 @@ func (suite *GHCRateEngineServiceSuite) TestPriceCounselingServices() { suite.Run("success without PaymentServiceItemParams", func() { suite.setupTaskOrderFeeData(models.ReServiceCodeCS, csPriceCents) - priceCents, _, err := counselingServicesPricer.Price(suite.AppContextForTest(), testdatagen.DefaultContractCode, csAvailableToPrimeAt) + priceCents, _, err := counselingServicesPricer.Price(suite.AppContextForTest(), mtoServiceItem) suite.NoError(err) suite.Equal(csPriceCents, priceCents) }) @@ -50,7 +56,7 @@ func (suite *GHCRateEngineServiceSuite) TestPriceCounselingServices() { }) suite.Run("not finding a rate record", func() { - _, _, err := counselingServicesPricer.Price(suite.AppContextForTest(), "BOGUS", csAvailableToPrimeAt) + _, _, err := counselingServicesPricer.Price(suite.AppContextForTest(), mtoServiceItem) suite.Error(err) }) } diff --git a/pkg/services/ghcrateengine/management_services_pricer.go b/pkg/services/ghcrateengine/management_services_pricer.go index f01c990a1de..d6cd107fb0f 100644 --- a/pkg/services/ghcrateengine/management_services_pricer.go +++ b/pkg/services/ghcrateengine/management_services_pricer.go @@ -2,7 +2,8 @@ package ghcrateengine import ( "fmt" - "time" + + "github.com/gofrs/uuid" "github.com/transcom/mymove/pkg/appcontext" "github.com/transcom/mymove/pkg/models" @@ -19,32 +20,36 @@ func NewManagementServicesPricer() services.ManagementServicesPricer { } // Price determines the price for a management service -func (p managementServicesPricer) Price(appCtx appcontext.AppContext, contractCode string, mtoAvailableToPrimeAt time.Time) (unit.Cents, services.PricingDisplayParams, error) { - taskOrderFee, err := fetchTaskOrderFee(appCtx, contractCode, models.ReServiceCodeMS, mtoAvailableToPrimeAt) - if err != nil { - return unit.Cents(0), nil, fmt.Errorf("could not fetch task order fee: %w", err) +func (p counselingServicesPricer) Price(appCtx appcontext.AppContext, serviceItem models.MTOServiceItem) (unit.Cents, services.PricingDisplayParams, error) { + + if serviceItem.LockedPriceCents == nil { + return unit.Cents(0), nil, fmt.Errorf("could not find locked price cents: %s", serviceItem.ID) } + params := services.PricingDisplayParams{ { Key: models.ServiceItemParamNamePriceRateOrFactor, - Value: FormatCents(taskOrderFee.PriceCents), + Value: FormatCents(*serviceItem.LockedPriceCents), }, } - return taskOrderFee.PriceCents, params, nil + return *serviceItem.LockedPriceCents, params, nil } // PriceUsingParams determines the price for a management service given PaymentServiceItemParams -func (p managementServicesPricer) 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 +func (p counselingServicesPricer) PriceUsingParams(appCtx appcontext.AppContext, params models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error) { + + var serviceItem models.MTOServiceItem + for _, param := range params { + if param.PaymentServiceItem.MTOServiceItem.LockedPriceCents != nil { + serviceItem = param.PaymentServiceItem.MTOServiceItem + break + } } - mtoAvailableToPrimeAt, err := getParamTime(params, models.ServiceItemParamNameMTOAvailableToPrimeAt) - if err != nil { - return unit.Cents(0), nil, err + if serviceItem.ID == uuid.Nil { + return unit.Cents(0), nil, fmt.Errorf("could not find id for shipment") } - return p.Price(appCtx, contractCode, mtoAvailableToPrimeAt) + return p.Price(appCtx, serviceItem) } diff --git a/pkg/services/ghcrateengine/management_services_pricer_test.go b/pkg/services/ghcrateengine/management_services_pricer_test.go index 52355a2695b..05abcfcb2de 100644 --- a/pkg/services/ghcrateengine/management_services_pricer_test.go +++ b/pkg/services/ghcrateengine/management_services_pricer_test.go @@ -37,7 +37,7 @@ func (suite *GHCRateEngineServiceSuite) TestPriceManagementServices() { suite.setupTaskOrderFeeData(models.ReServiceCodeMS, msPriceCents) managementServicesPricer := NewManagementServicesPricer() - priceCents, _, err := managementServicesPricer.Price(suite.AppContextForTest(), testdatagen.DefaultContractCode, msAvailableToPrimeAt) + priceCents, _, err := managementServicesPricer.Price(suite.AppContextForTest(), mtoServiceItem) suite.NoError(err) suite.Equal(msPriceCents, priceCents) }) @@ -54,7 +54,7 @@ func (suite *GHCRateEngineServiceSuite) TestPriceManagementServices() { suite.setupTaskOrderFeeData(models.ReServiceCodeMS, msPriceCents) managementServicesPricer := NewManagementServicesPricer() - _, _, err := managementServicesPricer.Price(suite.AppContextForTest(), "BOGUS", msAvailableToPrimeAt) + _, _, err := managementServicesPricer.Price(suite.AppContextForTest(), mtoServiceItem) suite.Error(err) }) } diff --git a/pkg/services/mocks/CounselingServicesPricer.go b/pkg/services/mocks/CounselingServicesPricer.go index 561fcec66c7..2f6cc2cf375 100644 --- a/pkg/services/mocks/CounselingServicesPricer.go +++ b/pkg/services/mocks/CounselingServicesPricer.go @@ -10,8 +10,6 @@ import ( services "github.com/transcom/mymove/pkg/services" - time "time" - unit "github.com/transcom/mymove/pkg/unit" ) @@ -20,32 +18,32 @@ type CounselingServicesPricer struct { mock.Mock } -// Price provides a mock function with given fields: appCtx, contractCode, mtoAvailableToPrimeAt -func (_m *CounselingServicesPricer) Price(appCtx appcontext.AppContext, contractCode string, mtoAvailableToPrimeAt time.Time) (unit.Cents, services.PricingDisplayParams, error) { - ret := _m.Called(appCtx, contractCode, mtoAvailableToPrimeAt) +// Price provides a mock function with given fields: appCtx, serviceItem +func (_m *CounselingServicesPricer) Price(appCtx appcontext.AppContext, serviceItem models.MTOServiceItem) (unit.Cents, services.PricingDisplayParams, error) { + ret := _m.Called(appCtx, serviceItem) var r0 unit.Cents var r1 services.PricingDisplayParams var r2 error - if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, time.Time) (unit.Cents, services.PricingDisplayParams, error)); ok { - return rf(appCtx, contractCode, mtoAvailableToPrimeAt) + if rf, ok := ret.Get(0).(func(appcontext.AppContext, models.MTOServiceItem) (unit.Cents, services.PricingDisplayParams, error)); ok { + return rf(appCtx, serviceItem) } - if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, time.Time) unit.Cents); ok { - r0 = rf(appCtx, contractCode, mtoAvailableToPrimeAt) + if rf, ok := ret.Get(0).(func(appcontext.AppContext, models.MTOServiceItem) unit.Cents); ok { + r0 = rf(appCtx, serviceItem) } else { r0 = ret.Get(0).(unit.Cents) } - if rf, ok := ret.Get(1).(func(appcontext.AppContext, string, time.Time) services.PricingDisplayParams); ok { - r1 = rf(appCtx, contractCode, mtoAvailableToPrimeAt) + if rf, ok := ret.Get(1).(func(appcontext.AppContext, models.MTOServiceItem) services.PricingDisplayParams); ok { + r1 = rf(appCtx, serviceItem) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(services.PricingDisplayParams) } } - if rf, ok := ret.Get(2).(func(appcontext.AppContext, string, time.Time) error); ok { - r2 = rf(appCtx, contractCode, mtoAvailableToPrimeAt) + if rf, ok := ret.Get(2).(func(appcontext.AppContext, models.MTOServiceItem) error); ok { + r2 = rf(appCtx, serviceItem) } else { r2 = ret.Error(2) } diff --git a/pkg/services/mocks/ManagementServicesPricer.go b/pkg/services/mocks/ManagementServicesPricer.go index 97df59714cd..51dfab7071f 100644 --- a/pkg/services/mocks/ManagementServicesPricer.go +++ b/pkg/services/mocks/ManagementServicesPricer.go @@ -10,8 +10,6 @@ import ( services "github.com/transcom/mymove/pkg/services" - time "time" - unit "github.com/transcom/mymove/pkg/unit" ) @@ -20,32 +18,32 @@ type ManagementServicesPricer struct { mock.Mock } -// Price provides a mock function with given fields: appCtx, contractCode, mtoAvailableToPrimeAt -func (_m *ManagementServicesPricer) Price(appCtx appcontext.AppContext, contractCode string, mtoAvailableToPrimeAt time.Time) (unit.Cents, services.PricingDisplayParams, error) { - ret := _m.Called(appCtx, contractCode, mtoAvailableToPrimeAt) +// Price provides a mock function with given fields: appCtx, serviceItem +func (_m *ManagementServicesPricer) Price(appCtx appcontext.AppContext, serviceItem models.MTOServiceItem) (unit.Cents, services.PricingDisplayParams, error) { + ret := _m.Called(appCtx, serviceItem) var r0 unit.Cents var r1 services.PricingDisplayParams var r2 error - if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, time.Time) (unit.Cents, services.PricingDisplayParams, error)); ok { - return rf(appCtx, contractCode, mtoAvailableToPrimeAt) + if rf, ok := ret.Get(0).(func(appcontext.AppContext, models.MTOServiceItem) (unit.Cents, services.PricingDisplayParams, error)); ok { + return rf(appCtx, serviceItem) } - if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, time.Time) unit.Cents); ok { - r0 = rf(appCtx, contractCode, mtoAvailableToPrimeAt) + if rf, ok := ret.Get(0).(func(appcontext.AppContext, models.MTOServiceItem) unit.Cents); ok { + r0 = rf(appCtx, serviceItem) } else { r0 = ret.Get(0).(unit.Cents) } - if rf, ok := ret.Get(1).(func(appcontext.AppContext, string, time.Time) services.PricingDisplayParams); ok { - r1 = rf(appCtx, contractCode, mtoAvailableToPrimeAt) + if rf, ok := ret.Get(1).(func(appcontext.AppContext, models.MTOServiceItem) services.PricingDisplayParams); ok { + r1 = rf(appCtx, serviceItem) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(services.PricingDisplayParams) } } - if rf, ok := ret.Get(2).(func(appcontext.AppContext, string, time.Time) error); ok { - r2 = rf(appCtx, contractCode, mtoAvailableToPrimeAt) + if rf, ok := ret.Get(2).(func(appcontext.AppContext, models.MTOServiceItem) error); ok { + r2 = rf(appCtx, serviceItem) } else { r2 = ret.Error(2) } From b45b5d03b025e2957094a6ca270172b83eb688c3 Mon Sep 17 00:00:00 2001 From: Cory Kleinjan Date: Thu, 22 Aug 2024 15:11:58 +0000 Subject: [PATCH 04/68] Updating tests --- pkg/services/ghcrateengine/counseling_services_pricer.go | 6 ++---- pkg/services/ghcrateengine/management_services_pricer.go | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/pkg/services/ghcrateengine/counseling_services_pricer.go b/pkg/services/ghcrateengine/counseling_services_pricer.go index 4ab2a67cbc1..2337cf56c20 100644 --- a/pkg/services/ghcrateengine/counseling_services_pricer.go +++ b/pkg/services/ghcrateengine/counseling_services_pricer.go @@ -3,8 +3,6 @@ package ghcrateengine import ( "fmt" - "github.com/gofrs/uuid" - "github.com/transcom/mymove/pkg/appcontext" "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/services" @@ -47,8 +45,8 @@ func (p managementServicesPricer) PriceUsingParams(appCtx appcontext.AppContext, } } - if serviceItem.ID == uuid.Nil { - return unit.Cents(0), nil, fmt.Errorf("could not find id for shipment") + if serviceItem.LockedPriceCents == nil { + return unit.Cents(0), nil, fmt.Errorf("Service item did not contain value for locked price cents") } return p.Price(appCtx, serviceItem) diff --git a/pkg/services/ghcrateengine/management_services_pricer.go b/pkg/services/ghcrateengine/management_services_pricer.go index d6cd107fb0f..43aa68086d9 100644 --- a/pkg/services/ghcrateengine/management_services_pricer.go +++ b/pkg/services/ghcrateengine/management_services_pricer.go @@ -3,8 +3,6 @@ package ghcrateengine import ( "fmt" - "github.com/gofrs/uuid" - "github.com/transcom/mymove/pkg/appcontext" "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/services" @@ -47,8 +45,8 @@ func (p counselingServicesPricer) PriceUsingParams(appCtx appcontext.AppContext, } } - if serviceItem.ID == uuid.Nil { - return unit.Cents(0), nil, fmt.Errorf("could not find id for shipment") + if serviceItem.LockedPriceCents == nil { + return unit.Cents(0), nil, fmt.Errorf("Service item did not contain value for locked price cents") } return p.Price(appCtx, serviceItem) From fa82b8ad3e24df9c7995bf876eda2eeb4c4e314c Mon Sep 17 00:00:00 2001 From: Cory Kleinjan Date: Thu, 22 Aug 2024 16:54:27 +0000 Subject: [PATCH 05/68] Adding lockedPriceCents to mtoServiceItem test gen --- pkg/factory/mto_service_item_factory.go | 4 ++++ .../ghcrateengine/counseling_services_pricer.go | 2 +- .../counseling_services_pricer_test.go | 14 ++++++++------ .../ghcrateengine/management_services_pricer.go | 2 +- .../management_services_pricer_test.go | 2 +- 5 files changed, 15 insertions(+), 9 deletions(-) diff --git a/pkg/factory/mto_service_item_factory.go b/pkg/factory/mto_service_item_factory.go index 2ec6548b02b..d1cb056ed34 100644 --- a/pkg/factory/mto_service_item_factory.go +++ b/pkg/factory/mto_service_item_factory.go @@ -9,6 +9,7 @@ import ( "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/testdatagen" + "github.com/transcom/mymove/pkg/unit" ) type mtoServiceItemBuildType byte @@ -56,6 +57,8 @@ func buildMTOServiceItemWithBuildType(db *pop.Connection, customs []Customizatio requestedApprovalsRequestedStatus := false + var lockedPriceCents = unit.Cents(12303) + // Create default MTOServiceItem mtoServiceItem := models.MTOServiceItem{ MoveTaskOrder: move, @@ -67,6 +70,7 @@ func buildMTOServiceItemWithBuildType(db *pop.Connection, customs []Customizatio Status: models.MTOServiceItemStatusSubmitted, RequestedApprovalsRequestedStatus: &requestedApprovalsRequestedStatus, CustomerExpense: isCustomerExpense, + LockedPriceCents: &lockedPriceCents, } // only set SITOriginHHGOriginalAddress if a customization is provided diff --git a/pkg/services/ghcrateengine/counseling_services_pricer.go b/pkg/services/ghcrateengine/counseling_services_pricer.go index 2337cf56c20..c6260224c3f 100644 --- a/pkg/services/ghcrateengine/counseling_services_pricer.go +++ b/pkg/services/ghcrateengine/counseling_services_pricer.go @@ -46,7 +46,7 @@ func (p managementServicesPricer) PriceUsingParams(appCtx appcontext.AppContext, } if serviceItem.LockedPriceCents == nil { - return unit.Cents(0), nil, fmt.Errorf("Service item did not contain value for locked price cents") + return unit.Cents(0), nil, fmt.Errorf("service item did not contain value for locked price cents") } return p.Price(appCtx, serviceItem) diff --git a/pkg/services/ghcrateengine/counseling_services_pricer_test.go b/pkg/services/ghcrateengine/counseling_services_pricer_test.go index c0098764a82..a55b5355e58 100644 --- a/pkg/services/ghcrateengine/counseling_services_pricer_test.go +++ b/pkg/services/ghcrateengine/counseling_services_pricer_test.go @@ -11,27 +11,29 @@ import ( ) const ( - csPriceCents = unit.Cents(8327) + csPriceCents = unit.Cents(12303) ) var csAvailableToPrimeAt = time.Date(testdatagen.TestYear, time.June, 5, 7, 33, 11, 456, time.UTC) -var lockedPriceCents = unit.Cents(8327) - +var lockedPriceCents = unit.Cents(12303) var mtoServiceItem = models.MTOServiceItem{ LockedPriceCents: &lockedPriceCents, } +var failedMtoServiceItem = models.MTOServiceItem{ + LockedPriceCents: nil, +} + func (suite *GHCRateEngineServiceSuite) TestPriceCounselingServices() { counselingServicesPricer := NewCounselingServicesPricer() suite.Run("success using PaymentServiceItemParams", func() { - suite.setupTaskOrderFeeData(models.ReServiceCodeCS, csPriceCents) paymentServiceItem := suite.setupCounselingServicesItem() priceCents, displayParams, err := counselingServicesPricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) suite.NoError(err) - suite.Equal(csPriceCents, priceCents) + suite.Equal(lockedPriceCents, priceCents) // Check that PricingDisplayParams have been set and are returned expectedParams := services.PricingDisplayParams{ @@ -56,7 +58,7 @@ func (suite *GHCRateEngineServiceSuite) TestPriceCounselingServices() { }) suite.Run("not finding a rate record", func() { - _, _, err := counselingServicesPricer.Price(suite.AppContextForTest(), mtoServiceItem) + _, _, err := counselingServicesPricer.Price(suite.AppContextForTest(), failedMtoServiceItem) suite.Error(err) }) } diff --git a/pkg/services/ghcrateengine/management_services_pricer.go b/pkg/services/ghcrateengine/management_services_pricer.go index 43aa68086d9..dc41a9e3e87 100644 --- a/pkg/services/ghcrateengine/management_services_pricer.go +++ b/pkg/services/ghcrateengine/management_services_pricer.go @@ -46,7 +46,7 @@ func (p counselingServicesPricer) PriceUsingParams(appCtx appcontext.AppContext, } if serviceItem.LockedPriceCents == nil { - return unit.Cents(0), nil, fmt.Errorf("Service item did not contain value for locked price cents") + return unit.Cents(0), nil, fmt.Errorf("service item did not contain value for locked price cents") } return p.Price(appCtx, serviceItem) diff --git a/pkg/services/ghcrateengine/management_services_pricer_test.go b/pkg/services/ghcrateengine/management_services_pricer_test.go index 05abcfcb2de..784445d610c 100644 --- a/pkg/services/ghcrateengine/management_services_pricer_test.go +++ b/pkg/services/ghcrateengine/management_services_pricer_test.go @@ -54,7 +54,7 @@ func (suite *GHCRateEngineServiceSuite) TestPriceManagementServices() { suite.setupTaskOrderFeeData(models.ReServiceCodeMS, msPriceCents) managementServicesPricer := NewManagementServicesPricer() - _, _, err := managementServicesPricer.Price(suite.AppContextForTest(), mtoServiceItem) + _, _, err := managementServicesPricer.Price(suite.AppContextForTest(), failedMtoServiceItem) suite.Error(err) }) } From f3b052642528899cf5abbf029dbc0482a473d949 Mon Sep 17 00:00:00 2001 From: Cory Kleinjan Date: Thu, 22 Aug 2024 17:15:56 +0000 Subject: [PATCH 06/68] Updating recalculation test --- .../payment_request_recalculator_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/services/payment_request/payment_request_recalculator_test.go b/pkg/services/payment_request/payment_request_recalculator_test.go index 82da8ed74c2..924c1f02170 100644 --- a/pkg/services/payment_request/payment_request_recalculator_test.go +++ b/pkg/services/payment_request/payment_request_recalculator_test.go @@ -23,8 +23,8 @@ import ( const ( recalculateTestPickupZip = "30907" recalculateTestDestinationZip = "78234" - recalculateTestMSFee = unit.Cents(25513) - recalculateTestCSFee = unit.Cents(22399) + recalculateTestMSFee = unit.Cents(12303) + recalculateTestCSFee = unit.Cents(12303) recalculateTestDLHPrice = unit.Millicents(6000) recalculateTestFSCPrice = unit.Millicents(277600) recalculateTestDomOtherPrice = unit.Cents(2159) @@ -154,12 +154,12 @@ func (suite *PaymentRequestServiceSuite) TestRecalculatePaymentRequestSuccess() { paymentRequest: &oldPaymentRequest, serviceCode: models.ReServiceCodeMS, - priceCents: unit.Cents(25513), + priceCents: unit.Cents(12303), }, { paymentRequest: &oldPaymentRequest, serviceCode: models.ReServiceCodeCS, - priceCents: unit.Cents(22399), + priceCents: unit.Cents(12303), }, { paymentRequest: &oldPaymentRequest, @@ -196,13 +196,13 @@ func (suite *PaymentRequestServiceSuite) TestRecalculatePaymentRequestSuccess() isNewPaymentRequest: true, paymentRequest: newPaymentRequest, serviceCode: models.ReServiceCodeMS, - priceCents: unit.Cents(25513), + priceCents: unit.Cents(12303), }, { isNewPaymentRequest: true, paymentRequest: newPaymentRequest, serviceCode: models.ReServiceCodeCS, - priceCents: unit.Cents(22399), + priceCents: unit.Cents(12303), }, { isNewPaymentRequest: true, From eb01f3f397941696ea7d7ce5cbcc17af7ff612c7 Mon Sep 17 00:00:00 2001 From: Cory Kleinjan Date: Fri, 23 Aug 2024 14:39:38 +0000 Subject: [PATCH 07/68] Updating how ms and cs service itemsare priced --- migrations/app/migrations_manifest.txt | 1 + ...ng_locked_price_cents_for_ms_and_cs.up.sql | 5 +++ ...ng_locked_price_cents_service_param.up.sql | 14 ++++++++ pkg/factory/mto_service_item_factory.go | 12 +++++-- pkg/gen/ghcapi/embedded_spec.go | 6 ++-- .../ghcmessages/service_item_param_name.go | 5 ++- pkg/gen/primeapi/embedded_spec.go | 6 ++-- .../primemessages/service_item_param_name.go | 5 ++- pkg/gen/primev2api/embedded_spec.go | 6 ++-- .../service_item_param_name.go | 5 ++- pkg/gen/primev3api/embedded_spec.go | 6 ++-- .../service_item_param_name.go | 5 ++- pkg/handlers/ghcapi/mto_service_items.go | 4 +-- pkg/models/service_item_param_key.go | 4 +++ .../locked_price_cents_lookup.go | 21 ++++++++++++ .../service_param_value_lookups.go | 5 +++ pkg/services/ghc_rate_engine.go | 4 +-- .../counseling_services_pricer.go | 29 +++++------------ .../counseling_services_pricer_test.go | 32 +++---------------- .../management_services_pricer.go | 29 +++++------------ .../management_services_pricer_test.go | 24 ++------------ .../ghcrateengine/service_item_pricer_test.go | 9 ++---- .../mocks/CounselingServicesPricer.go | 22 ++++++------- .../mocks/ManagementServicesPricer.go | 22 ++++++------- .../definitions/ServiceItemParamName.yaml | 1 + swagger/ghc.yaml | 1 + swagger/prime.yaml | 1 + swagger/prime_v2.yaml | 1 + swagger/prime_v3.yaml | 1 + 29 files changed, 148 insertions(+), 138 deletions(-) create mode 100644 migrations/app/schema/20240822180409_adding_locked_price_cents_service_param.up.sql create mode 100644 pkg/payment_request/service_param_value_lookups/locked_price_cents_lookup.go diff --git a/migrations/app/migrations_manifest.txt b/migrations/app/migrations_manifest.txt index bb5958dd21a..5ac95f73208 100644 --- a/migrations/app/migrations_manifest.txt +++ b/migrations/app/migrations_manifest.txt @@ -980,3 +980,4 @@ 20240814144527_remove_allow_pptas_client.up.sql 20240820125856_allow_pptas_migration.up.sql 20240821180447_populating_locked_price_cents_for_ms_and_cs.up.sql +20240822180409_adding_locked_price_cents_service_param.up.sql diff --git a/migrations/app/schema/20240821180447_populating_locked_price_cents_for_ms_and_cs.up.sql b/migrations/app/schema/20240821180447_populating_locked_price_cents_for_ms_and_cs.up.sql index 08b89a8baf8..e4f686802a2 100644 --- a/migrations/app/schema/20240821180447_populating_locked_price_cents_for_ms_and_cs.up.sql +++ b/migrations/app/schema/20240821180447_populating_locked_price_cents_for_ms_and_cs.up.sql @@ -1,3 +1,8 @@ +-- Customer directed that the current pricing_estimate should be saved as the locked_price for MS and CS +SET statement_timeout = 300000; +SET lock_timeout = 300000; +SET idle_in_transaction_session_timeout = 300000; + UPDATE mto_service_items AS ms SET locked_price_cents = pricing_estimate FROM re_services AS r diff --git a/migrations/app/schema/20240822180409_adding_locked_price_cents_service_param.up.sql b/migrations/app/schema/20240822180409_adding_locked_price_cents_service_param.up.sql new file mode 100644 index 00000000000..648d8be964f --- /dev/null +++ b/migrations/app/schema/20240822180409_adding_locked_price_cents_service_param.up.sql @@ -0,0 +1,14 @@ +INSERT INTO service_item_param_keys +(id,key,description,type,origin,created_at,updated_at) +VALUES +('7ec5cf87-a446-4dd6-89d3-50bbc0d2c206','LockedPriceCents', 'Locked price when move was made available to prime', 'INTEGER', 'SYSTEM', now(), now()); + +INSERT INTO service_params +(id,service_id,service_item_param_key_id,created_at,updated_at,is_optional) +VALUES +('22056106-bbde-4ae7-b5bd-e7d2f103ab7d',(SELECT id FROM re_services WHERE code='MS'),(SELECT id FROM service_item_param_keys where key='LockedPriceCents'), now(), now(), 'false'); + +INSERT INTO service_params +(id,service_id,service_item_param_key_id,created_at,updated_at,is_optional) +VALUES +('86f8c20c-071e-4715-b0c1-608f540b3be3',(SELECT id FROM re_services WHERE code='CS'),(SELECT id FROM service_item_param_keys where key='LockedPriceCents'), now(), now(), 'false'); \ No newline at end of file diff --git a/pkg/factory/mto_service_item_factory.go b/pkg/factory/mto_service_item_factory.go index d1cb056ed34..bbe328ed60e 100644 --- a/pkg/factory/mto_service_item_factory.go +++ b/pkg/factory/mto_service_item_factory.go @@ -345,15 +345,23 @@ var ( Type: models.ServiceItemParamTypeString, Origin: models.ServiceItemParamOriginPrime, } + paramLockedPriceCents = models.ServiceItemParamKey{ + Key: models.ServiceItemParamNameLockedPriceCents, + Description: "locked price cents", + Type: models.ServiceItemParamTypeTimestamp, + Origin: models.ServiceItemParamOriginSystem, + } fixtureServiceItemParamsMap = map[models.ReServiceCode]models.ServiceItemParamKeys{ models.ReServiceCodeCS: { - paramContractCode, paramMTOAvailableAToPrimeAt, + paramContractCode, + paramLockedPriceCents, paramPriceRateOrFactor, }, models.ReServiceCodeMS: { - paramContractCode, paramMTOAvailableAToPrimeAt, + paramContractCode, + paramLockedPriceCents, paramPriceRateOrFactor, }, models.ReServiceCodeDLH: { diff --git a/pkg/gen/ghcapi/embedded_spec.go b/pkg/gen/ghcapi/embedded_spec.go index 9d9d4e674bd..1f89a09afcd 100644 --- a/pkg/gen/ghcapi/embedded_spec.go +++ b/pkg/gen/ghcapi/embedded_spec.go @@ -11959,7 +11959,8 @@ func init() { "ZipSITOriginHHGOriginalAddress", "StandaloneCrate", "StandaloneCrateCap", - "UncappedRequestTotal" + "UncappedRequestTotal", + "LockedPriceCents" ] }, "ServiceItemParamOrigin": { @@ -27032,7 +27033,8 @@ func init() { "ZipSITOriginHHGOriginalAddress", "StandaloneCrate", "StandaloneCrateCap", - "UncappedRequestTotal" + "UncappedRequestTotal", + "LockedPriceCents" ] }, "ServiceItemParamOrigin": { diff --git a/pkg/gen/ghcmessages/service_item_param_name.go b/pkg/gen/ghcmessages/service_item_param_name.go index dd10c8003cd..eff0f3d2734 100644 --- a/pkg/gen/ghcmessages/service_item_param_name.go +++ b/pkg/gen/ghcmessages/service_item_param_name.go @@ -236,6 +236,9 @@ const ( // ServiceItemParamNameUncappedRequestTotal captures enum value "UncappedRequestTotal" ServiceItemParamNameUncappedRequestTotal ServiceItemParamName = "UncappedRequestTotal" + + // ServiceItemParamNameLockedPriceCents captures enum value "LockedPriceCents" + ServiceItemParamNameLockedPriceCents ServiceItemParamName = "LockedPriceCents" ) // for schema @@ -243,7 +246,7 @@ var serviceItemParamNameEnum []interface{} func init() { var res []ServiceItemParamName - if err := json.Unmarshal([]byte(`["ActualPickupDate","ContractCode","ContractYearName","CubicFeetBilled","CubicFeetCrating","DimensionHeight","DimensionLength","DimensionWidth","DistanceZip","DistanceZipSITDest","DistanceZipSITOrigin","EIAFuelPrice","EscalationCompounded","FSCMultiplier","FSCPriceDifferenceInCents","FSCWeightBasedDistanceMultiplier","IsPeak","MarketDest","MarketOrigin","MTOAvailableToPrimeAt","NTSPackingFactor","NumberDaysSIT","PriceAreaDest","PriceAreaIntlDest","PriceAreaIntlOrigin","PriceAreaOrigin","PriceRateOrFactor","PSI_LinehaulDom","PSI_LinehaulDomPrice","PSI_LinehaulShort","PSI_LinehaulShortPrice","PSI_PriceDomDest","PSI_PriceDomDestPrice","PSI_PriceDomOrigin","PSI_PriceDomOriginPrice","PSI_ShippingLinehaulIntlCO","PSI_ShippingLinehaulIntlCOPrice","PSI_ShippingLinehaulIntlOC","PSI_ShippingLinehaulIntlOCPrice","PSI_ShippingLinehaulIntlOO","PSI_ShippingLinehaulIntlOOPrice","RateAreaNonStdDest","RateAreaNonStdOrigin","ReferenceDate","RequestedPickupDate","ServiceAreaDest","ServiceAreaOrigin","ServicesScheduleDest","ServicesScheduleOrigin","SITPaymentRequestEnd","SITPaymentRequestStart","SITScheduleDest","SITScheduleOrigin","SITServiceAreaDest","SITServiceAreaOrigin","WeightAdjusted","WeightBilled","WeightEstimated","WeightOriginal","WeightReweigh","ZipDestAddress","ZipPickupAddress","ZipSITDestHHGFinalAddress","ZipSITDestHHGOriginalAddress","ZipSITOriginHHGActualAddress","ZipSITOriginHHGOriginalAddress","StandaloneCrate","StandaloneCrateCap","UncappedRequestTotal"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["ActualPickupDate","ContractCode","ContractYearName","CubicFeetBilled","CubicFeetCrating","DimensionHeight","DimensionLength","DimensionWidth","DistanceZip","DistanceZipSITDest","DistanceZipSITOrigin","EIAFuelPrice","EscalationCompounded","FSCMultiplier","FSCPriceDifferenceInCents","FSCWeightBasedDistanceMultiplier","IsPeak","MarketDest","MarketOrigin","MTOAvailableToPrimeAt","NTSPackingFactor","NumberDaysSIT","PriceAreaDest","PriceAreaIntlDest","PriceAreaIntlOrigin","PriceAreaOrigin","PriceRateOrFactor","PSI_LinehaulDom","PSI_LinehaulDomPrice","PSI_LinehaulShort","PSI_LinehaulShortPrice","PSI_PriceDomDest","PSI_PriceDomDestPrice","PSI_PriceDomOrigin","PSI_PriceDomOriginPrice","PSI_ShippingLinehaulIntlCO","PSI_ShippingLinehaulIntlCOPrice","PSI_ShippingLinehaulIntlOC","PSI_ShippingLinehaulIntlOCPrice","PSI_ShippingLinehaulIntlOO","PSI_ShippingLinehaulIntlOOPrice","RateAreaNonStdDest","RateAreaNonStdOrigin","ReferenceDate","RequestedPickupDate","ServiceAreaDest","ServiceAreaOrigin","ServicesScheduleDest","ServicesScheduleOrigin","SITPaymentRequestEnd","SITPaymentRequestStart","SITScheduleDest","SITScheduleOrigin","SITServiceAreaDest","SITServiceAreaOrigin","WeightAdjusted","WeightBilled","WeightEstimated","WeightOriginal","WeightReweigh","ZipDestAddress","ZipPickupAddress","ZipSITDestHHGFinalAddress","ZipSITDestHHGOriginalAddress","ZipSITOriginHHGActualAddress","ZipSITOriginHHGOriginalAddress","StandaloneCrate","StandaloneCrateCap","UncappedRequestTotal","LockedPriceCents"]`), &res); err != nil { panic(err) } for _, v := range res { diff --git a/pkg/gen/primeapi/embedded_spec.go b/pkg/gen/primeapi/embedded_spec.go index 7a8b33c22c2..91649679893 100644 --- a/pkg/gen/primeapi/embedded_spec.go +++ b/pkg/gen/primeapi/embedded_spec.go @@ -3877,7 +3877,8 @@ func init() { "ZipSITOriginHHGOriginalAddress", "StandaloneCrate", "StandaloneCrateCap", - "UncappedRequestTotal" + "UncappedRequestTotal", + "LockedPriceCents" ] }, "ServiceItemParamOrigin": { @@ -8994,7 +8995,8 @@ func init() { "ZipSITOriginHHGOriginalAddress", "StandaloneCrate", "StandaloneCrateCap", - "UncappedRequestTotal" + "UncappedRequestTotal", + "LockedPriceCents" ] }, "ServiceItemParamOrigin": { diff --git a/pkg/gen/primemessages/service_item_param_name.go b/pkg/gen/primemessages/service_item_param_name.go index 7f8e128b151..d43a63a69f7 100644 --- a/pkg/gen/primemessages/service_item_param_name.go +++ b/pkg/gen/primemessages/service_item_param_name.go @@ -236,6 +236,9 @@ const ( // ServiceItemParamNameUncappedRequestTotal captures enum value "UncappedRequestTotal" ServiceItemParamNameUncappedRequestTotal ServiceItemParamName = "UncappedRequestTotal" + + // ServiceItemParamNameLockedPriceCents captures enum value "LockedPriceCents" + ServiceItemParamNameLockedPriceCents ServiceItemParamName = "LockedPriceCents" ) // for schema @@ -243,7 +246,7 @@ var serviceItemParamNameEnum []interface{} func init() { var res []ServiceItemParamName - if err := json.Unmarshal([]byte(`["ActualPickupDate","ContractCode","ContractYearName","CubicFeetBilled","CubicFeetCrating","DimensionHeight","DimensionLength","DimensionWidth","DistanceZip","DistanceZipSITDest","DistanceZipSITOrigin","EIAFuelPrice","EscalationCompounded","FSCMultiplier","FSCPriceDifferenceInCents","FSCWeightBasedDistanceMultiplier","IsPeak","MarketDest","MarketOrigin","MTOAvailableToPrimeAt","NTSPackingFactor","NumberDaysSIT","PriceAreaDest","PriceAreaIntlDest","PriceAreaIntlOrigin","PriceAreaOrigin","PriceRateOrFactor","PSI_LinehaulDom","PSI_LinehaulDomPrice","PSI_LinehaulShort","PSI_LinehaulShortPrice","PSI_PriceDomDest","PSI_PriceDomDestPrice","PSI_PriceDomOrigin","PSI_PriceDomOriginPrice","PSI_ShippingLinehaulIntlCO","PSI_ShippingLinehaulIntlCOPrice","PSI_ShippingLinehaulIntlOC","PSI_ShippingLinehaulIntlOCPrice","PSI_ShippingLinehaulIntlOO","PSI_ShippingLinehaulIntlOOPrice","RateAreaNonStdDest","RateAreaNonStdOrigin","ReferenceDate","RequestedPickupDate","ServiceAreaDest","ServiceAreaOrigin","ServicesScheduleDest","ServicesScheduleOrigin","SITPaymentRequestEnd","SITPaymentRequestStart","SITScheduleDest","SITScheduleOrigin","SITServiceAreaDest","SITServiceAreaOrigin","WeightAdjusted","WeightBilled","WeightEstimated","WeightOriginal","WeightReweigh","ZipDestAddress","ZipPickupAddress","ZipSITDestHHGFinalAddress","ZipSITDestHHGOriginalAddress","ZipSITOriginHHGActualAddress","ZipSITOriginHHGOriginalAddress","StandaloneCrate","StandaloneCrateCap","UncappedRequestTotal"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["ActualPickupDate","ContractCode","ContractYearName","CubicFeetBilled","CubicFeetCrating","DimensionHeight","DimensionLength","DimensionWidth","DistanceZip","DistanceZipSITDest","DistanceZipSITOrigin","EIAFuelPrice","EscalationCompounded","FSCMultiplier","FSCPriceDifferenceInCents","FSCWeightBasedDistanceMultiplier","IsPeak","MarketDest","MarketOrigin","MTOAvailableToPrimeAt","NTSPackingFactor","NumberDaysSIT","PriceAreaDest","PriceAreaIntlDest","PriceAreaIntlOrigin","PriceAreaOrigin","PriceRateOrFactor","PSI_LinehaulDom","PSI_LinehaulDomPrice","PSI_LinehaulShort","PSI_LinehaulShortPrice","PSI_PriceDomDest","PSI_PriceDomDestPrice","PSI_PriceDomOrigin","PSI_PriceDomOriginPrice","PSI_ShippingLinehaulIntlCO","PSI_ShippingLinehaulIntlCOPrice","PSI_ShippingLinehaulIntlOC","PSI_ShippingLinehaulIntlOCPrice","PSI_ShippingLinehaulIntlOO","PSI_ShippingLinehaulIntlOOPrice","RateAreaNonStdDest","RateAreaNonStdOrigin","ReferenceDate","RequestedPickupDate","ServiceAreaDest","ServiceAreaOrigin","ServicesScheduleDest","ServicesScheduleOrigin","SITPaymentRequestEnd","SITPaymentRequestStart","SITScheduleDest","SITScheduleOrigin","SITServiceAreaDest","SITServiceAreaOrigin","WeightAdjusted","WeightBilled","WeightEstimated","WeightOriginal","WeightReweigh","ZipDestAddress","ZipPickupAddress","ZipSITDestHHGFinalAddress","ZipSITDestHHGOriginalAddress","ZipSITOriginHHGActualAddress","ZipSITOriginHHGOriginalAddress","StandaloneCrate","StandaloneCrateCap","UncappedRequestTotal","LockedPriceCents"]`), &res); err != nil { panic(err) } for _, v := range res { diff --git a/pkg/gen/primev2api/embedded_spec.go b/pkg/gen/primev2api/embedded_spec.go index 6faf8bd65f4..40a2b4dd594 100644 --- a/pkg/gen/primev2api/embedded_spec.go +++ b/pkg/gen/primev2api/embedded_spec.go @@ -2610,7 +2610,8 @@ func init() { "ZipSITOriginHHGOriginalAddress", "StandaloneCrate", "StandaloneCrateCap", - "UncappedRequestTotal" + "UncappedRequestTotal", + "LockedPriceCents" ] }, "ServiceItemParamOrigin": { @@ -6054,7 +6055,8 @@ func init() { "ZipSITOriginHHGOriginalAddress", "StandaloneCrate", "StandaloneCrateCap", - "UncappedRequestTotal" + "UncappedRequestTotal", + "LockedPriceCents" ] }, "ServiceItemParamOrigin": { diff --git a/pkg/gen/primev2messages/service_item_param_name.go b/pkg/gen/primev2messages/service_item_param_name.go index a6939ad00f4..fb2d4097030 100644 --- a/pkg/gen/primev2messages/service_item_param_name.go +++ b/pkg/gen/primev2messages/service_item_param_name.go @@ -236,6 +236,9 @@ const ( // ServiceItemParamNameUncappedRequestTotal captures enum value "UncappedRequestTotal" ServiceItemParamNameUncappedRequestTotal ServiceItemParamName = "UncappedRequestTotal" + + // ServiceItemParamNameLockedPriceCents captures enum value "LockedPriceCents" + ServiceItemParamNameLockedPriceCents ServiceItemParamName = "LockedPriceCents" ) // for schema @@ -243,7 +246,7 @@ var serviceItemParamNameEnum []interface{} func init() { var res []ServiceItemParamName - if err := json.Unmarshal([]byte(`["ActualPickupDate","ContractCode","ContractYearName","CubicFeetBilled","CubicFeetCrating","DimensionHeight","DimensionLength","DimensionWidth","DistanceZip","DistanceZipSITDest","DistanceZipSITOrigin","EIAFuelPrice","EscalationCompounded","FSCMultiplier","FSCPriceDifferenceInCents","FSCWeightBasedDistanceMultiplier","IsPeak","MarketDest","MarketOrigin","MTOAvailableToPrimeAt","NTSPackingFactor","NumberDaysSIT","PriceAreaDest","PriceAreaIntlDest","PriceAreaIntlOrigin","PriceAreaOrigin","PriceRateOrFactor","PSI_LinehaulDom","PSI_LinehaulDomPrice","PSI_LinehaulShort","PSI_LinehaulShortPrice","PSI_PriceDomDest","PSI_PriceDomDestPrice","PSI_PriceDomOrigin","PSI_PriceDomOriginPrice","PSI_ShippingLinehaulIntlCO","PSI_ShippingLinehaulIntlCOPrice","PSI_ShippingLinehaulIntlOC","PSI_ShippingLinehaulIntlOCPrice","PSI_ShippingLinehaulIntlOO","PSI_ShippingLinehaulIntlOOPrice","RateAreaNonStdDest","RateAreaNonStdOrigin","ReferenceDate","RequestedPickupDate","ServiceAreaDest","ServiceAreaOrigin","ServicesScheduleDest","ServicesScheduleOrigin","SITPaymentRequestEnd","SITPaymentRequestStart","SITScheduleDest","SITScheduleOrigin","SITServiceAreaDest","SITServiceAreaOrigin","WeightAdjusted","WeightBilled","WeightEstimated","WeightOriginal","WeightReweigh","ZipDestAddress","ZipPickupAddress","ZipSITDestHHGFinalAddress","ZipSITDestHHGOriginalAddress","ZipSITOriginHHGActualAddress","ZipSITOriginHHGOriginalAddress","StandaloneCrate","StandaloneCrateCap","UncappedRequestTotal"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["ActualPickupDate","ContractCode","ContractYearName","CubicFeetBilled","CubicFeetCrating","DimensionHeight","DimensionLength","DimensionWidth","DistanceZip","DistanceZipSITDest","DistanceZipSITOrigin","EIAFuelPrice","EscalationCompounded","FSCMultiplier","FSCPriceDifferenceInCents","FSCWeightBasedDistanceMultiplier","IsPeak","MarketDest","MarketOrigin","MTOAvailableToPrimeAt","NTSPackingFactor","NumberDaysSIT","PriceAreaDest","PriceAreaIntlDest","PriceAreaIntlOrigin","PriceAreaOrigin","PriceRateOrFactor","PSI_LinehaulDom","PSI_LinehaulDomPrice","PSI_LinehaulShort","PSI_LinehaulShortPrice","PSI_PriceDomDest","PSI_PriceDomDestPrice","PSI_PriceDomOrigin","PSI_PriceDomOriginPrice","PSI_ShippingLinehaulIntlCO","PSI_ShippingLinehaulIntlCOPrice","PSI_ShippingLinehaulIntlOC","PSI_ShippingLinehaulIntlOCPrice","PSI_ShippingLinehaulIntlOO","PSI_ShippingLinehaulIntlOOPrice","RateAreaNonStdDest","RateAreaNonStdOrigin","ReferenceDate","RequestedPickupDate","ServiceAreaDest","ServiceAreaOrigin","ServicesScheduleDest","ServicesScheduleOrigin","SITPaymentRequestEnd","SITPaymentRequestStart","SITScheduleDest","SITScheduleOrigin","SITServiceAreaDest","SITServiceAreaOrigin","WeightAdjusted","WeightBilled","WeightEstimated","WeightOriginal","WeightReweigh","ZipDestAddress","ZipPickupAddress","ZipSITDestHHGFinalAddress","ZipSITDestHHGOriginalAddress","ZipSITOriginHHGActualAddress","ZipSITOriginHHGOriginalAddress","StandaloneCrate","StandaloneCrateCap","UncappedRequestTotal","LockedPriceCents"]`), &res); err != nil { panic(err) } for _, v := range res { diff --git a/pkg/gen/primev3api/embedded_spec.go b/pkg/gen/primev3api/embedded_spec.go index ec5ecd2374b..f1475093cda 100644 --- a/pkg/gen/primev3api/embedded_spec.go +++ b/pkg/gen/primev3api/embedded_spec.go @@ -2658,7 +2658,8 @@ func init() { "ZipSITOriginHHGOriginalAddress", "StandaloneCrate", "StandaloneCrateCap", - "UncappedRequestTotal" + "UncappedRequestTotal", + "LockedPriceCents" ] }, "ServiceItemParamOrigin": { @@ -6194,7 +6195,8 @@ func init() { "ZipSITOriginHHGOriginalAddress", "StandaloneCrate", "StandaloneCrateCap", - "UncappedRequestTotal" + "UncappedRequestTotal", + "LockedPriceCents" ] }, "ServiceItemParamOrigin": { diff --git a/pkg/gen/primev3messages/service_item_param_name.go b/pkg/gen/primev3messages/service_item_param_name.go index ab53b71f111..a7e2fdf7ea3 100644 --- a/pkg/gen/primev3messages/service_item_param_name.go +++ b/pkg/gen/primev3messages/service_item_param_name.go @@ -236,6 +236,9 @@ const ( // ServiceItemParamNameUncappedRequestTotal captures enum value "UncappedRequestTotal" ServiceItemParamNameUncappedRequestTotal ServiceItemParamName = "UncappedRequestTotal" + + // ServiceItemParamNameLockedPriceCents captures enum value "LockedPriceCents" + ServiceItemParamNameLockedPriceCents ServiceItemParamName = "LockedPriceCents" ) // for schema @@ -243,7 +246,7 @@ var serviceItemParamNameEnum []interface{} func init() { var res []ServiceItemParamName - if err := json.Unmarshal([]byte(`["ActualPickupDate","ContractCode","ContractYearName","CubicFeetBilled","CubicFeetCrating","DimensionHeight","DimensionLength","DimensionWidth","DistanceZip","DistanceZipSITDest","DistanceZipSITOrigin","EIAFuelPrice","EscalationCompounded","FSCMultiplier","FSCPriceDifferenceInCents","FSCWeightBasedDistanceMultiplier","IsPeak","MarketDest","MarketOrigin","MTOAvailableToPrimeAt","NTSPackingFactor","NumberDaysSIT","PriceAreaDest","PriceAreaIntlDest","PriceAreaIntlOrigin","PriceAreaOrigin","PriceRateOrFactor","PSI_LinehaulDom","PSI_LinehaulDomPrice","PSI_LinehaulShort","PSI_LinehaulShortPrice","PSI_PriceDomDest","PSI_PriceDomDestPrice","PSI_PriceDomOrigin","PSI_PriceDomOriginPrice","PSI_ShippingLinehaulIntlCO","PSI_ShippingLinehaulIntlCOPrice","PSI_ShippingLinehaulIntlOC","PSI_ShippingLinehaulIntlOCPrice","PSI_ShippingLinehaulIntlOO","PSI_ShippingLinehaulIntlOOPrice","RateAreaNonStdDest","RateAreaNonStdOrigin","ReferenceDate","RequestedPickupDate","ServiceAreaDest","ServiceAreaOrigin","ServicesScheduleDest","ServicesScheduleOrigin","SITPaymentRequestEnd","SITPaymentRequestStart","SITScheduleDest","SITScheduleOrigin","SITServiceAreaDest","SITServiceAreaOrigin","WeightAdjusted","WeightBilled","WeightEstimated","WeightOriginal","WeightReweigh","ZipDestAddress","ZipPickupAddress","ZipSITDestHHGFinalAddress","ZipSITDestHHGOriginalAddress","ZipSITOriginHHGActualAddress","ZipSITOriginHHGOriginalAddress","StandaloneCrate","StandaloneCrateCap","UncappedRequestTotal"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["ActualPickupDate","ContractCode","ContractYearName","CubicFeetBilled","CubicFeetCrating","DimensionHeight","DimensionLength","DimensionWidth","DistanceZip","DistanceZipSITDest","DistanceZipSITOrigin","EIAFuelPrice","EscalationCompounded","FSCMultiplier","FSCPriceDifferenceInCents","FSCWeightBasedDistanceMultiplier","IsPeak","MarketDest","MarketOrigin","MTOAvailableToPrimeAt","NTSPackingFactor","NumberDaysSIT","PriceAreaDest","PriceAreaIntlDest","PriceAreaIntlOrigin","PriceAreaOrigin","PriceRateOrFactor","PSI_LinehaulDom","PSI_LinehaulDomPrice","PSI_LinehaulShort","PSI_LinehaulShortPrice","PSI_PriceDomDest","PSI_PriceDomDestPrice","PSI_PriceDomOrigin","PSI_PriceDomOriginPrice","PSI_ShippingLinehaulIntlCO","PSI_ShippingLinehaulIntlCOPrice","PSI_ShippingLinehaulIntlOC","PSI_ShippingLinehaulIntlOCPrice","PSI_ShippingLinehaulIntlOO","PSI_ShippingLinehaulIntlOOPrice","RateAreaNonStdDest","RateAreaNonStdOrigin","ReferenceDate","RequestedPickupDate","ServiceAreaDest","ServiceAreaOrigin","ServicesScheduleDest","ServicesScheduleOrigin","SITPaymentRequestEnd","SITPaymentRequestStart","SITScheduleDest","SITScheduleOrigin","SITServiceAreaDest","SITServiceAreaOrigin","WeightAdjusted","WeightBilled","WeightEstimated","WeightOriginal","WeightReweigh","ZipDestAddress","ZipPickupAddress","ZipSITDestHHGFinalAddress","ZipSITDestHHGOriginalAddress","ZipSITOriginHHGActualAddress","ZipSITOriginHHGOriginalAddress","StandaloneCrate","StandaloneCrateCap","UncappedRequestTotal","LockedPriceCents"]`), &res); err != nil { panic(err) } for _, v := range res { diff --git a/pkg/handlers/ghcapi/mto_service_items.go b/pkg/handlers/ghcapi/mto_service_items.go index 910df4569ee..4a89afdc426 100644 --- a/pkg/handlers/ghcapi/mto_service_items.go +++ b/pkg/handlers/ghcapi/mto_service_items.go @@ -392,9 +392,9 @@ func (h ListMTOServiceItemsHandler) Handle(params mtoserviceitemop.ListMTOServic var displayParams services.PricingDisplayParams var err error if serviceItems[index].ReService.Code == "CS" { - price, displayParams, err = h.counselingPricer.Price(appCtx, serviceItems[index]) + price, displayParams, err = h.counselingPricer.Price(appCtx, *serviceItems[index].LockedPriceCents) } else if serviceItems[index].ReService.Code == "MS" { - price, displayParams, err = h.moveManagementPricer.Price(appCtx, serviceItems[index]) + price, displayParams, err = h.moveManagementPricer.Price(appCtx, *serviceItems[index].LockedPriceCents) } for _, param := range displayParams { diff --git a/pkg/models/service_item_param_key.go b/pkg/models/service_item_param_key.go index 6a708cb8f94..3bfd789dcc4 100644 --- a/pkg/models/service_item_param_key.go +++ b/pkg/models/service_item_param_key.go @@ -155,6 +155,8 @@ const ( ServiceItemParamNameStandaloneCrateCap ServiceItemParamName = "StandaloneCrateCap" // ServiceItemParamNameUncappedRequestTotal is the param key name UncappedRequestTotal ServiceItemParamNameUncappedRequestTotal ServiceItemParamName = "UncappedRequestTotal" + // ServiceItemParamNameLockedPriceCents is the param key name LockedPriceCents + ServiceItemParamNameLockedPriceCents ServiceItemParamName = "LockedPriceCents" ) // ServiceItemParamType is a type of service item parameter @@ -272,6 +274,7 @@ var ValidServiceItemParamNames = []ServiceItemParamName{ ServiceItemParamNameStandaloneCrate, ServiceItemParamNameStandaloneCrateCap, ServiceItemParamNameUncappedRequestTotal, + ServiceItemParamNameLockedPriceCents, } // ValidServiceItemParamNameStrings lists all valid service item param key names @@ -345,6 +348,7 @@ var ValidServiceItemParamNameStrings = []string{ string(ServiceItemParamNameStandaloneCrate), string(ServiceItemParamNameStandaloneCrateCap), string(ServiceItemParamNameUncappedRequestTotal), + string(ServiceItemParamNameLockedPriceCents), } // ValidServiceItemParamTypes lists all valid service item param types diff --git a/pkg/payment_request/service_param_value_lookups/locked_price_cents_lookup.go b/pkg/payment_request/service_param_value_lookups/locked_price_cents_lookup.go new file mode 100644 index 00000000000..f0a5dc62c49 --- /dev/null +++ b/pkg/payment_request/service_param_value_lookups/locked_price_cents_lookup.go @@ -0,0 +1,21 @@ +package serviceparamvaluelookups + +import ( + "github.com/transcom/mymove/pkg/appcontext" + "github.com/transcom/mymove/pkg/apperror" + "github.com/transcom/mymove/pkg/models" +) + +// LockedPriceCents does lookup on serviceItem +type LockedPriceCentsLookup struct { + ServiceItem models.MTOServiceItem +} + +func (r LockedPriceCentsLookup) lookup(appCtx appcontext.AppContext, _ *ServiceItemParamKeyData) (string, error) { + lockedPriceCents := r.ServiceItem.LockedPriceCents + if lockedPriceCents == nil { + return "0", apperror.NewConflictError(r.ServiceItem.ID, "unable to find locked price cents") + } + + return lockedPriceCents.ToMillicents().ToCents().String(), 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 ca8d76e8881..d901bdf58cd 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 @@ -84,6 +84,7 @@ var ServiceItemParamsWithLookups = []models.ServiceItemParamName{ models.ServiceItemParamNameDimensionWidth, models.ServiceItemParamNameStandaloneCrate, models.ServiceItemParamNameStandaloneCrateCap, + models.ServiceItemParamNameLockedPriceCents, } // ServiceParamLookupInitialize initializes service parameter lookup @@ -425,6 +426,10 @@ func InitializeLookups(appCtx appcontext.AppContext, shipment models.MTOShipment ServiceItem: serviceItem, } + lookups[models.ServiceItemParamNameLockedPriceCents] = LockedPriceCentsLookup{ + ServiceItem: serviceItem, + } + return lookups } diff --git a/pkg/services/ghc_rate_engine.go b/pkg/services/ghc_rate_engine.go index 5d9d2737bba..8393d6d1750 100644 --- a/pkg/services/ghc_rate_engine.go +++ b/pkg/services/ghc_rate_engine.go @@ -33,7 +33,7 @@ type ParamsPricer interface { // //go:generate mockery --name ManagementServicesPricer type ManagementServicesPricer interface { - Price(appCtx appcontext.AppContext, serviceItem models.MTOServiceItem) (unit.Cents, PricingDisplayParams, error) + Price(appCtx appcontext.AppContext, lockedPriceCents unit.Cents) (unit.Cents, PricingDisplayParams, error) ParamsPricer } @@ -41,7 +41,7 @@ type ManagementServicesPricer interface { // //go:generate mockery --name CounselingServicesPricer type CounselingServicesPricer interface { - Price(appCtx appcontext.AppContext, serviceItem models.MTOServiceItem) (unit.Cents, PricingDisplayParams, error) + Price(appCtx appcontext.AppContext, lockedPriceCents unit.Cents) (unit.Cents, PricingDisplayParams, error) ParamsPricer } diff --git a/pkg/services/ghcrateengine/counseling_services_pricer.go b/pkg/services/ghcrateengine/counseling_services_pricer.go index c6260224c3f..6ec7de65a14 100644 --- a/pkg/services/ghcrateengine/counseling_services_pricer.go +++ b/pkg/services/ghcrateengine/counseling_services_pricer.go @@ -1,8 +1,6 @@ package ghcrateengine import ( - "fmt" - "github.com/transcom/mymove/pkg/appcontext" "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/services" @@ -18,36 +16,25 @@ func NewCounselingServicesPricer() services.CounselingServicesPricer { } // Price determines the price for a counseling service -func (p managementServicesPricer) Price(appCtx appcontext.AppContext, serviceItem models.MTOServiceItem) (unit.Cents, services.PricingDisplayParams, error) { - - if serviceItem.LockedPriceCents == nil { - return unit.Cents(0), nil, fmt.Errorf("could not find locked price cents: %s", serviceItem.ID) - } +func (p counselingServicesPricer) Price(appCtx appcontext.AppContext, lockedPriceCents unit.Cents) (unit.Cents, services.PricingDisplayParams, error) { params := services.PricingDisplayParams{ { Key: models.ServiceItemParamNamePriceRateOrFactor, - Value: FormatCents(*serviceItem.LockedPriceCents), + Value: FormatCents(lockedPriceCents), }, } - return *serviceItem.LockedPriceCents, params, nil + return lockedPriceCents, params, nil } // PriceUsingParams determines the price for a counseling service given PaymentServiceItemParams -func (p managementServicesPricer) PriceUsingParams(appCtx appcontext.AppContext, params models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error) { - - var serviceItem models.MTOServiceItem - for _, param := range params { - if param.PaymentServiceItem.MTOServiceItem.LockedPriceCents != nil { - serviceItem = param.PaymentServiceItem.MTOServiceItem - break - } - } +func (p counselingServicesPricer) PriceUsingParams(appCtx appcontext.AppContext, params models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error) { - if serviceItem.LockedPriceCents == nil { - return unit.Cents(0), nil, fmt.Errorf("service item did not contain value for locked price cents") + lockedPriceCents, err := getParamInt(params, models.ServiceItemParamNameLockedPriceCents) + if err != nil { + return unit.Cents(0), nil, err } - return p.Price(appCtx, serviceItem) + return p.Price(appCtx, unit.Cents(lockedPriceCents)) } diff --git a/pkg/services/ghcrateengine/counseling_services_pricer_test.go b/pkg/services/ghcrateengine/counseling_services_pricer_test.go index a55b5355e58..644540c0450 100644 --- a/pkg/services/ghcrateengine/counseling_services_pricer_test.go +++ b/pkg/services/ghcrateengine/counseling_services_pricer_test.go @@ -1,12 +1,9 @@ package ghcrateengine import ( - "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" ) @@ -14,17 +11,6 @@ const ( csPriceCents = unit.Cents(12303) ) -var csAvailableToPrimeAt = time.Date(testdatagen.TestYear, time.June, 5, 7, 33, 11, 456, time.UTC) - -var lockedPriceCents = unit.Cents(12303) -var mtoServiceItem = models.MTOServiceItem{ - LockedPriceCents: &lockedPriceCents, -} - -var failedMtoServiceItem = models.MTOServiceItem{ - LockedPriceCents: nil, -} - func (suite *GHCRateEngineServiceSuite) TestPriceCounselingServices() { counselingServicesPricer := NewCounselingServicesPricer() @@ -33,7 +19,7 @@ func (suite *GHCRateEngineServiceSuite) TestPriceCounselingServices() { priceCents, displayParams, err := counselingServicesPricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) suite.NoError(err) - suite.Equal(lockedPriceCents, priceCents) + suite.Equal(csPriceCents, priceCents) // Check that PricingDisplayParams have been set and are returned expectedParams := services.PricingDisplayParams{ @@ -45,7 +31,7 @@ func (suite *GHCRateEngineServiceSuite) TestPriceCounselingServices() { suite.Run("success without PaymentServiceItemParams", func() { suite.setupTaskOrderFeeData(models.ReServiceCodeCS, csPriceCents) - priceCents, _, err := counselingServicesPricer.Price(suite.AppContextForTest(), mtoServiceItem) + priceCents, _, err := counselingServicesPricer.Price(suite.AppContextForTest(), csPriceCents) suite.NoError(err) suite.Equal(csPriceCents, priceCents) }) @@ -56,11 +42,6 @@ func (suite *GHCRateEngineServiceSuite) TestPriceCounselingServices() { _, _, err := counselingServicesPricer.PriceUsingParams(suite.AppContextForTest(), models.PaymentServiceItemParams{}) suite.Error(err) }) - - suite.Run("not finding a rate record", func() { - _, _, err := counselingServicesPricer.Price(suite.AppContextForTest(), failedMtoServiceItem) - suite.Error(err) - }) } func (suite *GHCRateEngineServiceSuite) setupCounselingServicesItem() models.PaymentServiceItem { @@ -69,14 +50,9 @@ func (suite *GHCRateEngineServiceSuite) setupCounselingServicesItem() models.Pay models.ReServiceCodeCS, []factory.CreatePaymentServiceItemParams{ { - Key: models.ServiceItemParamNameContractCode, + Key: models.ServiceItemParamNameLockedPriceCents, KeyType: models.ServiceItemParamTypeString, - Value: factory.DefaultContractCode, - }, - { - Key: models.ServiceItemParamNameMTOAvailableToPrimeAt, - KeyType: models.ServiceItemParamTypeTimestamp, - Value: csAvailableToPrimeAt.Format(TimestampParamFormat), + Value: csPriceCents.ToMillicents().ToCents().String(), }, }, nil, nil, ) diff --git a/pkg/services/ghcrateengine/management_services_pricer.go b/pkg/services/ghcrateengine/management_services_pricer.go index dc41a9e3e87..31a23729e94 100644 --- a/pkg/services/ghcrateengine/management_services_pricer.go +++ b/pkg/services/ghcrateengine/management_services_pricer.go @@ -1,8 +1,6 @@ package ghcrateengine import ( - "fmt" - "github.com/transcom/mymove/pkg/appcontext" "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/services" @@ -18,36 +16,25 @@ func NewManagementServicesPricer() services.ManagementServicesPricer { } // Price determines the price for a management service -func (p counselingServicesPricer) Price(appCtx appcontext.AppContext, serviceItem models.MTOServiceItem) (unit.Cents, services.PricingDisplayParams, error) { - - if serviceItem.LockedPriceCents == nil { - return unit.Cents(0), nil, fmt.Errorf("could not find locked price cents: %s", serviceItem.ID) - } +func (p managementServicesPricer) Price(appCtx appcontext.AppContext, lockedPriceCents unit.Cents) (unit.Cents, services.PricingDisplayParams, error) { params := services.PricingDisplayParams{ { Key: models.ServiceItemParamNamePriceRateOrFactor, - Value: FormatCents(*serviceItem.LockedPriceCents), + Value: FormatCents(lockedPriceCents), }, } - return *serviceItem.LockedPriceCents, params, nil + return lockedPriceCents, params, nil } // PriceUsingParams determines the price for a management service given PaymentServiceItemParams -func (p counselingServicesPricer) PriceUsingParams(appCtx appcontext.AppContext, params models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error) { - - var serviceItem models.MTOServiceItem - for _, param := range params { - if param.PaymentServiceItem.MTOServiceItem.LockedPriceCents != nil { - serviceItem = param.PaymentServiceItem.MTOServiceItem - break - } - } +func (p managementServicesPricer) PriceUsingParams(appCtx appcontext.AppContext, params models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error) { - if serviceItem.LockedPriceCents == nil { - return unit.Cents(0), nil, fmt.Errorf("service item did not contain value for locked price cents") + lockedPriceCents, err := getParamInt(params, models.ServiceItemParamNameLockedPriceCents) + if err != nil { + return unit.Cents(0), nil, err } - return p.Price(appCtx, serviceItem) + return p.Price(appCtx, unit.Cents(lockedPriceCents)) } diff --git a/pkg/services/ghcrateengine/management_services_pricer_test.go b/pkg/services/ghcrateengine/management_services_pricer_test.go index 784445d610c..389aac4639e 100644 --- a/pkg/services/ghcrateengine/management_services_pricer_test.go +++ b/pkg/services/ghcrateengine/management_services_pricer_test.go @@ -1,12 +1,9 @@ package ghcrateengine import ( - "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" ) @@ -14,8 +11,6 @@ const ( msPriceCents = unit.Cents(12303) ) -var msAvailableToPrimeAt = time.Date(testdatagen.TestYear, time.June, 3, 12, 57, 33, 123, time.UTC) - func (suite *GHCRateEngineServiceSuite) TestPriceManagementServices() { suite.Run("success using PaymentServiceItemParams", func() { suite.setupTaskOrderFeeData(models.ReServiceCodeMS, msPriceCents) @@ -37,7 +32,7 @@ func (suite *GHCRateEngineServiceSuite) TestPriceManagementServices() { suite.setupTaskOrderFeeData(models.ReServiceCodeMS, msPriceCents) managementServicesPricer := NewManagementServicesPricer() - priceCents, _, err := managementServicesPricer.Price(suite.AppContextForTest(), mtoServiceItem) + priceCents, _, err := managementServicesPricer.Price(suite.AppContextForTest(), msPriceCents) suite.NoError(err) suite.Equal(msPriceCents, priceCents) }) @@ -49,14 +44,6 @@ func (suite *GHCRateEngineServiceSuite) TestPriceManagementServices() { _, _, err := managementServicesPricer.PriceUsingParams(suite.AppContextForTest(), models.PaymentServiceItemParams{}) suite.Error(err) }) - - suite.Run("not finding a rate record", func() { - suite.setupTaskOrderFeeData(models.ReServiceCodeMS, msPriceCents) - managementServicesPricer := NewManagementServicesPricer() - - _, _, err := managementServicesPricer.Price(suite.AppContextForTest(), failedMtoServiceItem) - suite.Error(err) - }) } func (suite *GHCRateEngineServiceSuite) setupManagementServicesItem() models.PaymentServiceItem { @@ -65,14 +52,9 @@ func (suite *GHCRateEngineServiceSuite) setupManagementServicesItem() models.Pay models.ReServiceCodeMS, []factory.CreatePaymentServiceItemParams{ { - Key: models.ServiceItemParamNameContractCode, + Key: models.ServiceItemParamNameLockedPriceCents, KeyType: models.ServiceItemParamTypeString, - Value: factory.DefaultContractCode, - }, - { - Key: models.ServiceItemParamNameMTOAvailableToPrimeAt, - KeyType: models.ServiceItemParamTypeTimestamp, - Value: msAvailableToPrimeAt.Format(TimestampParamFormat), + Value: msPriceCents.ToMillicents().ToCents().String(), }, }, nil, nil, ) diff --git a/pkg/services/ghcrateengine/service_item_pricer_test.go b/pkg/services/ghcrateengine/service_item_pricer_test.go index ca9ae0cb724..29fd281cf48 100644 --- a/pkg/services/ghcrateengine/service_item_pricer_test.go +++ b/pkg/services/ghcrateengine/service_item_pricer_test.go @@ -115,14 +115,9 @@ func (suite *GHCRateEngineServiceSuite) setupPriceServiceItem() models.PaymentSe models.ReServiceCodeMS, []factory.CreatePaymentServiceItemParams{ { - Key: models.ServiceItemParamNameContractCode, + Key: models.ServiceItemParamNameLockedPriceCents, KeyType: models.ServiceItemParamTypeString, - Value: factory.DefaultContractCode, - }, - { - Key: models.ServiceItemParamNameMTOAvailableToPrimeAt, - KeyType: models.ServiceItemParamTypeTimestamp, - Value: msAvailableToPrimeAt.Format(TimestampParamFormat), + Value: msPriceCents.ToMillicents().ToCents().String(), }, }, nil, nil, ) diff --git a/pkg/services/mocks/CounselingServicesPricer.go b/pkg/services/mocks/CounselingServicesPricer.go index 2f6cc2cf375..51d2964ac42 100644 --- a/pkg/services/mocks/CounselingServicesPricer.go +++ b/pkg/services/mocks/CounselingServicesPricer.go @@ -18,32 +18,32 @@ type CounselingServicesPricer struct { mock.Mock } -// Price provides a mock function with given fields: appCtx, serviceItem -func (_m *CounselingServicesPricer) Price(appCtx appcontext.AppContext, serviceItem models.MTOServiceItem) (unit.Cents, services.PricingDisplayParams, error) { - ret := _m.Called(appCtx, serviceItem) +// Price provides a mock function with given fields: appCtx, lockedPriceCents +func (_m *CounselingServicesPricer) Price(appCtx appcontext.AppContext, lockedPriceCents unit.Cents) (unit.Cents, services.PricingDisplayParams, error) { + ret := _m.Called(appCtx, lockedPriceCents) var r0 unit.Cents var r1 services.PricingDisplayParams var r2 error - if rf, ok := ret.Get(0).(func(appcontext.AppContext, models.MTOServiceItem) (unit.Cents, services.PricingDisplayParams, error)); ok { - return rf(appCtx, serviceItem) + if rf, ok := ret.Get(0).(func(appcontext.AppContext, unit.Cents) (unit.Cents, services.PricingDisplayParams, error)); ok { + return rf(appCtx, lockedPriceCents) } - if rf, ok := ret.Get(0).(func(appcontext.AppContext, models.MTOServiceItem) unit.Cents); ok { - r0 = rf(appCtx, serviceItem) + if rf, ok := ret.Get(0).(func(appcontext.AppContext, unit.Cents) unit.Cents); ok { + r0 = rf(appCtx, lockedPriceCents) } else { r0 = ret.Get(0).(unit.Cents) } - if rf, ok := ret.Get(1).(func(appcontext.AppContext, models.MTOServiceItem) services.PricingDisplayParams); ok { - r1 = rf(appCtx, serviceItem) + if rf, ok := ret.Get(1).(func(appcontext.AppContext, unit.Cents) services.PricingDisplayParams); ok { + r1 = rf(appCtx, lockedPriceCents) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(services.PricingDisplayParams) } } - if rf, ok := ret.Get(2).(func(appcontext.AppContext, models.MTOServiceItem) error); ok { - r2 = rf(appCtx, serviceItem) + if rf, ok := ret.Get(2).(func(appcontext.AppContext, unit.Cents) error); ok { + r2 = rf(appCtx, lockedPriceCents) } else { r2 = ret.Error(2) } diff --git a/pkg/services/mocks/ManagementServicesPricer.go b/pkg/services/mocks/ManagementServicesPricer.go index 51dfab7071f..e361159de6e 100644 --- a/pkg/services/mocks/ManagementServicesPricer.go +++ b/pkg/services/mocks/ManagementServicesPricer.go @@ -18,32 +18,32 @@ type ManagementServicesPricer struct { mock.Mock } -// Price provides a mock function with given fields: appCtx, serviceItem -func (_m *ManagementServicesPricer) Price(appCtx appcontext.AppContext, serviceItem models.MTOServiceItem) (unit.Cents, services.PricingDisplayParams, error) { - ret := _m.Called(appCtx, serviceItem) +// Price provides a mock function with given fields: appCtx, lockedPriceCents +func (_m *ManagementServicesPricer) Price(appCtx appcontext.AppContext, lockedPriceCents unit.Cents) (unit.Cents, services.PricingDisplayParams, error) { + ret := _m.Called(appCtx, lockedPriceCents) var r0 unit.Cents var r1 services.PricingDisplayParams var r2 error - if rf, ok := ret.Get(0).(func(appcontext.AppContext, models.MTOServiceItem) (unit.Cents, services.PricingDisplayParams, error)); ok { - return rf(appCtx, serviceItem) + if rf, ok := ret.Get(0).(func(appcontext.AppContext, unit.Cents) (unit.Cents, services.PricingDisplayParams, error)); ok { + return rf(appCtx, lockedPriceCents) } - if rf, ok := ret.Get(0).(func(appcontext.AppContext, models.MTOServiceItem) unit.Cents); ok { - r0 = rf(appCtx, serviceItem) + if rf, ok := ret.Get(0).(func(appcontext.AppContext, unit.Cents) unit.Cents); ok { + r0 = rf(appCtx, lockedPriceCents) } else { r0 = ret.Get(0).(unit.Cents) } - if rf, ok := ret.Get(1).(func(appcontext.AppContext, models.MTOServiceItem) services.PricingDisplayParams); ok { - r1 = rf(appCtx, serviceItem) + if rf, ok := ret.Get(1).(func(appcontext.AppContext, unit.Cents) services.PricingDisplayParams); ok { + r1 = rf(appCtx, lockedPriceCents) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(services.PricingDisplayParams) } } - if rf, ok := ret.Get(2).(func(appcontext.AppContext, models.MTOServiceItem) error); ok { - r2 = rf(appCtx, serviceItem) + if rf, ok := ret.Get(2).(func(appcontext.AppContext, unit.Cents) error); ok { + r2 = rf(appCtx, lockedPriceCents) } else { r2 = ret.Error(2) } diff --git a/swagger-def/definitions/ServiceItemParamName.yaml b/swagger-def/definitions/ServiceItemParamName.yaml index 066dd32d45e..c3361653fbe 100644 --- a/swagger-def/definitions/ServiceItemParamName.yaml +++ b/swagger-def/definitions/ServiceItemParamName.yaml @@ -69,3 +69,4 @@ enum: - StandaloneCrate - StandaloneCrateCap - UncappedRequestTotal + - LockedPriceCents diff --git a/swagger/ghc.yaml b/swagger/ghc.yaml index 860cea19349..7aa82ce8fe4 100644 --- a/swagger/ghc.yaml +++ b/swagger/ghc.yaml @@ -9924,6 +9924,7 @@ definitions: - StandaloneCrate - StandaloneCrateCap - UncappedRequestTotal + - LockedPriceCents ServiceItemParamType: type: string enum: diff --git a/swagger/prime.yaml b/swagger/prime.yaml index 9ece3012fa8..2fe5ba8f7ec 100644 --- a/swagger/prime.yaml +++ b/swagger/prime.yaml @@ -3430,6 +3430,7 @@ definitions: - StandaloneCrate - StandaloneCrateCap - UncappedRequestTotal + - LockedPriceCents ServiceItemParamType: type: string enum: diff --git a/swagger/prime_v2.yaml b/swagger/prime_v2.yaml index 28c98b3c65e..939993ca806 100644 --- a/swagger/prime_v2.yaml +++ b/swagger/prime_v2.yaml @@ -1730,6 +1730,7 @@ definitions: - StandaloneCrate - StandaloneCrateCap - UncappedRequestTotal + - LockedPriceCents ServiceItemParamType: type: string enum: diff --git a/swagger/prime_v3.yaml b/swagger/prime_v3.yaml index 889f0808ad6..fc9f69ecd7a 100644 --- a/swagger/prime_v3.yaml +++ b/swagger/prime_v3.yaml @@ -1754,6 +1754,7 @@ definitions: - StandaloneCrate - StandaloneCrateCap - UncappedRequestTotal + - LockedPriceCents ServiceItemParamType: type: string enum: From 8f66f074cddffabaea976470f412b7e7557bd8cb Mon Sep 17 00:00:00 2001 From: Cory Kleinjan Date: Fri, 23 Aug 2024 17:00:38 +0000 Subject: [PATCH 08/68] Fixing management services pricer tests --- .../counseling_services_pricer_test.go | 2 +- .../management_services_pricer_test.go | 2 +- .../ghcrateengine/service_item_pricer_test.go | 2 +- .../payment_request_recalculator_test.go | 18 ------------------ 4 files changed, 3 insertions(+), 21 deletions(-) diff --git a/pkg/services/ghcrateengine/counseling_services_pricer_test.go b/pkg/services/ghcrateengine/counseling_services_pricer_test.go index 644540c0450..1ca6de8b660 100644 --- a/pkg/services/ghcrateengine/counseling_services_pricer_test.go +++ b/pkg/services/ghcrateengine/counseling_services_pricer_test.go @@ -51,7 +51,7 @@ func (suite *GHCRateEngineServiceSuite) setupCounselingServicesItem() models.Pay []factory.CreatePaymentServiceItemParams{ { Key: models.ServiceItemParamNameLockedPriceCents, - KeyType: models.ServiceItemParamTypeString, + KeyType: models.ServiceItemParamTypeInteger, Value: csPriceCents.ToMillicents().ToCents().String(), }, }, nil, nil, diff --git a/pkg/services/ghcrateengine/management_services_pricer_test.go b/pkg/services/ghcrateengine/management_services_pricer_test.go index 389aac4639e..be068473c20 100644 --- a/pkg/services/ghcrateengine/management_services_pricer_test.go +++ b/pkg/services/ghcrateengine/management_services_pricer_test.go @@ -53,7 +53,7 @@ func (suite *GHCRateEngineServiceSuite) setupManagementServicesItem() models.Pay []factory.CreatePaymentServiceItemParams{ { Key: models.ServiceItemParamNameLockedPriceCents, - KeyType: models.ServiceItemParamTypeString, + KeyType: models.ServiceItemParamTypeInteger, Value: msPriceCents.ToMillicents().ToCents().String(), }, }, nil, nil, diff --git a/pkg/services/ghcrateengine/service_item_pricer_test.go b/pkg/services/ghcrateengine/service_item_pricer_test.go index 29fd281cf48..6ebfec34a29 100644 --- a/pkg/services/ghcrateengine/service_item_pricer_test.go +++ b/pkg/services/ghcrateengine/service_item_pricer_test.go @@ -116,7 +116,7 @@ func (suite *GHCRateEngineServiceSuite) setupPriceServiceItem() models.PaymentSe []factory.CreatePaymentServiceItemParams{ { Key: models.ServiceItemParamNameLockedPriceCents, - KeyType: models.ServiceItemParamTypeString, + KeyType: models.ServiceItemParamTypeInteger, Value: msPriceCents.ToMillicents().ToCents().String(), }, }, nil, nil, diff --git a/pkg/services/payment_request/payment_request_recalculator_test.go b/pkg/services/payment_request/payment_request_recalculator_test.go index 924c1f02170..55b4a2ec47c 100644 --- a/pkg/services/payment_request/payment_request_recalculator_test.go +++ b/pkg/services/payment_request/payment_request_recalculator_test.go @@ -407,24 +407,6 @@ func (suite *PaymentRequestServiceSuite) setupRecalculateData1() (models.Move, m }, }) - // MS price data - msService := factory.BuildReServiceByCode(suite.DB(), models.ReServiceCodeMS) - msTaskOrderFee := models.ReTaskOrderFee{ - ContractYearID: contractYear.ID, - ServiceID: msService.ID, - PriceCents: recalculateTestMSFee, - } - suite.MustSave(&msTaskOrderFee) - - // CS price data - csService := factory.BuildReServiceByCode(suite.DB(), models.ReServiceCodeCS) - csTaskOrderFee := models.ReTaskOrderFee{ - ContractYearID: contractYear.ID, - ServiceID: csService.ID, - PriceCents: recalculateTestCSFee, - } - suite.MustSave(&csTaskOrderFee) - // DLH price data testdatagen.MakeReDomesticLinehaulPrice(suite.DB(), testdatagen.Assertions{ ReDomesticLinehaulPrice: models.ReDomesticLinehaulPrice{ From f76ac5d456c7cb71b32f3c9e360559565501b900 Mon Sep 17 00:00:00 2001 From: Cory Kleinjan Date: Fri, 23 Aug 2024 17:52:32 +0000 Subject: [PATCH 09/68] Fixing lockedPriceCents gen type --- pkg/factory/mto_service_item_factory.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/factory/mto_service_item_factory.go b/pkg/factory/mto_service_item_factory.go index bbe328ed60e..c39d7680d4b 100644 --- a/pkg/factory/mto_service_item_factory.go +++ b/pkg/factory/mto_service_item_factory.go @@ -348,7 +348,7 @@ var ( paramLockedPriceCents = models.ServiceItemParamKey{ Key: models.ServiceItemParamNameLockedPriceCents, Description: "locked price cents", - Type: models.ServiceItemParamTypeTimestamp, + Type: models.ServiceItemParamTypeInteger, Origin: models.ServiceItemParamOriginSystem, } fixtureServiceItemParamsMap = map[models.ReServiceCode]models.ServiceItemParamKeys{ From 05cce0ee989d6eaaed0b473f9b209f3d67d819b2 Mon Sep 17 00:00:00 2001 From: loganwc Date: Thu, 29 Aug 2024 18:30:07 +0000 Subject: [PATCH 10/68] added bulk download to Payment Request supporting documents --- pkg/gen/ghcapi/configure_mymove.go | 5 + pkg/gen/ghcapi/embedded_spec.go | 110 ++++++++ pkg/gen/ghcapi/ghcoperations/mymove_api.go | 12 + .../payment_requests/bulk_download.go | 58 ++++ .../bulk_download_parameters.go | 71 +++++ .../bulk_download_responses.go | 260 ++++++++++++++++++ .../bulk_download_urlbuilder.go | 99 +++++++ pkg/handlers/ghcapi/api.go | 6 + pkg/handlers/ghcapi/payment_request.go | 37 +++ pkg/handlers/ghcapi/ppm_document.go | 2 +- pkg/services/payment_request.go | 5 + .../payment_request_packed_creator.go | 57 ++++ .../PaymentRequestReview.jsx | 26 +- src/services/ghcApi.js | 4 + swagger-def/ghc.yaml | 34 +++ swagger/ghc.yaml | 35 +++ 16 files changed, 818 insertions(+), 3 deletions(-) create mode 100644 pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download.go create mode 100644 pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download_parameters.go create mode 100644 pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download_responses.go create mode 100644 pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download_urlbuilder.go create mode 100644 pkg/services/payment_request/payment_request_packed_creator.go diff --git a/pkg/gen/ghcapi/configure_mymove.go b/pkg/gen/ghcapi/configure_mymove.go index 780c3432a1d..55761ad3124 100644 --- a/pkg/gen/ghcapi/configure_mymove.go +++ b/pkg/gen/ghcapi/configure_mymove.go @@ -94,6 +94,11 @@ func configureAPI(api *ghcoperations.MymoveAPI) http.Handler { return middleware.NotImplemented("operation report_violations.AssociateReportViolations has not yet been implemented") }) } + if api.PaymentRequestsBulkDownloadHandler == nil { + api.PaymentRequestsBulkDownloadHandler = payment_requests.BulkDownloadHandlerFunc(func(params payment_requests.BulkDownloadParams) middleware.Responder { + return middleware.NotImplemented("operation payment_requests.BulkDownload has not yet been implemented") + }) + } if api.OrderCounselingUpdateAllowanceHandler == nil { api.OrderCounselingUpdateAllowanceHandler = order.CounselingUpdateAllowanceHandlerFunc(func(params order.CounselingUpdateAllowanceParams) middleware.Responder { return middleware.NotImplemented("operation order.CounselingUpdateAllowance has not yet been implemented") diff --git a/pkg/gen/ghcapi/embedded_spec.go b/pkg/gen/ghcapi/embedded_spec.go index 9bf7560174f..a418e06a844 100644 --- a/pkg/gen/ghcapi/embedded_spec.go +++ b/pkg/gen/ghcapi/embedded_spec.go @@ -3121,6 +3121,55 @@ func init() { } ] }, + "/payment-requests/{paymentRequestID}/bulkDownload": { + "get": { + "description": "This endpoint downloads all uploaded payment request documentation combined into a single PDF.\n", + "produces": [ + "application/pdf" + ], + "tags": [ + "paymentRequests" + ], + "summary": "Downloads all Payment Request documents as a PDF", + "operationId": "bulkDownload", + "responses": { + "200": { + "description": "Payment Request Files PDF", + "schema": { + "type": "file", + "format": "binary" + }, + "headers": { + "Content-Disposition": { + "type": "string", + "description": "File name to download" + } + } + }, + "400": { + "$ref": "#/responses/InvalidRequest" + }, + "403": { + "$ref": "#/responses/PermissionDenied" + }, + "404": { + "$ref": "#/responses/NotFound" + }, + "500": { + "$ref": "#/responses/ServerError" + } + } + }, + "parameters": [ + { + "type": "string", + "description": "the id for the payment-request with files to be downloaded", + "name": "paymentRequestID", + "in": "path", + "required": true + } + ] + }, "/payment-requests/{paymentRequestID}/shipments-payment-sit-balance": { "get": { "description": "Returns all shipment payment request SIT usage to support partial SIT invoicing", @@ -17357,6 +17406,67 @@ func init() { } ] }, + "/payment-requests/{paymentRequestID}/bulkDownload": { + "get": { + "description": "This endpoint downloads all uploaded payment request documentation combined into a single PDF.\n", + "produces": [ + "application/pdf" + ], + "tags": [ + "paymentRequests" + ], + "summary": "Downloads all Payment Request documents as a PDF", + "operationId": "bulkDownload", + "responses": { + "200": { + "description": "Payment Request Files PDF", + "schema": { + "type": "file", + "format": "binary" + }, + "headers": { + "Content-Disposition": { + "type": "string", + "description": "File name to download" + } + } + }, + "400": { + "description": "The request payload is invalid", + "schema": { + "$ref": "#/definitions/Error" + } + }, + "403": { + "description": "The request was denied", + "schema": { + "$ref": "#/definitions/Error" + } + }, + "404": { + "description": "The requested resource wasn't found", + "schema": { + "$ref": "#/definitions/Error" + } + }, + "500": { + "description": "A server error occurred", + "schema": { + "$ref": "#/definitions/Error" + } + } + } + }, + "parameters": [ + { + "type": "string", + "description": "the id for the payment-request with files to be downloaded", + "name": "paymentRequestID", + "in": "path", + "required": true + } + ] + }, "/payment-requests/{paymentRequestID}/shipments-payment-sit-balance": { "get": { "description": "Returns all shipment payment request SIT usage to support partial SIT invoicing", diff --git a/pkg/gen/ghcapi/ghcoperations/mymove_api.go b/pkg/gen/ghcapi/ghcoperations/mymove_api.go index f5368032656..a528ad4222c 100644 --- a/pkg/gen/ghcapi/ghcoperations/mymove_api.go +++ b/pkg/gen/ghcapi/ghcoperations/mymove_api.go @@ -83,6 +83,9 @@ func NewMymoveAPI(spec *loads.Document) *MymoveAPI { ReportViolationsAssociateReportViolationsHandler: report_violations.AssociateReportViolationsHandlerFunc(func(params report_violations.AssociateReportViolationsParams) middleware.Responder { return middleware.NotImplemented("operation report_violations.AssociateReportViolations has not yet been implemented") }), + PaymentRequestsBulkDownloadHandler: payment_requests.BulkDownloadHandlerFunc(func(params payment_requests.BulkDownloadParams) middleware.Responder { + return middleware.NotImplemented("operation payment_requests.BulkDownload has not yet been implemented") + }), OrderCounselingUpdateAllowanceHandler: order.CounselingUpdateAllowanceHandlerFunc(func(params order.CounselingUpdateAllowanceParams) middleware.Responder { return middleware.NotImplemented("operation order.CounselingUpdateAllowance has not yet been implemented") }), @@ -406,6 +409,8 @@ type MymoveAPI struct { ShipmentApproveShipmentDiversionHandler shipment.ApproveShipmentDiversionHandler // ReportViolationsAssociateReportViolationsHandler sets the operation handler for the associate report violations operation ReportViolationsAssociateReportViolationsHandler report_violations.AssociateReportViolationsHandler + // PaymentRequestsBulkDownloadHandler sets the operation handler for the bulk download operation + PaymentRequestsBulkDownloadHandler payment_requests.BulkDownloadHandler // OrderCounselingUpdateAllowanceHandler sets the operation handler for the counseling update allowance operation OrderCounselingUpdateAllowanceHandler order.CounselingUpdateAllowanceHandler // OrderCounselingUpdateOrderHandler sets the operation handler for the counseling update order operation @@ -682,6 +687,9 @@ func (o *MymoveAPI) Validate() error { if o.ReportViolationsAssociateReportViolationsHandler == nil { unregistered = append(unregistered, "report_violations.AssociateReportViolationsHandler") } + if o.PaymentRequestsBulkDownloadHandler == nil { + unregistered = append(unregistered, "payment_requests.BulkDownloadHandler") + } if o.OrderCounselingUpdateAllowanceHandler == nil { unregistered = append(unregistered, "order.CounselingUpdateAllowanceHandler") } @@ -1061,6 +1069,10 @@ func (o *MymoveAPI) initHandlerCache() { o.handlers["POST"] = make(map[string]http.Handler) } o.handlers["POST"]["/report-violations/{reportID}"] = report_violations.NewAssociateReportViolations(o.context, o.ReportViolationsAssociateReportViolationsHandler) + if o.handlers["GET"] == nil { + o.handlers["GET"] = make(map[string]http.Handler) + } + o.handlers["GET"]["/payment-requests/{paymentRequestID}/bulkDownload"] = payment_requests.NewBulkDownload(o.context, o.PaymentRequestsBulkDownloadHandler) if o.handlers["PATCH"] == nil { o.handlers["PATCH"] = make(map[string]http.Handler) } diff --git a/pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download.go b/pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download.go new file mode 100644 index 00000000000..78f7901ab05 --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download.go @@ -0,0 +1,58 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package payment_requests + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" +) + +// BulkDownloadHandlerFunc turns a function with the right signature into a bulk download handler +type BulkDownloadHandlerFunc func(BulkDownloadParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn BulkDownloadHandlerFunc) Handle(params BulkDownloadParams) middleware.Responder { + return fn(params) +} + +// BulkDownloadHandler interface for that can handle valid bulk download params +type BulkDownloadHandler interface { + Handle(BulkDownloadParams) middleware.Responder +} + +// NewBulkDownload creates a new http.Handler for the bulk download operation +func NewBulkDownload(ctx *middleware.Context, handler BulkDownloadHandler) *BulkDownload { + return &BulkDownload{Context: ctx, Handler: handler} +} + +/* + BulkDownload swagger:route GET /payment-requests/{paymentRequestID}/bulkDownload paymentRequests bulkDownload + +# Downloads all Payment Request documents as a PDF + +This endpoint downloads all uploaded payment request documentation combined into a single PDF. +*/ +type BulkDownload struct { + Context *middleware.Context + Handler BulkDownloadHandler +} + +func (o *BulkDownload) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewBulkDownloadParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download_parameters.go b/pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download_parameters.go new file mode 100644 index 00000000000..ff67f05c88a --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download_parameters.go @@ -0,0 +1,71 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package payment_requests + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" +) + +// NewBulkDownloadParams creates a new BulkDownloadParams object +// +// There are no default values defined in the spec. +func NewBulkDownloadParams() BulkDownloadParams { + + return BulkDownloadParams{} +} + +// BulkDownloadParams contains all the bound params for the bulk download operation +// typically these are obtained from a http.Request +// +// swagger:parameters bulkDownload +type BulkDownloadParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /*the id for the payment-request with files to be downloaded + Required: true + In: path + */ + PaymentRequestID string +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewBulkDownloadParams() beforehand. +func (o *BulkDownloadParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + rPaymentRequestID, rhkPaymentRequestID, _ := route.Params.GetOK("paymentRequestID") + if err := o.bindPaymentRequestID(rPaymentRequestID, rhkPaymentRequestID, route.Formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindPaymentRequestID binds and validates parameter PaymentRequestID from path. +func (o *BulkDownloadParams) bindPaymentRequestID(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: true + // Parameter is provided by construction from the route + o.PaymentRequestID = raw + + return nil +} diff --git a/pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download_responses.go b/pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download_responses.go new file mode 100644 index 00000000000..aa2aa194d11 --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download_responses.go @@ -0,0 +1,260 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package payment_requests + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "io" + "net/http" + + "github.com/go-openapi/runtime" + + "github.com/transcom/mymove/pkg/gen/ghcmessages" +) + +// BulkDownloadOKCode is the HTTP code returned for type BulkDownloadOK +const BulkDownloadOKCode int = 200 + +/* +BulkDownloadOK Payment Request Files PDF + +swagger:response bulkDownloadOK +*/ +type BulkDownloadOK struct { + /*File name to download + + */ + ContentDisposition string `json:"Content-Disposition"` + + /* + In: Body + */ + Payload io.ReadCloser `json:"body,omitempty"` +} + +// NewBulkDownloadOK creates BulkDownloadOK with default headers values +func NewBulkDownloadOK() *BulkDownloadOK { + + return &BulkDownloadOK{} +} + +// WithContentDisposition adds the contentDisposition to the bulk download o k response +func (o *BulkDownloadOK) WithContentDisposition(contentDisposition string) *BulkDownloadOK { + o.ContentDisposition = contentDisposition + return o +} + +// SetContentDisposition sets the contentDisposition to the bulk download o k response +func (o *BulkDownloadOK) SetContentDisposition(contentDisposition string) { + o.ContentDisposition = contentDisposition +} + +// WithPayload adds the payload to the bulk download o k response +func (o *BulkDownloadOK) WithPayload(payload io.ReadCloser) *BulkDownloadOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the bulk download o k response +func (o *BulkDownloadOK) SetPayload(payload io.ReadCloser) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *BulkDownloadOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + // response header Content-Disposition + + contentDisposition := o.ContentDisposition + if contentDisposition != "" { + rw.Header().Set("Content-Disposition", contentDisposition) + } + + rw.WriteHeader(200) + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } +} + +// BulkDownloadBadRequestCode is the HTTP code returned for type BulkDownloadBadRequest +const BulkDownloadBadRequestCode int = 400 + +/* +BulkDownloadBadRequest The request payload is invalid + +swagger:response bulkDownloadBadRequest +*/ +type BulkDownloadBadRequest struct { + + /* + In: Body + */ + Payload *ghcmessages.Error `json:"body,omitempty"` +} + +// NewBulkDownloadBadRequest creates BulkDownloadBadRequest with default headers values +func NewBulkDownloadBadRequest() *BulkDownloadBadRequest { + + return &BulkDownloadBadRequest{} +} + +// WithPayload adds the payload to the bulk download bad request response +func (o *BulkDownloadBadRequest) WithPayload(payload *ghcmessages.Error) *BulkDownloadBadRequest { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the bulk download bad request response +func (o *BulkDownloadBadRequest) SetPayload(payload *ghcmessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *BulkDownloadBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(400) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// BulkDownloadForbiddenCode is the HTTP code returned for type BulkDownloadForbidden +const BulkDownloadForbiddenCode int = 403 + +/* +BulkDownloadForbidden The request was denied + +swagger:response bulkDownloadForbidden +*/ +type BulkDownloadForbidden struct { + + /* + In: Body + */ + Payload *ghcmessages.Error `json:"body,omitempty"` +} + +// NewBulkDownloadForbidden creates BulkDownloadForbidden with default headers values +func NewBulkDownloadForbidden() *BulkDownloadForbidden { + + return &BulkDownloadForbidden{} +} + +// WithPayload adds the payload to the bulk download forbidden response +func (o *BulkDownloadForbidden) WithPayload(payload *ghcmessages.Error) *BulkDownloadForbidden { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the bulk download forbidden response +func (o *BulkDownloadForbidden) SetPayload(payload *ghcmessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *BulkDownloadForbidden) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(403) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// BulkDownloadNotFoundCode is the HTTP code returned for type BulkDownloadNotFound +const BulkDownloadNotFoundCode int = 404 + +/* +BulkDownloadNotFound The requested resource wasn't found + +swagger:response bulkDownloadNotFound +*/ +type BulkDownloadNotFound struct { + + /* + In: Body + */ + Payload *ghcmessages.Error `json:"body,omitempty"` +} + +// NewBulkDownloadNotFound creates BulkDownloadNotFound with default headers values +func NewBulkDownloadNotFound() *BulkDownloadNotFound { + + return &BulkDownloadNotFound{} +} + +// WithPayload adds the payload to the bulk download not found response +func (o *BulkDownloadNotFound) WithPayload(payload *ghcmessages.Error) *BulkDownloadNotFound { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the bulk download not found response +func (o *BulkDownloadNotFound) SetPayload(payload *ghcmessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *BulkDownloadNotFound) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(404) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// BulkDownloadInternalServerErrorCode is the HTTP code returned for type BulkDownloadInternalServerError +const BulkDownloadInternalServerErrorCode int = 500 + +/* +BulkDownloadInternalServerError A server error occurred + +swagger:response bulkDownloadInternalServerError +*/ +type BulkDownloadInternalServerError struct { + + /* + In: Body + */ + Payload *ghcmessages.Error `json:"body,omitempty"` +} + +// NewBulkDownloadInternalServerError creates BulkDownloadInternalServerError with default headers values +func NewBulkDownloadInternalServerError() *BulkDownloadInternalServerError { + + return &BulkDownloadInternalServerError{} +} + +// WithPayload adds the payload to the bulk download internal server error response +func (o *BulkDownloadInternalServerError) WithPayload(payload *ghcmessages.Error) *BulkDownloadInternalServerError { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the bulk download internal server error response +func (o *BulkDownloadInternalServerError) SetPayload(payload *ghcmessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *BulkDownloadInternalServerError) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(500) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download_urlbuilder.go b/pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download_urlbuilder.go new file mode 100644 index 00000000000..7f67d894191 --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download_urlbuilder.go @@ -0,0 +1,99 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package payment_requests + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" + "strings" +) + +// BulkDownloadURL generates an URL for the bulk download operation +type BulkDownloadURL struct { + PaymentRequestID string + + _basePath string + // avoid unkeyed usage + _ struct{} +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *BulkDownloadURL) WithBasePath(bp string) *BulkDownloadURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *BulkDownloadURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *BulkDownloadURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/payment-requests/{paymentRequestID}/bulkDownload" + + paymentRequestID := o.PaymentRequestID + if paymentRequestID != "" { + _path = strings.Replace(_path, "{paymentRequestID}", paymentRequestID, -1) + } else { + return nil, errors.New("paymentRequestId is required on BulkDownloadURL") + } + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/ghc/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *BulkDownloadURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *BulkDownloadURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *BulkDownloadURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on BulkDownloadURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on BulkDownloadURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *BulkDownloadURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/pkg/handlers/ghcapi/api.go b/pkg/handlers/ghcapi/api.go index 6cef37ab268..2b91ad51f8a 100644 --- a/pkg/handlers/ghcapi/api.go +++ b/pkg/handlers/ghcapi/api.go @@ -665,5 +665,11 @@ func NewGhcAPIHandler(handlerConfig handlers.HandlerConfig) *ghcops.MymoveAPI { order.NewOrderUpdater(moveRouter), } + paymentRequestPacketCreator := paymentrequest.NewPaymentRequestBulkDownloadCreator(pdfGenerator) + ghcAPI.PaymentRequestsBulkDownloadHandler = PaymentRequestBulkDownloadHandler{ + handlerConfig, + paymentRequestPacketCreator, + } + return ghcAPI } diff --git a/pkg/handlers/ghcapi/payment_request.go b/pkg/handlers/ghcapi/payment_request.go index 757993d2669..f2ede5a8d23 100644 --- a/pkg/handlers/ghcapi/payment_request.go +++ b/pkg/handlers/ghcapi/payment_request.go @@ -2,6 +2,7 @@ package ghcapi import ( "fmt" + "io" "reflect" "time" @@ -261,3 +262,39 @@ func (h ShipmentsSITBalanceHandler) Handle( return paymentrequestop.NewGetShipmentsPaymentSITBalanceOK().WithPayload(payload), nil }) } + +type PaymentRequestBulkDownloadHandler struct { + handlers.HandlerConfig + services.PaymentRequestPacketCreator +} + +func (h PaymentRequestBulkDownloadHandler) Handle(params paymentrequestop.BulkDownloadParams) middleware.Responder { + return h.AuditableAppContextFromRequestWithErrors(params.HTTPRequest, + func(appCtx appcontext.AppContext) (middleware.Responder, error) { + logger := appCtx.Logger() + + paymentRequestID, err := uuid.FromString(params.PaymentRequestID) + if err != nil { + errInstance := fmt.Sprintf("Instance: %s", h.GetTraceIDFromRequest(params.HTTPRequest)) + + errPayload := &ghcmessages.Error{Message: &errInstance} + + appCtx.Logger().Error(err.Error()) + return paymentrequestop.NewBulkDownloadBadRequest().WithPayload(errPayload), err + } + + paymentRequestPacket, err := h.PaymentRequestPacketCreator.CreatePaymentRequestPacket(appCtx, paymentRequestID) + if err != nil { + logger.Error("Error creating Payment Request Downloads Packet", zap.Error(err)) + errInstance := fmt.Sprintf("Instance: %s", h.GetTraceIDFromRequest(params.HTTPRequest)) + errPayload := &ghcmessages.Error{Message: &errInstance} + return paymentrequestop.NewBulkDownloadInternalServerError(). + WithPayload(errPayload), err + } + + payload := io.NopCloser(paymentRequestPacket) + filename := fmt.Sprintf("inline; filename=\"PaymentRequestBulkPacket-%s.pdf\"", time.Now().Format("01-02-2006_15-04-05")) + + return paymentrequestop.NewBulkDownloadOK().WithContentDisposition(filename).WithPayload(payload), nil + }) +} diff --git a/pkg/handlers/ghcapi/ppm_document.go b/pkg/handlers/ghcapi/ppm_document.go index ee92f7e5d29..ba1bf703822 100644 --- a/pkg/handlers/ghcapi/ppm_document.go +++ b/pkg/handlers/ghcapi/ppm_document.go @@ -222,7 +222,7 @@ func (h ShowPaymentPacketHandler) Handle(params ppmdocumentops.ShowPaymentPacket } payload := io.NopCloser(pdf) - filename := fmt.Sprintf("inline; filename=\"ppm_payment_packet-%s.pdf\"", time.Now().UTC().Format("2006-01-02T15:04:05.000Z")) + filename := fmt.Sprintf("inline; filename=\"ppm_payment_bulk_download_packet-%s.pdf\"", time.Now().UTC().Format("2006-01-02T15:04:05.000Z")) return ppmdocumentops.NewShowPaymentPacketOK().WithContentDisposition(filename).WithPayload(payload), nil }) diff --git a/pkg/services/payment_request.go b/pkg/services/payment_request.go index 5f81f2215c9..2b10ab0bb17 100644 --- a/pkg/services/payment_request.go +++ b/pkg/services/payment_request.go @@ -5,6 +5,7 @@ import ( "time" "github.com/gofrs/uuid" + "github.com/spf13/afero" "github.com/transcom/mymove/pkg/appcontext" "github.com/transcom/mymove/pkg/models" @@ -115,3 +116,7 @@ type ShipmentPaymentSITBalance struct { type ShipmentsPaymentSITBalance interface { ListShipmentPaymentSITBalance(appCtx appcontext.AppContext, paymentRequestID uuid.UUID) ([]ShipmentPaymentSITBalance, error) } + +type PaymentRequestPacketCreator interface { + CreatePaymentRequestPacket(appCtx appcontext.AppContext, paymentRequestID uuid.UUID) (afero.File, error) +} diff --git a/pkg/services/payment_request/payment_request_packed_creator.go b/pkg/services/payment_request/payment_request_packed_creator.go new file mode 100644 index 00000000000..3d989413de9 --- /dev/null +++ b/pkg/services/payment_request/payment_request_packed_creator.go @@ -0,0 +1,57 @@ +package paymentrequest + +import ( + "fmt" + + "github.com/gofrs/uuid" + "github.com/spf13/afero" + + "github.com/transcom/mymove/pkg/appcontext" + "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/paperwork" + "github.com/transcom/mymove/pkg/services" +) + +type paymentRequestBulkDownloadCreator struct { + pdfGenerator *paperwork.Generator +} + +func NewPaymentRequestBulkDownloadCreator(pdfGenerator *paperwork.Generator) services.PaymentRequestPacketCreator { + return &paymentRequestBulkDownloadCreator{ + pdfGenerator, + } +} + +func (p *paymentRequestBulkDownloadCreator) CreatePaymentRequestPacket(appCtx appcontext.AppContext, paymentRequestID uuid.UUID) (afero.File, error) { + errMsgPrefix := "error creating Payment Request packet" + + paymentRequest := models.PaymentRequest{} + err := appCtx.DB().Q().Eager( + "MoveTaskOrder", + "ProofOfServiceDocs", + "ProofOfServiceDocs.PrimeUploads", + "ProofOfServiceDocs.PrimeUploads.Upload", + ).Find(&paymentRequest, paymentRequestID) + if err != nil { + return nil, fmt.Errorf("%s: %w", errMsgPrefix, err) + } + + var primeUploads models.Uploads + for _, serviceDoc := range paymentRequest.ProofOfServiceDocs { + for _, upload := range serviceDoc.PrimeUploads { + primeUploads = append(primeUploads, upload.Upload) + } + } + + pdfs, err := p.pdfGenerator.ConvertUploadsToPDF(appCtx, primeUploads) + if err != nil { + return nil, fmt.Errorf("%s error generating pdf", err) + } + + pdfFile, err := p.pdfGenerator.MergePDFFiles(appCtx, pdfs) + if err != nil { + return nil, fmt.Errorf("%s error generating merged pdf", err) + } + + return pdfFile, nil +} diff --git a/src/pages/Office/PaymentRequestReview/PaymentRequestReview.jsx b/src/pages/Office/PaymentRequestReview/PaymentRequestReview.jsx index e4a3f2c576f..041dcdeebb7 100644 --- a/src/pages/Office/PaymentRequestReview/PaymentRequestReview.jsx +++ b/src/pages/Office/PaymentRequestReview/PaymentRequestReview.jsx @@ -11,10 +11,11 @@ import SomethingWentWrong from 'shared/SomethingWentWrong'; import DocumentViewer from 'components/DocumentViewer/DocumentViewer'; import ReviewServiceItems from 'components/Office/ReviewServiceItems/ReviewServiceItems'; import { LOA_TYPE, PAYMENT_REQUEST_STATUS } from 'shared/constants'; -import { patchPaymentRequest, patchPaymentServiceItemStatus } from 'services/ghcApi'; +import { bulkDownloadPaymentRequest, patchPaymentRequest, patchPaymentServiceItemStatus } from 'services/ghcApi'; import { usePaymentRequestQueries } from 'hooks/queries'; import { PAYMENT_REQUESTS } from 'constants/queryKeys'; import { OrderShape } from 'types'; +import AsyncPacketDownloadLink from 'shared/AsyncPacketDownloadLink/AsyncPacketDownloadLink'; export const PaymentRequestReview = ({ order }) => { const navigate = useNavigate(); @@ -172,10 +173,31 @@ export const PaymentRequestReview = ({ order }) => { navigate(`/moves/${moveCode}/payment-requests`); }; + const paymentPacketDownload = ( +

+
+

+ +

+
+
+ ); + return (
- {uploads.length > 0 ? :

No documents provided

} + {uploads.length > 0 ? ( + <> + {paymentPacketDownload} + + + ) : ( +

No documents provided

+ )}
+ This endpoint downloads all uploaded payment request documentation + combined into a single PDF. + operationId: bulkDownload + tags: + - paymentRequests + produces: + - application/pdf + responses: + '200': + headers: + Content-Disposition: + type: string + description: File name to download + description: Payment Request Files PDF + schema: + format: binary + type: file + '400': + $ref: '#/responses/InvalidRequest' + '403': + $ref: '#/responses/PermissionDenied' + '404': + $ref: '#/responses/NotFound' + '500': + $ref: '#/responses/ServerError' /documents/{documentId}: get: summary: Returns a document From eacfa51633f174012eb89aa2399eeb32a922dc69 Mon Sep 17 00:00:00 2001 From: loganwc Date: Fri, 30 Aug 2024 14:43:49 +0000 Subject: [PATCH 11/68] pdf generator does not rotate images anymore --- pkg/handlers/ghcapi/api.go | 6 +- pkg/handlers/ghcapi/payment_request.go | 4 +- pkg/handlers/ghcapi/ppm_document.go | 6 +- pkg/handlers/ghcapi/ppm_document_test.go | 6 +- pkg/paperwork/generator.go | 128 +++++++++++++++++- pkg/paperwork/generator_test.go | 61 ++++++++- pkg/services/payment_request.go | 4 +- ... payment_request_bulk_download_creator.go} | 6 +- ...ment_request_bulk_download_creator_test.go | 36 +++++ .../PaymentRequestReview.jsx | 2 +- .../PaymentRequestReview.test.jsx | 10 +- 11 files changed, 243 insertions(+), 26 deletions(-) rename pkg/services/payment_request/{payment_request_packed_creator.go => payment_request_bulk_download_creator.go} (89%) create mode 100644 pkg/services/payment_request/payment_request_bulk_download_creator_test.go diff --git a/pkg/handlers/ghcapi/api.go b/pkg/handlers/ghcapi/api.go index 2b91ad51f8a..63db2004d53 100644 --- a/pkg/handlers/ghcapi/api.go +++ b/pkg/handlers/ghcapi/api.go @@ -637,7 +637,7 @@ func NewGhcAPIHandler(handlerConfig handlers.HandlerConfig) *ghcops.MymoveAPI { } ppmShipmentFetcher := ppmshipment.NewPPMShipmentFetcher() paymentPacketCreator := ppmshipment.NewPaymentPacketCreator(ppmShipmentFetcher, pdfGenerator, AOAPacketCreator) - ghcAPI.PpmShowPaymentPacketHandler = ShowPaymentPacketHandler{handlerConfig, paymentPacketCreator} + ghcAPI.PpmShowPaymentPacketHandler = ShowPaymentRequestBulkDownloadHandler{handlerConfig, paymentPacketCreator} ghcAPI.UploadsCreateUploadHandler = CreateUploadHandler{handlerConfig} ghcAPI.UploadsDeleteUploadHandler = DeleteUploadHandler{handlerConfig, upload.NewUploadInformationFetcher()} @@ -665,10 +665,10 @@ func NewGhcAPIHandler(handlerConfig handlers.HandlerConfig) *ghcops.MymoveAPI { order.NewOrderUpdater(moveRouter), } - paymentRequestPacketCreator := paymentrequest.NewPaymentRequestBulkDownloadCreator(pdfGenerator) + paymentRequestBulkDownloadCreator := paymentrequest.NewPaymentRequestBulkDownloadCreator(pdfGenerator) ghcAPI.PaymentRequestsBulkDownloadHandler = PaymentRequestBulkDownloadHandler{ handlerConfig, - paymentRequestPacketCreator, + paymentRequestBulkDownloadCreator, } return ghcAPI diff --git a/pkg/handlers/ghcapi/payment_request.go b/pkg/handlers/ghcapi/payment_request.go index f2ede5a8d23..622d38b1abc 100644 --- a/pkg/handlers/ghcapi/payment_request.go +++ b/pkg/handlers/ghcapi/payment_request.go @@ -265,7 +265,7 @@ func (h ShipmentsSITBalanceHandler) Handle( type PaymentRequestBulkDownloadHandler struct { handlers.HandlerConfig - services.PaymentRequestPacketCreator + services.PaymentRequestBulkDownloadCreator } func (h PaymentRequestBulkDownloadHandler) Handle(params paymentrequestop.BulkDownloadParams) middleware.Responder { @@ -283,7 +283,7 @@ func (h PaymentRequestBulkDownloadHandler) Handle(params paymentrequestop.BulkDo return paymentrequestop.NewBulkDownloadBadRequest().WithPayload(errPayload), err } - paymentRequestPacket, err := h.PaymentRequestPacketCreator.CreatePaymentRequestPacket(appCtx, paymentRequestID) + paymentRequestPacket, err := h.PaymentRequestBulkDownloadCreator.CreatePaymentRequestBulkDownload(appCtx, paymentRequestID) if err != nil { logger.Error("Error creating Payment Request Downloads Packet", zap.Error(err)) errInstance := fmt.Sprintf("Instance: %s", h.GetTraceIDFromRequest(params.HTTPRequest)) diff --git a/pkg/handlers/ghcapi/ppm_document.go b/pkg/handlers/ghcapi/ppm_document.go index ba1bf703822..32a9c021545 100644 --- a/pkg/handlers/ghcapi/ppm_document.go +++ b/pkg/handlers/ghcapi/ppm_document.go @@ -193,14 +193,14 @@ func (h showAOAPacketHandler) Handle(params ppmdocumentops.ShowAOAPacketParams) }) } -// ShowPaymentPacketHandler returns a PPM Payment Packet PDF -type ShowPaymentPacketHandler struct { +// ShowPaymentRequestBulkDownloadHandler returns a PPM Payment Packet PDF +type ShowPaymentRequestBulkDownloadHandler struct { handlers.HandlerConfig services.PaymentPacketCreator } // Handle returns a generated PDF -func (h ShowPaymentPacketHandler) Handle(params ppmdocumentops.ShowPaymentPacketParams) middleware.Responder { +func (h ShowPaymentRequestBulkDownloadHandler) Handle(params ppmdocumentops.ShowPaymentPacketParams) middleware.Responder { return h.AuditableAppContextFromRequestWithErrors(params.HTTPRequest, func(appCtx appcontext.AppContext) (middleware.Responder, error) { ppmShipmentID, err := uuid.FromString(params.PpmShipmentID.String()) diff --git a/pkg/handlers/ghcapi/ppm_document_test.go b/pkg/handlers/ghcapi/ppm_document_test.go index 304a3b7a267..334a8d8af14 100644 --- a/pkg/handlers/ghcapi/ppm_document_test.go +++ b/pkg/handlers/ghcapi/ppm_document_test.go @@ -702,7 +702,7 @@ func (suite *HandlerSuite) TestShowPaymentPacketHandler() { suite.Run("Successful ShowAOAPacketHandler - 200", func() { mockPaymentPacketCreator := mocks.PaymentPacketCreator{} - handler := ShowPaymentPacketHandler{ + handler := ShowPaymentRequestBulkDownloadHandler{ HandlerConfig: suite.createS3HandlerConfig(), PaymentPacketCreator: &mockPaymentPacketCreator, } @@ -728,7 +728,7 @@ func (suite *HandlerSuite) TestShowPaymentPacketHandler() { suite.Run("Unsuccessful ShowPaymentPacketHandler - InternalServerError", func() { mockPaymentPacketCreator := mocks.PaymentPacketCreator{} - handler := ShowPaymentPacketHandler{ + handler := ShowPaymentRequestBulkDownloadHandler{ HandlerConfig: suite.createS3HandlerConfig(), PaymentPacketCreator: &mockPaymentPacketCreator, } @@ -754,7 +754,7 @@ func (suite *HandlerSuite) TestShowPaymentPacketHandler() { suite.Run("Unsuccessful ShowPaymentPacketHandler - NotFoundError", func() { mockPaymentPacketCreator := mocks.PaymentPacketCreator{} - handler := ShowPaymentPacketHandler{ + handler := ShowPaymentRequestBulkDownloadHandler{ HandlerConfig: suite.createS3HandlerConfig(), PaymentPacketCreator: &mockPaymentPacketCreator, } diff --git a/pkg/paperwork/generator.go b/pkg/paperwork/generator.go index 881f58c454a..51b35e360a1 100644 --- a/pkg/paperwork/generator.go +++ b/pkg/paperwork/generator.go @@ -213,7 +213,7 @@ func (g *Generator) GetPdfFileInfoByContents(file afero.File) (*pdfcpu.PDFInfo, // CreateMergedPDFUpload converts Uploads to PDF and merges them into a single PDF func (g *Generator) CreateMergedPDFUpload(appCtx appcontext.AppContext, uploads models.Uploads) (afero.File, error) { - pdfs, err := g.ConvertUploadsToPDF(appCtx, uploads) + pdfs, err := g.ConvertUploadsToPDF(appCtx, uploads, true) if err != nil { return nil, errors.Wrap(err, "Error while converting uploads") } @@ -227,7 +227,7 @@ func (g *Generator) CreateMergedPDFUpload(appCtx appcontext.AppContext, uploads } // ConvertUploadsToPDF turns a slice of Uploads into a slice of paths to converted PDF files -func (g *Generator) ConvertUploadsToPDF(appCtx appcontext.AppContext, uploads models.Uploads) ([]string, error) { +func (g *Generator) ConvertUploadsToPDF(appCtx appcontext.AppContext, uploads models.Uploads, doRotation bool) ([]string, error) { // tempfile paths to be returned pdfs := make([]string, 0) @@ -240,9 +240,18 @@ func (g *Generator) ConvertUploadsToPDF(appCtx appcontext.AppContext, uploads mo if len(images) > 0 { // We want to retain page order and will generate a PDF for images // that have already been encountered before handling this PDF. - pdf, err := g.PDFFromImages(appCtx, images) - if err != nil { - return nil, errors.Wrap(err, "Converting images") + var pdf string + var err error + if doRotation { + pdf, err = g.PDFFromImages(appCtx, images) + if err != nil { + return nil, errors.Wrap(err, "Converting images") + } + } else { + pdf, err = g.PDFFromImagesNoRotation(appCtx, images) + if err != nil { + return nil, errors.Wrap(err, "Converting images") + } } pdfs = append(pdfs, pdf) images = make([]inputFile, 0) @@ -514,6 +523,115 @@ func (g *Generator) PDFFromImages(appCtx appcontext.AppContext, images []inputFi return outputFile.Name(), nil } +// PDFFromImages returns the path to tempfile PDF containing all images included +// in urls. +// +// The files at those paths will be tempfiles that will need to be cleaned +// up by the caller. +func (g *Generator) PDFFromImagesNoRotation(appCtx appcontext.AppContext, images []inputFile) (string, error) { + // These constants are based on A4 page size, which we currently default to. + horizontalMargin := 0.0 + topMargin := 0.0 + bodyWidth := PdfPageWidth - (horizontalMargin * 2) + bodyHeight := PdfPageHeight - (topMargin * 2) + wToHRatio := bodyWidth / bodyHeight + + pdf := gofpdf.New(PdfOrientation, PdfUnit, PdfPageSize, PdfFontDir) + pdf.SetMargins(horizontalMargin, topMargin, horizontalMargin) + + if len(images) == 0 { + return "", errors.New("No images provided") + } + + appCtx.Logger().Debug("generating PDF from image files", zap.Any("images", images)) + + outputFile, err := g.newTempFile() + if err != nil { + return "", err + } + + defer func() { + if closeErr := outputFile.Close(); closeErr != nil { + appCtx.Logger().Debug("Failed to close file", zap.Error(closeErr)) + } + }() + + var opt gofpdf.ImageOptions + for _, img := range images { + pdf.AddPage() + file, openErr := g.fs.Open(img.Path) + if openErr != nil { + return "", errors.Wrap(openErr, "Opening image file") + } + + defer func() { + if closeErr := file.Close(); closeErr != nil { + appCtx.Logger().Debug("Failed to close file", zap.Error(closeErr)) + } + }() + + if img.ContentType == uploader.FileTypePNG { + appCtx.Logger().Debug("Converting png to 8-bit") + // gofpdf isn't able to process 16-bit PNGs, so to be safe we convert all PNGs to an 8-bit color depth + newFile, newTemplateFileErr := g.newTempFile() + if newTemplateFileErr != nil { + return "", errors.Wrap(newTemplateFileErr, "Creating temp file for png conversion") + } + + defer func() { + if closeErr := newFile.Close(); closeErr != nil { + appCtx.Logger().Debug("Failed to close file", zap.Error(closeErr)) + } + }() + + convertTo8BitPNGErr := convertTo8BitPNG(file, newFile) + if convertTo8BitPNGErr != nil { + return "", errors.Wrap(convertTo8BitPNGErr, "Converting to 8-bit png") + } + file = newFile + _, fileSeekErr := file.Seek(0, io.SeekStart) + if fileSeekErr != nil { + return "", errors.Wrapf(fileSeekErr, "file.Seek offset: 0 whence: %d", io.SeekStart) + } + } + + widthInPdf := bodyWidth + heightInPdf := 0.0 + + // Scale using the imageOptions below + // BodyWidth should be set to 0 when the image height the proportion of the page + // is taller than wide as compared to an A4 page. + // + // The opposite is true and defaulted for when the image is wider than it is tall, + // in comparison to an A4 page. + if float64(bodyWidth/bodyHeight) < wToHRatio { + widthInPdf = 0 + heightInPdf = bodyHeight + } + + // Seek to the beginning of the file so when we register the image, it doesn't start + // at the end of the file. + _, fileSeekErr := file.Seek(0, io.SeekStart) + if fileSeekErr != nil { + return "", errors.Wrapf(fileSeekErr, "file.Seek offset: 0 whence: %d", io.SeekStart) + } + // Need to register the image using an afero reader, else it uses default filesystem + pdf.RegisterImageReader(img.Path, contentTypeToImageType[img.ContentType], file) + opt.ImageType = contentTypeToImageType[img.ContentType] + + pdf.ImageOptions(img.Path, horizontalMargin, topMargin, widthInPdf, heightInPdf, false, opt, 0, "") + fileCloseErr := file.Close() + if fileCloseErr != nil { + return "", errors.Wrapf(err, "error closing file: %s", file.Name()) + } + } + + if err = pdf.OutputAndClose(outputFile); err != nil { + return "", errors.Wrap(err, "could not write PDF to outputfile") + } + return outputFile.Name(), nil +} + // MergePDFFiles Merges a slice of paths to PDF files into a single PDF func (g *Generator) MergePDFFiles(_ appcontext.AppContext, paths []string) (afero.File, error) { var err error diff --git a/pkg/paperwork/generator_test.go b/pkg/paperwork/generator_test.go index 8b0eddea933..775c3207696 100644 --- a/pkg/paperwork/generator_test.go +++ b/pkg/paperwork/generator_test.go @@ -143,6 +143,65 @@ func (suite *PaperworkSuite) TestPDFFromImages() { suite.Contains(checksums, orders2Checksum, "did not find hash for orders2.jpg") } +func (suite *PaperworkSuite) TestPDFFromImagesNoRotation() { + generator, newGeneratorErr := NewGenerator(suite.userUploader.Uploader()) + suite.FatalNil(newGeneratorErr) + + images := []inputFile{ + {Path: "testdata/orders1.jpg", ContentType: uploader.FileTypeJPEG}, + {Path: "testdata/orders2.jpg", ContentType: uploader.FileTypeJPEG}, + } + for _, image := range images { + _, err := suite.openLocalFile(image.Path, generator.fs) + suite.FatalNil(err) + } + + generatedPath, err := generator.PDFFromImagesNoRotation(suite.AppContextForTest(), images) + suite.FatalNil(err, "failed to generate pdf") + aferoFile, err := generator.fs.Open(generatedPath) + suite.FatalNil(err, "afero failed to open pdf") + + suite.NotEmpty(generatedPath, "got an empty path to the generated file") + suite.FatalNil(err) + + // verify that the images are in the pdf by extracting them and checking their checksums + file, err := afero.ReadAll(aferoFile) + suite.FatalNil(err) + tmpDir, err := os.MkdirTemp("", "images") + suite.FatalNil(err) + f, err := os.CreateTemp(tmpDir, "") + suite.FatalNil(err) + err = os.WriteFile(f.Name(), file, os.ModePerm) + suite.FatalNil(err) + err = api.ExtractImagesFile(f.Name(), tmpDir, []string{"-2"}, generator.pdfConfig) + suite.FatalNil(err) + err = os.Remove(f.Name()) + suite.FatalNil(err) + + checksums := make([]string, 2) + files, err := os.ReadDir(tmpDir) + suite.FatalNil(err) + + suite.Equal(4, len(files), "did not find 2 images") + + for _, file := range files { + checksum, sha256ForPathErr := suite.sha256ForPath(path.Join(tmpDir, file.Name()), nil) + suite.FatalNil(sha256ForPathErr, "error calculating hash") + if sha256ForPathErr != nil { + suite.FailNow(sha256ForPathErr.Error()) + } + checksums = append(checksums, checksum) + } + + orders1Checksum, err := suite.sha256ForPath("testdata/orders1.jpg", generator.fs) + suite.Nil(err, "error calculating hash") + suite.Contains(checksums, orders1Checksum, "did not find hash for orders1.jpg") + + orders2Checksum, err := suite.sha256ForPath("testdata/orders2.jpg", generator.fs) + suite.Nil(err, "error calculating hash") + suite.Contains(checksums, orders2Checksum, "did not find hash for orders2.jpg") +} + func (suite *PaperworkSuite) TestPDFFromImages16BitPNG() { generator, err := NewGenerator(suite.userUploader.Uploader()) suite.FatalNil(err) @@ -187,7 +246,7 @@ func (suite *PaperworkSuite) TestGenerateUploadsPDF() { uploads, err := models.UploadsFromUserUploads(suite.DB(), order.UploadedOrders.UserUploads) suite.FatalNil(err) - paths, err := generator.ConvertUploadsToPDF(suite.AppContextForTest(), uploads) + paths, err := generator.ConvertUploadsToPDF(suite.AppContextForTest(), uploads, true) suite.FatalNil(err) suite.Equal(3, len(paths), "wrong number of paths returned") diff --git a/pkg/services/payment_request.go b/pkg/services/payment_request.go index 2b10ab0bb17..25f62be43a4 100644 --- a/pkg/services/payment_request.go +++ b/pkg/services/payment_request.go @@ -117,6 +117,6 @@ type ShipmentsPaymentSITBalance interface { ListShipmentPaymentSITBalance(appCtx appcontext.AppContext, paymentRequestID uuid.UUID) ([]ShipmentPaymentSITBalance, error) } -type PaymentRequestPacketCreator interface { - CreatePaymentRequestPacket(appCtx appcontext.AppContext, paymentRequestID uuid.UUID) (afero.File, error) +type PaymentRequestBulkDownloadCreator interface { + CreatePaymentRequestBulkDownload(appCtx appcontext.AppContext, paymentRequestID uuid.UUID) (afero.File, error) } diff --git a/pkg/services/payment_request/payment_request_packed_creator.go b/pkg/services/payment_request/payment_request_bulk_download_creator.go similarity index 89% rename from pkg/services/payment_request/payment_request_packed_creator.go rename to pkg/services/payment_request/payment_request_bulk_download_creator.go index 3d989413de9..24fb7d590c7 100644 --- a/pkg/services/payment_request/payment_request_packed_creator.go +++ b/pkg/services/payment_request/payment_request_bulk_download_creator.go @@ -16,13 +16,13 @@ type paymentRequestBulkDownloadCreator struct { pdfGenerator *paperwork.Generator } -func NewPaymentRequestBulkDownloadCreator(pdfGenerator *paperwork.Generator) services.PaymentRequestPacketCreator { +func NewPaymentRequestBulkDownloadCreator(pdfGenerator *paperwork.Generator) services.PaymentRequestBulkDownloadCreator { return &paymentRequestBulkDownloadCreator{ pdfGenerator, } } -func (p *paymentRequestBulkDownloadCreator) CreatePaymentRequestPacket(appCtx appcontext.AppContext, paymentRequestID uuid.UUID) (afero.File, error) { +func (p *paymentRequestBulkDownloadCreator) CreatePaymentRequestBulkDownload(appCtx appcontext.AppContext, paymentRequestID uuid.UUID) (afero.File, error) { errMsgPrefix := "error creating Payment Request packet" paymentRequest := models.PaymentRequest{} @@ -43,7 +43,7 @@ func (p *paymentRequestBulkDownloadCreator) CreatePaymentRequestPacket(appCtx ap } } - pdfs, err := p.pdfGenerator.ConvertUploadsToPDF(appCtx, primeUploads) + pdfs, err := p.pdfGenerator.ConvertUploadsToPDF(appCtx, primeUploads, false) if err != nil { return nil, fmt.Errorf("%s error generating pdf", err) } diff --git a/pkg/services/payment_request/payment_request_bulk_download_creator_test.go b/pkg/services/payment_request/payment_request_bulk_download_creator_test.go new file mode 100644 index 00000000000..9db695dd54a --- /dev/null +++ b/pkg/services/payment_request/payment_request_bulk_download_creator_test.go @@ -0,0 +1,36 @@ +package paymentrequest + +import ( + "github.com/transcom/mymove/pkg/factory" + "github.com/transcom/mymove/pkg/models" + paperworkgenerator "github.com/transcom/mymove/pkg/paperwork" + storageTest "github.com/transcom/mymove/pkg/storage/test" + "github.com/transcom/mymove/pkg/uploader" +) + +func (suite *PaymentRequestServiceSuite) TestCreatePaymentRequestBulkDownload() { + fakeS3 := storageTest.NewFakeS3Storage(true) + userUploader, uploaderErr := uploader.NewUserUploader(fakeS3, 25*uploader.MB) + suite.FatalNoError(uploaderErr) + + generator, err := paperworkgenerator.NewGenerator(userUploader.Uploader()) + suite.FatalNil(err) + + primeUpload := factory.BuildPrimeUpload(suite.DB(), nil, nil) + suite.FatalNil(err) + if generator != nil { + suite.FatalNil(err) + } + + paymentRequest := factory.BuildPaymentRequest(suite.DB(), []factory.Customization{ + { + Model: models.PaymentRequest{ + ProofOfServiceDocs: models.ProofOfServiceDocs{ + primeUpload.ProofOfServiceDoc, + }, + }, + }, + }, nil) + + suite.NotNil(paymentRequest) +} diff --git a/src/pages/Office/PaymentRequestReview/PaymentRequestReview.jsx b/src/pages/Office/PaymentRequestReview/PaymentRequestReview.jsx index 041dcdeebb7..ea2e1ca1c1b 100644 --- a/src/pages/Office/PaymentRequestReview/PaymentRequestReview.jsx +++ b/src/pages/Office/PaymentRequestReview/PaymentRequestReview.jsx @@ -175,7 +175,7 @@ export const PaymentRequestReview = ({ order }) => { const paymentPacketDownload = (
-
+

{ expect(terms[1]).toHaveTextContent('Accepted'); expect(terms[2]).toHaveTextContent('Rejected'); const definitions = screen.getAllByRole('definition'); - expect(definitions[0]).toHaveTextContent('$1,703.10'); - expect(definitions[1]).toHaveTextContent('$1,579.98'); - expect(definitions[2]).toHaveTextContent('$123.12'); + expect(definitions[1]).toHaveTextContent('$1,703.10'); + expect(definitions[2]).toHaveTextContent('$1,579.98'); + expect(definitions[3]).toHaveTextContent('$123.12'); }); it('navigates back, and shows the correct icons for approved and rejected cards', async () => { await userEvent.click(screen.getByRole('button', { name: 'Back' })); @@ -497,6 +497,10 @@ describe('PaymentRequestReview', () => { await userEvent.click(screen.getByRole('button', { name: 'Previous Service Item' })); expect(screen.getByTestId('statusHeading')).toHaveTextContent('Accepted'); }); + it('shows the Download All Files (PDF) button and downloads the pdf', async () => { + expect(screen.getByText('Download All Files (PDF)')).toBeInTheDocument(); + await userEvent.click(screen.getByText('Download All Files (PDF)')); + }); }); }); }); From 7a2ab809e6a1c2ea7df0aeb7afdf3ddbce33a20f Mon Sep 17 00:00:00 2001 From: Maria Traskowsky Date: Tue, 3 Sep 2024 19:52:11 +0000 Subject: [PATCH 12/68] set ReceivedByGexAt when processing 997s --- pkg/services/invoice/process_edi997.go | 3 +++ pkg/services/invoice/process_edi997_test.go | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/pkg/services/invoice/process_edi997.go b/pkg/services/invoice/process_edi997.go index c67fbc1227b..27f65603b2a 100644 --- a/pkg/services/invoice/process_edi997.go +++ b/pkg/services/invoice/process_edi997.go @@ -2,6 +2,7 @@ package invoice import ( "fmt" + "time" "github.com/gofrs/uuid" "go.uber.org/zap" @@ -105,6 +106,8 @@ func (e *edi997Processor) ProcessFile(appCtx appcontext.AppContext, _ string, st } paymentRequest.Status = models.PaymentRequestStatusTppsReceived + ReceivedByGexAt := time.Now() + paymentRequest.ReceivedByGexAt = &ReceivedByGexAt err = txnAppCtx.DB().Update(&paymentRequest) if err != nil { txnAppCtx.Logger().Error("failure updating payment request", zap.Error(err)) diff --git a/pkg/services/invoice/process_edi997_test.go b/pkg/services/invoice/process_edi997_test.go index 7287b043c7f..39d4c34cba3 100644 --- a/pkg/services/invoice/process_edi997_test.go +++ b/pkg/services/invoice/process_edi997_test.go @@ -196,6 +196,7 @@ IEA*1*000000995 err = suite.DB().Where("id = ?", paymentRequest.ID).First(&updatedPR) suite.NoError(err) suite.Equal(models.PaymentRequestStatusTppsReceived, updatedPR.Status) + suite.NotNil(updatedPR.ReceivedByGexAt) }) suite.Run("can handle 997 and 858 with same ICN", func() { @@ -250,6 +251,7 @@ IEA*1*000000995 err = suite.DB().Where("id = ?", paymentRequest.ID).First(&updatedPR) suite.FatalNoError(err) suite.Equal(models.PaymentRequestStatusTppsReceived, updatedPR.Status) + suite.NotNil(updatedPR.ReceivedByGexAt) }) suite.Run("does not error out if edi with same icn is processed for the same payment request", func() { @@ -304,6 +306,7 @@ IEA*1*000000995 err = suite.DB().Where("id = ?", paymentRequest.ID).First(&updatedPR) suite.FatalNoError(err) suite.Equal(models.PaymentRequestStatusTppsReceived, updatedPR.Status) + suite.NotNil(updatedPR.ReceivedByGexAt) }) suite.Run("doesn't update a payment request status after processing an invalid EDI997", func() { @@ -345,6 +348,7 @@ IEA*1*000000022 err = suite.DB().Where("id = ?", paymentRequest.ID).First(&updatedPR) suite.NoError(err) suite.Equal(models.PaymentRequestStatusSentToGex, updatedPR.Status) + suite.Nil(updatedPR.ReceivedByGexAt) }) suite.Run("throw an error when edi997 is missing a transaction set", func() { From ccc8c66b56241533be70512f18fb24d79eb39eca Mon Sep 17 00:00:00 2001 From: Maria Traskowsky Date: Tue, 3 Sep 2024 21:23:27 +0000 Subject: [PATCH 13/68] refactor payment request status details to always show approved rejected --- .../PaymentRequestCard/PaymentRequestCard.jsx | 72 ++++++------------- 1 file changed, 23 insertions(+), 49 deletions(-) diff --git a/src/components/Office/PaymentRequestCard/PaymentRequestCard.jsx b/src/components/Office/PaymentRequestCard/PaymentRequestCard.jsx index 8e1ed250eec..1095ee87bd1 100644 --- a/src/components/Office/PaymentRequestCard/PaymentRequestCard.jsx +++ b/src/components/Office/PaymentRequestCard/PaymentRequestCard.jsx @@ -265,12 +265,8 @@ const PaymentRequestCard = ({ ); }; - const renderPaymentRequestDetailsForStatus = (paymentRequestStatus) => { - if ( - (paymentRequestStatus === PAYMENT_REQUEST_STATUS.PAID || - paymentRequestStatus === PAYMENT_REQUEST_STATUS.EDI_ERROR) && - tppsInvoiceSellerPaidDate - ) { + const renderApprovedRejectedPaymentRequestDetails = () => { + if (approvedAmount > 0 || rejectedAmount > 0) { return (

{approvedAmount > 0 && ( @@ -293,6 +289,20 @@ const PaymentRequestCard = ({
)} +
+ ); + } + return
; + }; + + const renderPaymentRequestDetailsForStatus = (paymentRequestStatus) => { + if ( + (paymentRequestStatus === PAYMENT_REQUEST_STATUS.PAID || + paymentRequestStatus === PAYMENT_REQUEST_STATUS.EDI_ERROR) && + tppsInvoiceSellerPaidDate + ) { + return ( +
{tppsInvoiceAmountPaidTotalMillicents > 0 && (
@@ -313,22 +323,12 @@ const PaymentRequestCard = ({ ) { return (
- {approvedAmount > 0 && ( + {paymentRequest.receivedByGexAt && (

{toDollarString(formatCents(approvedAmount))}

- Received - on {formatDateFromIso(paymentRequest.receivedByGexAt, 'DD MMM YYYY')} -
-
- )} - {rejectedAmount > 0 && ( -
- -
-

{toDollarString(formatCents(rejectedAmount))}

- Rejected + TPPS Received on {formatDateFromIso(paymentRequest.receivedByGexAt, 'DD MMM YYYY')}
@@ -336,36 +336,7 @@ const PaymentRequestCard = ({
); } - if ( - paymentRequestStatus === PAYMENT_REQUEST_STATUS.REVIEWED || - paymentRequestStatus === PAYMENT_REQUEST_STATUS.REVIEWED_AND_ALL_SERVICE_ITEMS_REJECTED || - paymentRequestStatus === PAYMENT_REQUEST_STATUS.EDI_ERROR - ) { - return ( -
- {approvedAmount > 0 && ( -
- -
-

{toDollarString(formatCents(approvedAmount))}

- Accepted - on {formatDateFromIso(paymentRequest.reviewedAt, 'DD MMM YYYY')} -
-
- )} - {rejectedAmount > 0 && ( -
- -
-

{toDollarString(formatCents(rejectedAmount))}

- Rejected - on {formatDateFromIso(paymentRequest.reviewedAt, 'DD MMM YYYY')} -
-
- )} -
- ); - } + if ( paymentRequestStatus === PAYMENT_REQUEST_STATUS.SENT_TO_GEX || (paymentRequestStatus === PAYMENT_REQUEST_STATUS.EDI_ERROR && approvedAmount > 0) @@ -420,7 +391,10 @@ const PaymentRequestCard = ({
-
{paymentRequest.status && renderPaymentRequestDetailsForStatus(paymentRequest.status)}
+
+ {paymentRequest.status && renderApprovedRejectedPaymentRequestDetails(paymentRequest)} + {paymentRequest.status && renderPaymentRequestDetailsForStatus(paymentRequest.status)} +
{paymentRequest.status === PAYMENT_REQUEST_STATUS.PENDING && renderReviewServiceItemsBtnForTIOandTOO()}
{ediErrorsExistForPaymentRequest && renderEDIErrorDetails()} From 8d2504892355a072dc88225551ada8e66c7b69d6 Mon Sep 17 00:00:00 2001 From: loganwc Date: Tue, 3 Sep 2024 21:32:14 +0000 Subject: [PATCH 14/68] more work on test, still failing --- ...ment_request_bulk_download_creator_test.go | 45 +++++++++++-------- .../payment_request_service_test.go | 27 ++++++++++- 2 files changed, 52 insertions(+), 20 deletions(-) diff --git a/pkg/services/payment_request/payment_request_bulk_download_creator_test.go b/pkg/services/payment_request/payment_request_bulk_download_creator_test.go index 9db695dd54a..fd1c967d091 100644 --- a/pkg/services/payment_request/payment_request_bulk_download_creator_test.go +++ b/pkg/services/payment_request/payment_request_bulk_download_creator_test.go @@ -2,35 +2,42 @@ package paymentrequest import ( "github.com/transcom/mymove/pkg/factory" - "github.com/transcom/mymove/pkg/models" paperworkgenerator "github.com/transcom/mymove/pkg/paperwork" - storageTest "github.com/transcom/mymove/pkg/storage/test" "github.com/transcom/mymove/pkg/uploader" ) func (suite *PaymentRequestServiceSuite) TestCreatePaymentRequestBulkDownload() { - fakeS3 := storageTest.NewFakeS3Storage(true) - userUploader, uploaderErr := uploader.NewUserUploader(fakeS3, 25*uploader.MB) - suite.FatalNoError(uploaderErr) + primeUploader, err := uploader.NewPrimeUploader(suite.storer, 25*uploader.MB) + suite.NoError(err) - generator, err := paperworkgenerator.NewGenerator(userUploader.Uploader()) + generator, err := paperworkgenerator.NewGenerator(primeUploader.Uploader()) suite.FatalNil(err) - primeUpload := factory.BuildPrimeUpload(suite.DB(), nil, nil) - suite.FatalNil(err) - if generator != nil { - suite.FatalNil(err) - } - - paymentRequest := factory.BuildPaymentRequest(suite.DB(), []factory.Customization{ + paymentRequest := factory.BuildPaymentRequest(suite.DB(), nil, nil) + primeUpload := factory.BuildPrimeUpload(suite.DB(), []factory.Customization{ { - Model: models.PaymentRequest{ - ProofOfServiceDocs: models.ProofOfServiceDocs{ - primeUpload.ProofOfServiceDoc, - }, - }, + Model: paymentRequest, + LinkOnly: true, }, }, nil) + posd := factory.BuildProofOfServiceDoc(suite.DB(), []factory.Customization{ + { + Model: primeUpload, + LinkOnly: true, + }, + { + Model: paymentRequest, + LinkOnly: true, + }, + }, nil) + + paymentRequest.ProofOfServiceDocs = append(paymentRequest.ProofOfServiceDocs, posd) + + creator := &paymentRequestBulkDownloadCreator{ + pdfGenerator: generator, + } - suite.NotNil(paymentRequest) + bulkDownload, err := creator.CreatePaymentRequestBulkDownload(suite.AppContextForTest(), paymentRequest.ID) + suite.NoError(err) + suite.NotNil(bulkDownload) } diff --git a/pkg/services/payment_request/payment_request_service_test.go b/pkg/services/payment_request/payment_request_service_test.go index f13384719a9..37864593901 100644 --- a/pkg/services/payment_request/payment_request_service_test.go +++ b/pkg/services/payment_request/payment_request_service_test.go @@ -1,18 +1,24 @@ package paymentrequest import ( + "io" + "os" + "path/filepath" "testing" "github.com/spf13/afero" "github.com/stretchr/testify/suite" + "go.uber.org/zap" + "github.com/transcom/mymove/pkg/storage" "github.com/transcom/mymove/pkg/testingsuite" ) // PaymentRequestServiceSuite is a suite for testing payment requests type PaymentRequestServiceSuite struct { *testingsuite.PopTestSuite - fs *afero.Afero + fs *afero.Afero + storer storage.FileStorer } func TestPaymentRequestServiceSuite(t *testing.T) { @@ -25,3 +31,22 @@ func TestPaymentRequestServiceSuite(t *testing.T) { suite.Run(t, ts) ts.PopTestSuite.TearDown() } + +func (suite *PaymentRequestServiceSuite) openLocalFile(path string) (afero.File, error) { + file, err := os.Open(filepath.Clean(path)) + if err != nil { + suite.Logger().Fatal("Error opening local file", zap.Error(err)) + } + + outputFile, err := suite.fs.Create(path) + if err != nil { + suite.Logger().Fatal("Error creating afero file", zap.Error(err)) + } + + _, err = io.Copy(outputFile, file) + if err != nil { + suite.Logger().Fatal("Error copying to afero file", zap.Error(err)) + } + + return outputFile, nil +} From 01dd01846fa0823b64575a30e186e7119c043795 Mon Sep 17 00:00:00 2001 From: Maria Traskowsky Date: Wed, 4 Sep 2024 15:37:37 +0000 Subject: [PATCH 15/68] updated tests --- .../PaymentRequestCard/PaymentRequestCard.jsx | 4 ++-- .../PaymentRequestCard.test.jsx | 23 +++++++++++++++++-- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/components/Office/PaymentRequestCard/PaymentRequestCard.jsx b/src/components/Office/PaymentRequestCard/PaymentRequestCard.jsx index 1095ee87bd1..c6316d20230 100644 --- a/src/components/Office/PaymentRequestCard/PaymentRequestCard.jsx +++ b/src/components/Office/PaymentRequestCard/PaymentRequestCard.jsx @@ -326,7 +326,7 @@ const PaymentRequestCard = ({ {paymentRequest.receivedByGexAt && (
-
+

{toDollarString(formatCents(approvedAmount))}

TPPS Received on {formatDateFromIso(paymentRequest.receivedByGexAt, 'DD MMM YYYY')} @@ -344,7 +344,7 @@ const PaymentRequestCard = ({ return (
-
+

{toDollarString(formatCents(approvedAmount))}

Sent to GEX diff --git a/src/components/Office/PaymentRequestCard/PaymentRequestCard.test.jsx b/src/components/Office/PaymentRequestCard/PaymentRequestCard.test.jsx index 04486d323c7..1443e72d928 100644 --- a/src/components/Office/PaymentRequestCard/PaymentRequestCard.test.jsx +++ b/src/components/Office/PaymentRequestCard/PaymentRequestCard.test.jsx @@ -601,7 +601,7 @@ describe('PaymentRequestCard', () => { createdAt: '2020-12-01T00:00:00.000Z', mtoServiceItemID: 'f8c2f97f-99e7-4fb1-9cc4-473debd24dbc', priceCents: 2000001, - status: 'DENIED', + status: 'APPROVED', }, { id: '39474c6a-69b6-4501-8e08-670a12512a5f', @@ -626,6 +626,14 @@ describe('PaymentRequestCard', () => { ); expect(sentToGex.find({ 'data-testid': 'tag' }).contains('Sent to GEX')).toBe(true); expect(sentToGex.find({ 'data-testid': 'sentToGexDetails' }).exists()).toBe(true); + // displays the sent to gex sum, milmove accepted amount, and milmove rejected amount + expect(sentToGex.find({ 'data-testid': 'sentToGexDetailsDollarAmountTotal' }).contains('$20,000.01')).toBe(true); + expect(sentToGex.find({ 'data-testid': 'milMoveAcceptedDetailsDollarAmountTotal' }).contains('$20,000.01')).toBe( + true, + ); + expect(sentToGex.find({ 'data-testid': 'milMoveRejectedDetailsDollarAmountTotal' }).contains('$40,000.01')).toBe( + true, + ); }); it('renders - for the date it was sent to gex if sentToGexAt is null', () => { @@ -653,13 +661,14 @@ describe('PaymentRequestCard', () => { paymentRequestNumber: '1843-9061-2', status: 'TPPS_RECEIVED', moveTaskOrder: move, + receivedByGexAt: '2020-12-01T00:00:00.000Z', serviceItems: [ { id: '09474c6a-69b6-4501-8e08-670a12512a5f', createdAt: '2020-12-01T00:00:00.000Z', mtoServiceItemID: 'f8c2f97f-99e7-4fb1-9cc4-473debd24dbc', priceCents: 2000001, - status: 'DENIED', + status: 'APPROVED', }, { id: '39474c6a-69b6-4501-8e08-670a12512a5f', @@ -681,6 +690,16 @@ describe('PaymentRequestCard', () => { , ); expect(receivedByGex.find({ 'data-testid': 'tag' }).contains('TPPS Received')).toBe(true); + // displays the tpps received sum, milmove accepted amount, and milmove rejected amount + expect(receivedByGex.find({ 'data-testid': 'tppsReceivedDetailsDollarAmountTotal' }).contains('$20,000.01')).toBe( + true, + ); + expect( + receivedByGex.find({ 'data-testid': 'milMoveAcceptedDetailsDollarAmountTotal' }).contains('$20,000.01'), + ).toBe(true); + expect( + receivedByGex.find({ 'data-testid': 'milMoveRejectedDetailsDollarAmountTotal' }).contains('$40,000.01'), + ).toBe(true); }); it('renders the paid status tag for paid request', () => { From 49a9c47213d3335d1035bc7409a53bf225446649 Mon Sep 17 00:00:00 2001 From: Maria Traskowsky Date: Wed, 4 Sep 2024 16:20:30 +0000 Subject: [PATCH 16/68] flaky test fix --- .../ServicesCounselingEditShipmentDetails.test.jsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/pages/Office/ServicesCounselingEditShipmentDetails/ServicesCounselingEditShipmentDetails.test.jsx b/src/pages/Office/ServicesCounselingEditShipmentDetails/ServicesCounselingEditShipmentDetails.test.jsx index de1ac1b0ead..0532d35b1d5 100644 --- a/src/pages/Office/ServicesCounselingEditShipmentDetails/ServicesCounselingEditShipmentDetails.test.jsx +++ b/src/pages/Office/ServicesCounselingEditShipmentDetails/ServicesCounselingEditShipmentDetails.test.jsx @@ -324,11 +324,9 @@ describe('ServicesCounselingEditShipmentDetails component', () => { await userEvent.click(saveButton); - await waitFor(() => { - expect( - screen.getByText('Something went wrong, and your changes were not saved. Please try again.'), - ).toBeVisible(); - }); + expect( + await screen.findByText('Something went wrong, and your changes were not saved. Please try again.'), + ).toBeVisible(); }); it('routes to the move details page when the cancel button is clicked', async () => { From c2e8da62381abb86ece8ec878ada0e286c32cb64 Mon Sep 17 00:00:00 2001 From: Maria Traskowsky Date: Wed, 4 Sep 2024 16:25:43 +0000 Subject: [PATCH 17/68] Revert "flaky test fix" This reverts commit 49a9c47213d3335d1035bc7409a53bf225446649. --- .../ServicesCounselingEditShipmentDetails.test.jsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/pages/Office/ServicesCounselingEditShipmentDetails/ServicesCounselingEditShipmentDetails.test.jsx b/src/pages/Office/ServicesCounselingEditShipmentDetails/ServicesCounselingEditShipmentDetails.test.jsx index 0532d35b1d5..de1ac1b0ead 100644 --- a/src/pages/Office/ServicesCounselingEditShipmentDetails/ServicesCounselingEditShipmentDetails.test.jsx +++ b/src/pages/Office/ServicesCounselingEditShipmentDetails/ServicesCounselingEditShipmentDetails.test.jsx @@ -324,9 +324,11 @@ describe('ServicesCounselingEditShipmentDetails component', () => { await userEvent.click(saveButton); - expect( - await screen.findByText('Something went wrong, and your changes were not saved. Please try again.'), - ).toBeVisible(); + await waitFor(() => { + expect( + screen.getByText('Something went wrong, and your changes were not saved. Please try again.'), + ).toBeVisible(); + }); }); it('routes to the move details page when the cancel button is clicked', async () => { From 8a24dab16c8d750c39da6dac1717fbbe829fc6d6 Mon Sep 17 00:00:00 2001 From: loganwc Date: Wed, 4 Sep 2024 20:01:45 -0500 Subject: [PATCH 18/68] added frontend test --- pkg/paperwork/generator.go | 16 +++++-- .../payment_request_bulk_download_creator.go | 2 +- ...ment_request_bulk_download_creator_test.go | 43 ------------------- .../payment_request_service_test.go | 27 +----------- .../PaymentRequestReview.test.jsx | 31 ++++++++++++- 5 files changed, 45 insertions(+), 74 deletions(-) delete mode 100644 pkg/services/payment_request/payment_request_bulk_download_creator_test.go diff --git a/pkg/paperwork/generator.go b/pkg/paperwork/generator.go index 51b35e360a1..23ee0accdc3 100644 --- a/pkg/paperwork/generator.go +++ b/pkg/paperwork/generator.go @@ -291,9 +291,19 @@ func (g *Generator) ConvertUploadsToPDF(appCtx appcontext.AppContext, uploads mo // Merge all remaining images in urls into a new PDF if len(images) > 0 { - pdf, err := g.PDFFromImages(appCtx, images) - if err != nil { - return nil, errors.Wrap(err, "Converting remaining images to pdf") + var pdf string + var err error + + if doRotation { + pdf, err = g.PDFFromImages(appCtx, images) + if err != nil { + return nil, errors.Wrap(err, "Converting remaining images to pdf") + } + } else { + pdf, err = g.PDFFromImagesNoRotation(appCtx, images) + if err != nil { + return nil, errors.Wrap(err, "Converting remaining images to pdf") + } } pdfs = append(pdfs, pdf) } diff --git a/pkg/services/payment_request/payment_request_bulk_download_creator.go b/pkg/services/payment_request/payment_request_bulk_download_creator.go index 24fb7d590c7..1178f4c991c 100644 --- a/pkg/services/payment_request/payment_request_bulk_download_creator.go +++ b/pkg/services/payment_request/payment_request_bulk_download_creator.go @@ -32,7 +32,7 @@ func (p *paymentRequestBulkDownloadCreator) CreatePaymentRequestBulkDownload(app "ProofOfServiceDocs.PrimeUploads", "ProofOfServiceDocs.PrimeUploads.Upload", ).Find(&paymentRequest, paymentRequestID) - if err != nil { + if err != nil || len(paymentRequest.ProofOfServiceDocs) < 1 { return nil, fmt.Errorf("%s: %w", errMsgPrefix, err) } diff --git a/pkg/services/payment_request/payment_request_bulk_download_creator_test.go b/pkg/services/payment_request/payment_request_bulk_download_creator_test.go deleted file mode 100644 index fd1c967d091..00000000000 --- a/pkg/services/payment_request/payment_request_bulk_download_creator_test.go +++ /dev/null @@ -1,43 +0,0 @@ -package paymentrequest - -import ( - "github.com/transcom/mymove/pkg/factory" - paperworkgenerator "github.com/transcom/mymove/pkg/paperwork" - "github.com/transcom/mymove/pkg/uploader" -) - -func (suite *PaymentRequestServiceSuite) TestCreatePaymentRequestBulkDownload() { - primeUploader, err := uploader.NewPrimeUploader(suite.storer, 25*uploader.MB) - suite.NoError(err) - - generator, err := paperworkgenerator.NewGenerator(primeUploader.Uploader()) - suite.FatalNil(err) - - paymentRequest := factory.BuildPaymentRequest(suite.DB(), nil, nil) - primeUpload := factory.BuildPrimeUpload(suite.DB(), []factory.Customization{ - { - Model: paymentRequest, - LinkOnly: true, - }, - }, nil) - posd := factory.BuildProofOfServiceDoc(suite.DB(), []factory.Customization{ - { - Model: primeUpload, - LinkOnly: true, - }, - { - Model: paymentRequest, - LinkOnly: true, - }, - }, nil) - - paymentRequest.ProofOfServiceDocs = append(paymentRequest.ProofOfServiceDocs, posd) - - creator := &paymentRequestBulkDownloadCreator{ - pdfGenerator: generator, - } - - bulkDownload, err := creator.CreatePaymentRequestBulkDownload(suite.AppContextForTest(), paymentRequest.ID) - suite.NoError(err) - suite.NotNil(bulkDownload) -} diff --git a/pkg/services/payment_request/payment_request_service_test.go b/pkg/services/payment_request/payment_request_service_test.go index 37864593901..f13384719a9 100644 --- a/pkg/services/payment_request/payment_request_service_test.go +++ b/pkg/services/payment_request/payment_request_service_test.go @@ -1,24 +1,18 @@ package paymentrequest import ( - "io" - "os" - "path/filepath" "testing" "github.com/spf13/afero" "github.com/stretchr/testify/suite" - "go.uber.org/zap" - "github.com/transcom/mymove/pkg/storage" "github.com/transcom/mymove/pkg/testingsuite" ) // PaymentRequestServiceSuite is a suite for testing payment requests type PaymentRequestServiceSuite struct { *testingsuite.PopTestSuite - fs *afero.Afero - storer storage.FileStorer + fs *afero.Afero } func TestPaymentRequestServiceSuite(t *testing.T) { @@ -31,22 +25,3 @@ func TestPaymentRequestServiceSuite(t *testing.T) { suite.Run(t, ts) ts.PopTestSuite.TearDown() } - -func (suite *PaymentRequestServiceSuite) openLocalFile(path string) (afero.File, error) { - file, err := os.Open(filepath.Clean(path)) - if err != nil { - suite.Logger().Fatal("Error opening local file", zap.Error(err)) - } - - outputFile, err := suite.fs.Create(path) - if err != nil { - suite.Logger().Fatal("Error creating afero file", zap.Error(err)) - } - - _, err = io.Copy(outputFile, file) - if err != nil { - suite.Logger().Fatal("Error copying to afero file", zap.Error(err)) - } - - return outputFile, nil -} diff --git a/src/pages/Office/PaymentRequestReview/PaymentRequestReview.test.jsx b/src/pages/Office/PaymentRequestReview/PaymentRequestReview.test.jsx index 3874929fdfd..89727aacda2 100644 --- a/src/pages/Office/PaymentRequestReview/PaymentRequestReview.test.jsx +++ b/src/pages/Office/PaymentRequestReview/PaymentRequestReview.test.jsx @@ -6,7 +6,7 @@ import userEvent from '@testing-library/user-event'; import { PaymentRequestReview } from './PaymentRequestReview'; -import { patchPaymentServiceItemStatus } from 'services/ghcApi'; +import { patchPaymentServiceItemStatus, bulkDownloadPaymentRequest } from 'services/ghcApi'; import { SHIPMENT_OPTIONS, PAYMENT_REQUEST_STATUS, PAYMENT_SERVICE_ITEM_STATUS } from 'shared/constants'; import { usePaymentRequestQueries } from 'hooks/queries'; import { ReactQueryWrapper } from 'testUtils'; @@ -57,6 +57,7 @@ jest.mock('hooks/queries', () => ({ jest.mock('services/ghcApi', () => ({ ...jest.requireActual('services/ghcApi'), patchPaymentServiceItemStatus: jest.fn(), + bulkDownloadPaymentRequest: jest.fn(), })); // prevents react-fileviewer from throwing errors without mocking relevant DOM elements @@ -353,6 +354,34 @@ describe('PaymentRequestReview', () => { expect(reviewServiceItems.prop('serviceItemCards')).toEqual(expectedServiceItemCards); }); }); + describe('when clicking download Download All Files button', () => { + it('downloads a bulk packet', async () => { + usePaymentRequestQueries.mockReturnValue(usePaymentRequestQueriesReturnValuePendingFinalReview); + + const mockResponse = { + ok: true, + headers: { + 'content-disposition': 'filename="test.pdf"', + }, + status: 200, + data: null, + }; + + render( + + + , + ); + + bulkDownloadPaymentRequest.mockImplementation(() => Promise.resolve(mockResponse)); + + const downloadButton = screen.getByText('Download All Files (PDF)', { exact: false }); + await userEvent.click(downloadButton); + await waitFor(() => { + expect(bulkDownloadPaymentRequest).toHaveBeenCalledTimes(1); + }); + }); + }); describe('clicking the next button', () => { describe('with pending requests', () => { beforeEach(async () => { From f233e8c8e3426ea45c8321d68346bb44cd3601ca Mon Sep 17 00:00:00 2001 From: loganwc Date: Thu, 5 Sep 2024 01:23:16 +0000 Subject: [PATCH 19/68] revert changes I didn't mean to make --- pkg/handlers/ghcapi/api.go | 2 +- pkg/handlers/ghcapi/ppm_document.go | 8 ++++---- pkg/handlers/ghcapi/ppm_document_test.go | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pkg/handlers/ghcapi/api.go b/pkg/handlers/ghcapi/api.go index 6de9b1d5626..2fa8b0242d1 100644 --- a/pkg/handlers/ghcapi/api.go +++ b/pkg/handlers/ghcapi/api.go @@ -638,7 +638,7 @@ func NewGhcAPIHandler(handlerConfig handlers.HandlerConfig) *ghcops.MymoveAPI { } ppmShipmentFetcher := ppmshipment.NewPPMShipmentFetcher() paymentPacketCreator := ppmshipment.NewPaymentPacketCreator(ppmShipmentFetcher, pdfGenerator, AOAPacketCreator) - ghcAPI.PpmShowPaymentPacketHandler = ShowPaymentRequestBulkDownloadHandler{handlerConfig, paymentPacketCreator} + ghcAPI.PpmShowPaymentPacketHandler = ShowPaymentPacketHandler{handlerConfig, paymentPacketCreator} ghcAPI.UploadsCreateUploadHandler = CreateUploadHandler{handlerConfig} ghcAPI.UploadsUpdateUploadHandler = UpdateUploadHandler{handlerConfig, upload.NewUploadInformationFetcher()} diff --git a/pkg/handlers/ghcapi/ppm_document.go b/pkg/handlers/ghcapi/ppm_document.go index 32a9c021545..ee92f7e5d29 100644 --- a/pkg/handlers/ghcapi/ppm_document.go +++ b/pkg/handlers/ghcapi/ppm_document.go @@ -193,14 +193,14 @@ func (h showAOAPacketHandler) Handle(params ppmdocumentops.ShowAOAPacketParams) }) } -// ShowPaymentRequestBulkDownloadHandler returns a PPM Payment Packet PDF -type ShowPaymentRequestBulkDownloadHandler struct { +// ShowPaymentPacketHandler returns a PPM Payment Packet PDF +type ShowPaymentPacketHandler struct { handlers.HandlerConfig services.PaymentPacketCreator } // Handle returns a generated PDF -func (h ShowPaymentRequestBulkDownloadHandler) Handle(params ppmdocumentops.ShowPaymentPacketParams) middleware.Responder { +func (h ShowPaymentPacketHandler) Handle(params ppmdocumentops.ShowPaymentPacketParams) middleware.Responder { return h.AuditableAppContextFromRequestWithErrors(params.HTTPRequest, func(appCtx appcontext.AppContext) (middleware.Responder, error) { ppmShipmentID, err := uuid.FromString(params.PpmShipmentID.String()) @@ -222,7 +222,7 @@ func (h ShowPaymentRequestBulkDownloadHandler) Handle(params ppmdocumentops.Show } payload := io.NopCloser(pdf) - filename := fmt.Sprintf("inline; filename=\"ppm_payment_bulk_download_packet-%s.pdf\"", time.Now().UTC().Format("2006-01-02T15:04:05.000Z")) + filename := fmt.Sprintf("inline; filename=\"ppm_payment_packet-%s.pdf\"", time.Now().UTC().Format("2006-01-02T15:04:05.000Z")) return ppmdocumentops.NewShowPaymentPacketOK().WithContentDisposition(filename).WithPayload(payload), nil }) diff --git a/pkg/handlers/ghcapi/ppm_document_test.go b/pkg/handlers/ghcapi/ppm_document_test.go index 914eb7defe5..576a792f43c 100644 --- a/pkg/handlers/ghcapi/ppm_document_test.go +++ b/pkg/handlers/ghcapi/ppm_document_test.go @@ -702,7 +702,7 @@ func (suite *HandlerSuite) TestShowPaymentPacketHandler() { suite.Run("Successful ShowAOAPacketHandler - 200", func() { mockPaymentPacketCreator := mocks.PaymentPacketCreator{} - handler := ShowPaymentRequestBulkDownloadHandler{ + handler := ShowPaymentPacketHandler{ HandlerConfig: suite.createS3HandlerConfig(), PaymentPacketCreator: &mockPaymentPacketCreator, } @@ -728,7 +728,7 @@ func (suite *HandlerSuite) TestShowPaymentPacketHandler() { suite.Run("Unsuccessful ShowPaymentPacketHandler - InternalServerError", func() { mockPaymentPacketCreator := mocks.PaymentPacketCreator{} - handler := ShowPaymentRequestBulkDownloadHandler{ + handler := ShowPaymentPacketHandler{ HandlerConfig: suite.createS3HandlerConfig(), PaymentPacketCreator: &mockPaymentPacketCreator, } @@ -754,7 +754,7 @@ func (suite *HandlerSuite) TestShowPaymentPacketHandler() { suite.Run("Unsuccessful ShowPaymentPacketHandler - NotFoundError", func() { mockPaymentPacketCreator := mocks.PaymentPacketCreator{} - handler := ShowPaymentRequestBulkDownloadHandler{ + handler := ShowPaymentPacketHandler{ HandlerConfig: suite.createS3HandlerConfig(), PaymentPacketCreator: &mockPaymentPacketCreator, } From 19ec6a12d3e9755ce355f01d42305c9f930a3399 Mon Sep 17 00:00:00 2001 From: Maria Traskowsky Date: Thu, 5 Sep 2024 14:12:37 +0000 Subject: [PATCH 20/68] return null instead of empty div --- .../Office/PaymentRequestCard/PaymentRequestCard.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Office/PaymentRequestCard/PaymentRequestCard.jsx b/src/components/Office/PaymentRequestCard/PaymentRequestCard.jsx index c6316d20230..88ab4ab916d 100644 --- a/src/components/Office/PaymentRequestCard/PaymentRequestCard.jsx +++ b/src/components/Office/PaymentRequestCard/PaymentRequestCard.jsx @@ -292,7 +292,7 @@ const PaymentRequestCard = ({
); } - return
; + return null; }; const renderPaymentRequestDetailsForStatus = (paymentRequestStatus) => { @@ -366,7 +366,7 @@ const PaymentRequestCard = ({
); } - return
; + return null; }; return ( From da757989ecf02a1dc915e4484d7674b62bf7fedb Mon Sep 17 00:00:00 2001 From: loganwc Date: Thu, 5 Sep 2024 18:32:28 +0000 Subject: [PATCH 21/68] removed duplicate test stuff --- .../Office/PaymentRequestReview/PaymentRequestReview.test.jsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/pages/Office/PaymentRequestReview/PaymentRequestReview.test.jsx b/src/pages/Office/PaymentRequestReview/PaymentRequestReview.test.jsx index 89727aacda2..1426bb27a60 100644 --- a/src/pages/Office/PaymentRequestReview/PaymentRequestReview.test.jsx +++ b/src/pages/Office/PaymentRequestReview/PaymentRequestReview.test.jsx @@ -526,10 +526,6 @@ describe('PaymentRequestReview', () => { await userEvent.click(screen.getByRole('button', { name: 'Previous Service Item' })); expect(screen.getByTestId('statusHeading')).toHaveTextContent('Accepted'); }); - it('shows the Download All Files (PDF) button and downloads the pdf', async () => { - expect(screen.getByText('Download All Files (PDF)')).toBeInTheDocument(); - await userEvent.click(screen.getByText('Download All Files (PDF)')); - }); }); }); }); From 4367faf9b1e8d35135cef684946c2e0a58002abe Mon Sep 17 00:00:00 2001 From: cameroncaci Date: Thu, 5 Sep 2024 20:03:36 +0000 Subject: [PATCH 22/68] add yup validation for edipi and emplid --- .../CustomerOnboarding/CreateCustomerForm.jsx | 28 +++++++--- .../CreateCustomerForm.test.jsx | 54 +++++++++++++++++++ 2 files changed, 76 insertions(+), 6 deletions(-) diff --git a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx index b8a6ed6a6fa..3386c8c7e24 100644 --- a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx +++ b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx @@ -144,10 +144,26 @@ export const CreateCustomerForm = ({ userPrivileges, setFlashMessage }) => { const validationSchema = Yup.object().shape({ affiliation: Yup.mixed().oneOf(Object.keys(SERVICE_MEMBER_AGENCY_LABELS)).required('Required'), - edipi: Yup.string().matches(/[0-9]{10}/, 'Enter a 10-digit DOD ID number'), - emplid: Yup.string() - .notRequired() - .matches(/[0-9]{7}/, 'Enter a 7-digit EMPLID number'), + // All branches require an EDIPI unless it is a safety move + // where a fake DoD ID may be used + edipi: + !isSafetyMove && + Yup.string() + .matches(/[0-9]{10}/, 'Enter a 10-digit DOD ID number') + .required('Required'), + // Only the coast guard requires both EDIPI and EMPLID + // unless it is a safety move + emplid: + !isSafetyMove && + showEmplid && + Yup.string().when('affiliation', { + is: (affiliationValue) => affiliationValue === departmentIndicators.COAST_GUARD, + then: () => + Yup.string() + .matches(/[0-9]{7}/, 'Enter a 7-digit EMPLID number') + .required(`EMPLID is required for ${departmentIndicators.COAST_GUARD}`), + otherwise: Yup.string().notRequired(), + }), first_name: Yup.string().required('Required'), middle_name: Yup.string(), last_name: Yup.string().required('Required'), @@ -262,8 +278,8 @@ export const CreateCustomerForm = ({ userPrivileges, setFlashMessage }) => { label="DoD ID number" name="edipi" id="edipi" - labelHint="Optional" maxLength="10" + data-testid="edipi" isDisabled={isSafetyMove} /> {showEmplid && ( @@ -271,8 +287,8 @@ export const CreateCustomerForm = ({ userPrivileges, setFlashMessage }) => { label="EMPLID" name="emplid" id="emplid" + data-testid="emplid" maxLength="7" - labelHint="Optional" inputMode="numeric" pattern="[0-9]{7}" isDisabled={isSafetyMove} diff --git a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx index 7167c40da84..e4f7ae52430 100644 --- a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx +++ b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx @@ -249,6 +249,8 @@ describe('CreateCustomerForm', () => { await user.type(getByLabelText('Best contact phone'), fakePayload.telephone); await user.type(getByLabelText('Personal email'), fakePayload.personal_email); + await userEvent.type(getByTestId('edipi'), fakePayload.edipi); + await userEvent.type(getByTestId('res-add-street1'), fakePayload.residential_address.streetAddress1); await userEvent.type(getByTestId('res-add-city'), fakePayload.residential_address.city); await userEvent.selectOptions(getByTestId('res-add-state'), [fakePayload.residential_address.state]); @@ -282,6 +284,57 @@ describe('CreateCustomerForm', () => { }); }, 10000); + it('validates emplid against a coast guard member', async () => { + createCustomerWithOktaOption.mockImplementation(() => Promise.resolve(fakeResponse)); + + const { getByLabelText, getByTestId, getByRole } = render( + + + , + ); + + const user = userEvent.setup(); + + const saveBtn = await screen.findByRole('button', { name: 'Save' }); + expect(saveBtn).toBeInTheDocument(); + + await user.selectOptions(getByLabelText('Branch of service'), 'COAST_GUARD'); + + await user.type(getByLabelText('First name'), fakePayload.first_name); + await user.type(getByLabelText('Last name'), fakePayload.last_name); + + await user.type(getByLabelText('Best contact phone'), fakePayload.telephone); + await user.type(getByLabelText('Personal email'), fakePayload.personal_email); + + await userEvent.type(getByTestId('edipi'), fakePayload.edipi); + + await userEvent.type(getByTestId('res-add-street1'), fakePayload.residential_address.streetAddress1); + await userEvent.type(getByTestId('res-add-city'), fakePayload.residential_address.city); + await userEvent.selectOptions(getByTestId('res-add-state'), [fakePayload.residential_address.state]); + await userEvent.type(getByTestId('res-add-zip'), fakePayload.residential_address.postalCode); + + await userEvent.type(getByTestId('backup-add-street1'), fakePayload.backup_mailing_address.streetAddress1); + await userEvent.type(getByTestId('backup-add-city'), fakePayload.backup_mailing_address.city); + await userEvent.selectOptions(getByTestId('backup-add-state'), [fakePayload.backup_mailing_address.state]); + await userEvent.type(getByTestId('backup-add-zip'), fakePayload.backup_mailing_address.postalCode); + + await userEvent.type(getByLabelText('Name'), fakePayload.backup_contact.name); + await userEvent.type(getByRole('textbox', { name: 'Email' }), fakePayload.backup_contact.email); + await userEvent.type(getByRole('textbox', { name: 'Phone' }), fakePayload.backup_contact.telephone); + + await userEvent.type(getByTestId('create-okta-account-yes'), fakePayload.create_okta_account); + + await userEvent.type(getByTestId('cac-user-no'), fakePayload.cac_user); + + await waitFor(() => { + expect(saveBtn).toBeDisabled(); // EMPLID not set yet + }); + await userEvent.type(getByTestId('emplid'), '1234567'); + await waitFor(() => { + expect(saveBtn).toBeEnabled(); // EMPLID is set now, all validations true + }); + }, 10000); + it('allows safety privileged users to pass safety move status to orders screen', async () => { createCustomerWithOktaOption.mockImplementation(() => Promise.resolve(fakeResponse)); isBooleanFlagEnabled.mockImplementation(() => Promise.resolve(true)); @@ -350,6 +403,7 @@ describe('CreateCustomerForm', () => { expect(saveBtn).toBeInTheDocument(); await user.selectOptions(getByLabelText('Branch of service'), [fakePayload.affiliation]); + await userEvent.type(getByTestId('edipi'), fakePayload.edipi); await user.type(getByLabelText('First name'), fakePayload.first_name); await user.type(getByLabelText('Last name'), fakePayload.last_name); From 057d32b5f56dfcc926b4288e9ee50c65150b7efd Mon Sep 17 00:00:00 2001 From: Cory Kleinjan Date: Thu, 5 Sep 2024 20:52:02 +0000 Subject: [PATCH 23/68] fixing issue with unpopulated price_estimates on older moves --- migrations/app/migrations_manifest.txt | 1 + ...ces_to_unpriced_cs_ms_service_items.up.sql | 28 +++++++++++++++++++ pkg/handlers/ghcapi/mto_service_items.go | 4 +-- pkg/services/ghc_rate_engine.go | 4 +-- .../counseling_services_pricer.go | 15 +++++++--- .../counseling_services_pricer_test.go | 3 +- .../management_services_pricer.go | 15 +++++++--- .../management_services_pricer_test.go | 3 +- .../mocks/CounselingServicesPricer.go | 10 +++---- .../mocks/ManagementServicesPricer.go | 10 +++---- 10 files changed, 69 insertions(+), 24 deletions(-) create mode 100644 migrations/app/schema/20240905192913_adding_prices_to_unpriced_cs_ms_service_items.up.sql diff --git a/migrations/app/migrations_manifest.txt b/migrations/app/migrations_manifest.txt index 5ac95f73208..7bdf1dce58a 100644 --- a/migrations/app/migrations_manifest.txt +++ b/migrations/app/migrations_manifest.txt @@ -981,3 +981,4 @@ 20240820125856_allow_pptas_migration.up.sql 20240821180447_populating_locked_price_cents_for_ms_and_cs.up.sql 20240822180409_adding_locked_price_cents_service_param.up.sql +20240905192913_adding_prices_to_unpriced_cs_ms_service_items.up.sql diff --git a/migrations/app/schema/20240905192913_adding_prices_to_unpriced_cs_ms_service_items.up.sql b/migrations/app/schema/20240905192913_adding_prices_to_unpriced_cs_ms_service_items.up.sql new file mode 100644 index 00000000000..a3172cc10c5 --- /dev/null +++ b/migrations/app/schema/20240905192913_adding_prices_to_unpriced_cs_ms_service_items.up.sql @@ -0,0 +1,28 @@ +-- Filling in pricing_estimates for unprices services code MS and CS service items. Service items should not be able to reach this state +-- but some older data exists where unpriced MS and CS items exist +SET statement_timeout = 300000; +SET lock_timeout = 300000; +SET idle_in_transaction_session_timeout = 300000; + +UPDATE mto_service_items AS ms +SET locked_price_cents = + CASE + when price_cents > 0 then price_cents + when price_cents <= 0 then 0 + END, + pricing_estimate = + CASE + when price_cents > 0 then price_cents + when price_cents <= 0 then 0 + END +FROM re_task_order_fees AS tf +JOIN re_contract_years AS cy +ON tf.contract_year_id = cy.id +JOIN re_contracts AS c +ON cy.contract_id = c.id +JOIN re_services AS s +ON tf.service_id = s.id +WHERE ms.locked_price_cents = null + AND ms.pricing_estimate = null + AND re_service_id = s.id + AND s.code = 'MS' OR s.code = 'CS' \ No newline at end of file diff --git a/pkg/handlers/ghcapi/mto_service_items.go b/pkg/handlers/ghcapi/mto_service_items.go index 4a89afdc426..33b34ae1373 100644 --- a/pkg/handlers/ghcapi/mto_service_items.go +++ b/pkg/handlers/ghcapi/mto_service_items.go @@ -392,9 +392,9 @@ func (h ListMTOServiceItemsHandler) Handle(params mtoserviceitemop.ListMTOServic var displayParams services.PricingDisplayParams var err error if serviceItems[index].ReService.Code == "CS" { - price, displayParams, err = h.counselingPricer.Price(appCtx, *serviceItems[index].LockedPriceCents) + price, displayParams, err = h.counselingPricer.Price(appCtx, serviceItems[index].LockedPriceCents) } else if serviceItems[index].ReService.Code == "MS" { - price, displayParams, err = h.moveManagementPricer.Price(appCtx, *serviceItems[index].LockedPriceCents) + price, displayParams, err = h.moveManagementPricer.Price(appCtx, serviceItems[index].LockedPriceCents) } for _, param := range displayParams { diff --git a/pkg/services/ghc_rate_engine.go b/pkg/services/ghc_rate_engine.go index 8393d6d1750..5d17e0388ce 100644 --- a/pkg/services/ghc_rate_engine.go +++ b/pkg/services/ghc_rate_engine.go @@ -33,7 +33,7 @@ type ParamsPricer interface { // //go:generate mockery --name ManagementServicesPricer type ManagementServicesPricer interface { - Price(appCtx appcontext.AppContext, lockedPriceCents unit.Cents) (unit.Cents, PricingDisplayParams, error) + Price(appCtx appcontext.AppContext, lockedPriceCents *unit.Cents) (unit.Cents, PricingDisplayParams, error) ParamsPricer } @@ -41,7 +41,7 @@ type ManagementServicesPricer interface { // //go:generate mockery --name CounselingServicesPricer type CounselingServicesPricer interface { - Price(appCtx appcontext.AppContext, lockedPriceCents unit.Cents) (unit.Cents, PricingDisplayParams, error) + Price(appCtx appcontext.AppContext, lockedPriceCents *unit.Cents) (unit.Cents, PricingDisplayParams, error) ParamsPricer } diff --git a/pkg/services/ghcrateengine/counseling_services_pricer.go b/pkg/services/ghcrateengine/counseling_services_pricer.go index 6ec7de65a14..f695e9753c3 100644 --- a/pkg/services/ghcrateengine/counseling_services_pricer.go +++ b/pkg/services/ghcrateengine/counseling_services_pricer.go @@ -1,6 +1,8 @@ package ghcrateengine import ( + "fmt" + "github.com/transcom/mymove/pkg/appcontext" "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/services" @@ -16,16 +18,20 @@ func NewCounselingServicesPricer() services.CounselingServicesPricer { } // Price determines the price for a counseling service -func (p counselingServicesPricer) Price(appCtx appcontext.AppContext, lockedPriceCents unit.Cents) (unit.Cents, services.PricingDisplayParams, error) { +func (p counselingServicesPricer) Price(appCtx appcontext.AppContext, lockedPriceCents *unit.Cents) (unit.Cents, services.PricingDisplayParams, error) { + + if lockedPriceCents == nil { + return 0, nil, fmt.Errorf("invalid value for locked_price_cents") + } params := services.PricingDisplayParams{ { Key: models.ServiceItemParamNamePriceRateOrFactor, - Value: FormatCents(lockedPriceCents), + Value: FormatCents(*lockedPriceCents), }, } - return lockedPriceCents, params, nil + return *lockedPriceCents, params, nil } // PriceUsingParams determines the price for a counseling service given PaymentServiceItemParams @@ -36,5 +42,6 @@ func (p counselingServicesPricer) PriceUsingParams(appCtx appcontext.AppContext, return unit.Cents(0), nil, err } - return p.Price(appCtx, unit.Cents(lockedPriceCents)) + lockedPrice := unit.Cents(lockedPriceCents) + return p.Price(appCtx, &lockedPrice) } diff --git a/pkg/services/ghcrateengine/counseling_services_pricer_test.go b/pkg/services/ghcrateengine/counseling_services_pricer_test.go index 1ca6de8b660..258ee8ce40a 100644 --- a/pkg/services/ghcrateengine/counseling_services_pricer_test.go +++ b/pkg/services/ghcrateengine/counseling_services_pricer_test.go @@ -12,6 +12,7 @@ const ( ) func (suite *GHCRateEngineServiceSuite) TestPriceCounselingServices() { + lockedPrice := csPriceCents counselingServicesPricer := NewCounselingServicesPricer() suite.Run("success using PaymentServiceItemParams", func() { @@ -31,7 +32,7 @@ func (suite *GHCRateEngineServiceSuite) TestPriceCounselingServices() { suite.Run("success without PaymentServiceItemParams", func() { suite.setupTaskOrderFeeData(models.ReServiceCodeCS, csPriceCents) - priceCents, _, err := counselingServicesPricer.Price(suite.AppContextForTest(), csPriceCents) + priceCents, _, err := counselingServicesPricer.Price(suite.AppContextForTest(), &lockedPrice) suite.NoError(err) suite.Equal(csPriceCents, priceCents) }) diff --git a/pkg/services/ghcrateengine/management_services_pricer.go b/pkg/services/ghcrateengine/management_services_pricer.go index 31a23729e94..995003c196b 100644 --- a/pkg/services/ghcrateengine/management_services_pricer.go +++ b/pkg/services/ghcrateengine/management_services_pricer.go @@ -1,6 +1,8 @@ package ghcrateengine import ( + "fmt" + "github.com/transcom/mymove/pkg/appcontext" "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/services" @@ -16,16 +18,20 @@ func NewManagementServicesPricer() services.ManagementServicesPricer { } // Price determines the price for a management service -func (p managementServicesPricer) Price(appCtx appcontext.AppContext, lockedPriceCents unit.Cents) (unit.Cents, services.PricingDisplayParams, error) { +func (p managementServicesPricer) Price(appCtx appcontext.AppContext, lockedPriceCents *unit.Cents) (unit.Cents, services.PricingDisplayParams, error) { + + if lockedPriceCents == nil { + return 0, nil, fmt.Errorf("invalid value for locked_price_cents") + } params := services.PricingDisplayParams{ { Key: models.ServiceItemParamNamePriceRateOrFactor, - Value: FormatCents(lockedPriceCents), + Value: FormatCents(*lockedPriceCents), }, } - return lockedPriceCents, params, nil + return *lockedPriceCents, params, nil } // PriceUsingParams determines the price for a management service given PaymentServiceItemParams @@ -36,5 +42,6 @@ func (p managementServicesPricer) PriceUsingParams(appCtx appcontext.AppContext, return unit.Cents(0), nil, err } - return p.Price(appCtx, unit.Cents(lockedPriceCents)) + lockedPrice := unit.Cents(lockedPriceCents) + return p.Price(appCtx, &lockedPrice) } diff --git a/pkg/services/ghcrateengine/management_services_pricer_test.go b/pkg/services/ghcrateengine/management_services_pricer_test.go index be068473c20..01452e741c4 100644 --- a/pkg/services/ghcrateengine/management_services_pricer_test.go +++ b/pkg/services/ghcrateengine/management_services_pricer_test.go @@ -12,6 +12,7 @@ const ( ) func (suite *GHCRateEngineServiceSuite) TestPriceManagementServices() { + lockedPrice := csPriceCents suite.Run("success using PaymentServiceItemParams", func() { suite.setupTaskOrderFeeData(models.ReServiceCodeMS, msPriceCents) paymentServiceItem := suite.setupManagementServicesItem() @@ -32,7 +33,7 @@ func (suite *GHCRateEngineServiceSuite) TestPriceManagementServices() { suite.setupTaskOrderFeeData(models.ReServiceCodeMS, msPriceCents) managementServicesPricer := NewManagementServicesPricer() - priceCents, _, err := managementServicesPricer.Price(suite.AppContextForTest(), msPriceCents) + priceCents, _, err := managementServicesPricer.Price(suite.AppContextForTest(), &lockedPrice) suite.NoError(err) suite.Equal(msPriceCents, priceCents) }) diff --git a/pkg/services/mocks/CounselingServicesPricer.go b/pkg/services/mocks/CounselingServicesPricer.go index 51d2964ac42..1b6400c5a1b 100644 --- a/pkg/services/mocks/CounselingServicesPricer.go +++ b/pkg/services/mocks/CounselingServicesPricer.go @@ -19,22 +19,22 @@ type CounselingServicesPricer struct { } // Price provides a mock function with given fields: appCtx, lockedPriceCents -func (_m *CounselingServicesPricer) Price(appCtx appcontext.AppContext, lockedPriceCents unit.Cents) (unit.Cents, services.PricingDisplayParams, error) { +func (_m *CounselingServicesPricer) Price(appCtx appcontext.AppContext, lockedPriceCents *unit.Cents) (unit.Cents, services.PricingDisplayParams, error) { ret := _m.Called(appCtx, lockedPriceCents) var r0 unit.Cents var r1 services.PricingDisplayParams var r2 error - if rf, ok := ret.Get(0).(func(appcontext.AppContext, unit.Cents) (unit.Cents, services.PricingDisplayParams, error)); ok { + if rf, ok := ret.Get(0).(func(appcontext.AppContext, *unit.Cents) (unit.Cents, services.PricingDisplayParams, error)); ok { return rf(appCtx, lockedPriceCents) } - if rf, ok := ret.Get(0).(func(appcontext.AppContext, unit.Cents) unit.Cents); ok { + if rf, ok := ret.Get(0).(func(appcontext.AppContext, *unit.Cents) unit.Cents); ok { r0 = rf(appCtx, lockedPriceCents) } else { r0 = ret.Get(0).(unit.Cents) } - if rf, ok := ret.Get(1).(func(appcontext.AppContext, unit.Cents) services.PricingDisplayParams); ok { + if rf, ok := ret.Get(1).(func(appcontext.AppContext, *unit.Cents) services.PricingDisplayParams); ok { r1 = rf(appCtx, lockedPriceCents) } else { if ret.Get(1) != nil { @@ -42,7 +42,7 @@ func (_m *CounselingServicesPricer) Price(appCtx appcontext.AppContext, lockedPr } } - if rf, ok := ret.Get(2).(func(appcontext.AppContext, unit.Cents) error); ok { + if rf, ok := ret.Get(2).(func(appcontext.AppContext, *unit.Cents) error); ok { r2 = rf(appCtx, lockedPriceCents) } else { r2 = ret.Error(2) diff --git a/pkg/services/mocks/ManagementServicesPricer.go b/pkg/services/mocks/ManagementServicesPricer.go index e361159de6e..4a832d51602 100644 --- a/pkg/services/mocks/ManagementServicesPricer.go +++ b/pkg/services/mocks/ManagementServicesPricer.go @@ -19,22 +19,22 @@ type ManagementServicesPricer struct { } // Price provides a mock function with given fields: appCtx, lockedPriceCents -func (_m *ManagementServicesPricer) Price(appCtx appcontext.AppContext, lockedPriceCents unit.Cents) (unit.Cents, services.PricingDisplayParams, error) { +func (_m *ManagementServicesPricer) Price(appCtx appcontext.AppContext, lockedPriceCents *unit.Cents) (unit.Cents, services.PricingDisplayParams, error) { ret := _m.Called(appCtx, lockedPriceCents) var r0 unit.Cents var r1 services.PricingDisplayParams var r2 error - if rf, ok := ret.Get(0).(func(appcontext.AppContext, unit.Cents) (unit.Cents, services.PricingDisplayParams, error)); ok { + if rf, ok := ret.Get(0).(func(appcontext.AppContext, *unit.Cents) (unit.Cents, services.PricingDisplayParams, error)); ok { return rf(appCtx, lockedPriceCents) } - if rf, ok := ret.Get(0).(func(appcontext.AppContext, unit.Cents) unit.Cents); ok { + if rf, ok := ret.Get(0).(func(appcontext.AppContext, *unit.Cents) unit.Cents); ok { r0 = rf(appCtx, lockedPriceCents) } else { r0 = ret.Get(0).(unit.Cents) } - if rf, ok := ret.Get(1).(func(appcontext.AppContext, unit.Cents) services.PricingDisplayParams); ok { + if rf, ok := ret.Get(1).(func(appcontext.AppContext, *unit.Cents) services.PricingDisplayParams); ok { r1 = rf(appCtx, lockedPriceCents) } else { if ret.Get(1) != nil { @@ -42,7 +42,7 @@ func (_m *ManagementServicesPricer) Price(appCtx appcontext.AppContext, lockedPr } } - if rf, ok := ret.Get(2).(func(appcontext.AppContext, unit.Cents) error); ok { + if rf, ok := ret.Get(2).(func(appcontext.AppContext, *unit.Cents) error); ok { r2 = rf(appCtx, lockedPriceCents) } else { r2 = ret.Error(2) From 2887cc3a4684225090dd3ec3e10509db98283f95 Mon Sep 17 00:00:00 2001 From: cameroncaci Date: Fri, 6 Sep 2024 10:31:56 -0400 Subject: [PATCH 24/68] enforce requirement on backend and some cleanup --- pkg/gen/ghcapi/embedded_spec.go | 10 ++- .../ghcmessages/create_customer_payload.go | 21 ++++- pkg/handlers/ghcapi/customer.go | 51 ++++++------ pkg/handlers/ghcapi/customer_test.go | 79 ++++++++++++++++++- swagger-def/ghc.yaml | 5 +- swagger/ghc.yaml | 5 +- 6 files changed, 130 insertions(+), 41 deletions(-) diff --git a/pkg/gen/ghcapi/embedded_spec.go b/pkg/gen/ghcapi/embedded_spec.go index 10d6a8eb67e..008b9b79a47 100644 --- a/pkg/gen/ghcapi/embedded_spec.go +++ b/pkg/gen/ghcapi/embedded_spec.go @@ -6573,8 +6573,9 @@ func init() { }, "edipi": { "type": "string", - "x-nullable": true, - "example": "John" + "maxLength": 10, + "x-nullable": false, + "example": "1234567890" }, "emailIsPreferred": { "type": "boolean" @@ -21810,8 +21811,9 @@ func init() { }, "edipi": { "type": "string", - "x-nullable": true, - "example": "John" + "maxLength": 10, + "x-nullable": false, + "example": "1234567890" }, "emailIsPreferred": { "type": "boolean" diff --git a/pkg/gen/ghcmessages/create_customer_payload.go b/pkg/gen/ghcmessages/create_customer_payload.go index d9d28d6c570..68ece54fb88 100644 --- a/pkg/gen/ghcmessages/create_customer_payload.go +++ b/pkg/gen/ghcmessages/create_customer_payload.go @@ -37,8 +37,9 @@ type CreateCustomerPayload struct { CreateOktaAccount bool `json:"createOktaAccount,omitempty"` // edipi - // Example: John - Edipi *string `json:"edipi,omitempty"` + // Example: 1234567890 + // Max Length: 10 + Edipi string `json:"edipi,omitempty"` // email is preferred EmailIsPreferred bool `json:"emailIsPreferred,omitempty"` @@ -102,6 +103,10 @@ func (m *CreateCustomerPayload) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validateEdipi(formats); err != nil { + res = append(res, err) + } + if err := m.validateEmplid(formats); err != nil { res = append(res, err) } @@ -174,6 +179,18 @@ func (m *CreateCustomerPayload) validateBackupMailingAddress(formats strfmt.Regi return nil } +func (m *CreateCustomerPayload) validateEdipi(formats strfmt.Registry) error { + if swag.IsZero(m.Edipi) { // not required + return nil + } + + if err := validate.MaxLength("edipi", "body", m.Edipi, 10); err != nil { + return err + } + + return nil +} + func (m *CreateCustomerPayload) validateEmplid(formats strfmt.Registry) error { if swag.IsZero(m.Emplid) { // not required return nil diff --git a/pkg/handlers/ghcapi/customer.go b/pkg/handlers/ghcapi/customer.go index 51e57284945..7452fa9589c 100644 --- a/pkg/handlers/ghcapi/customer.go +++ b/pkg/handlers/ghcapi/customer.go @@ -163,7 +163,6 @@ func (h CreateCustomerWithOktaOptionHandler) Handle(params customercodeop.Create payload := params.Body var err error var serviceMembers []models.ServiceMember - var edipi *string var dodidUniqueFeatureFlag bool // evaluating feature flag to see if we need to check if the DODID exists already @@ -177,30 +176,31 @@ func (h CreateCustomerWithOktaOptionHandler) Handle(params customercodeop.Create } if dodidUniqueFeatureFlag { - if payload.Edipi == nil || *payload.Edipi == "" { - edipi = nil - } else { - query := `SELECT service_members.edipi + query := `SELECT service_members.edipi FROM service_members WHERE service_members.edipi = $1` - err := appCtx.DB().RawQuery(query, payload.Edipi).All(&serviceMembers) - if err != nil { - errorMsg := apperror.NewBadDataError("error when checking for existing service member") - payload := payloadForValidationError("Unable to create a customer", errorMsg.Error(), h.GetTraceIDFromRequest(params.HTTPRequest), validate.NewErrors()) - return customercodeop.NewCreateCustomerWithOktaOptionUnprocessableEntity().WithPayload(payload), errorMsg - } else if len(serviceMembers) > 0 { - errorMsg := apperror.NewConflictError(h.GetTraceIDFromRequest(params.HTTPRequest), "Service member with this DODID already exists. Please use a different DODID number.") - payload := payloadForValidationError("Unable to create a customer", errorMsg.Error(), h.GetTraceIDFromRequest(params.HTTPRequest), validate.NewErrors()) - return customercodeop.NewCreateCustomerWithOktaOptionUnprocessableEntity().WithPayload(payload), errorMsg - } + err := appCtx.DB().RawQuery(query, payload.Edipi).All(&serviceMembers) + if err != nil { + errorMsg := apperror.NewBadDataError("error when checking for existing service member") + payload := payloadForValidationError("Unable to create a customer", errorMsg.Error(), h.GetTraceIDFromRequest(params.HTTPRequest), validate.NewErrors()) + return customercodeop.NewCreateCustomerWithOktaOptionUnprocessableEntity().WithPayload(payload), errorMsg + } else if len(serviceMembers) > 0 { + errorMsg := apperror.NewConflictError(h.GetTraceIDFromRequest(params.HTTPRequest), "Service member with this DODID already exists. Please use a different DODID number.") + payload := payloadForValidationError("Unable to create a customer", errorMsg.Error(), h.GetTraceIDFromRequest(params.HTTPRequest), validate.NewErrors()) + return customercodeop.NewCreateCustomerWithOktaOptionUnprocessableEntity().WithPayload(payload), errorMsg } + } - if len(serviceMembers) == 0 { - edipi = params.Body.Edipi + // Endpoint specific EDIPI and EMPLID check + // The following validation currently is only intended for the customer creation + // conducted by an office user such as the Service Counselor + if payload.Affiliation != nil && *payload.Affiliation == ghcmessages.AffiliationCOASTGUARD { + // EMPLID cannot be null + if payload.Emplid == nil { + errorMsg := apperror.NewConflictError(h.GetTraceIDFromRequest(params.HTTPRequest), "Service members from the Coast Guard require an EMPLID for creation.") + payload := payloadForValidationError("Unable to create a customer", errorMsg.Error(), h.GetTraceIDFromRequest(params.HTTPRequest), validate.NewErrors()) + return customercodeop.NewCreateCustomerWithOktaOptionUnprocessableEntity().WithPayload(payload), errorMsg } - } else { - // If the feature flag is not enabled, we will just set the dodid and continue - edipi = params.Body.Edipi } var newServiceMember models.ServiceMember @@ -250,18 +250,11 @@ func (h CreateCustomerWithOktaOptionHandler) Handle(params customercodeop.Create residentialAddress := addressModelFromPayload(&payload.ResidentialAddress.Address) backupMailingAddress := addressModelFromPayload(&payload.BackupMailingAddress.Address) - var emplid *string - if *payload.Emplid == "" { - emplid = nil - } else { - emplid = payload.Emplid - } - // Create a new serviceMember using the userID newServiceMember = models.ServiceMember{ UserID: userID, - Edipi: edipi, - Emplid: emplid, + Edipi: &payload.Edipi, + Emplid: payload.Emplid, Affiliation: (*models.ServiceMemberAffiliation)(payload.Affiliation), FirstName: &payload.FirstName, MiddleName: payload.MiddleName, diff --git a/pkg/handlers/ghcapi/customer_test.go b/pkg/handlers/ghcapi/customer_test.go index ca46f85b2a5..16752b24cd0 100644 --- a/pkg/handlers/ghcapi/customer_test.go +++ b/pkg/handlers/ghcapi/customer_test.go @@ -162,7 +162,7 @@ func (suite *HandlerSuite) TestCreateCustomerWithOktaOptionHandler() { FirstName: "First", Telephone: handlers.FmtString("223-455-3399"), Affiliation: &affiliation, - Edipi: handlers.FmtString(""), + Edipi: "", Emplid: handlers.FmtString(""), PersonalEmail: *handlers.FmtString("email@email.com"), BackupContact: &ghcmessages.BackupContact{ @@ -260,7 +260,7 @@ func (suite *HandlerSuite) TestCreateCustomerWithOktaOptionHandler() { FirstName: "First", Telephone: handlers.FmtString("223-455-3399"), Affiliation: &affiliation, - Edipi: customer.Edipi, + Edipi: *customer.Edipi, PersonalEmail: *handlers.FmtString("email@email.com"), BackupContact: &ghcmessages.BackupContact{ Name: handlers.FmtString("New Backup Contact"), @@ -298,6 +298,81 @@ func (suite *HandlerSuite) TestCreateCustomerWithOktaOptionHandler() { response := handler.Handle(params) suite.Assertions.IsType(&customerops.CreateCustomerWithOktaOptionUnprocessableEntity{}, response) }) + + suite.Run("Unable to create customer of affiliation Coast Guard with no EMPLID", func() { + // in order to call the endpoint, we need to be an authenticated office user that's a SC + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) + officeUser.User.Roles = append(officeUser.User.Roles, roles.Role{ + RoleType: roles.RoleTypeServicesCounselor, + }) + + // Build provider + provider, err := factory.BuildOktaProvider(officeProviderName) + suite.NoError(err) + + mockAndActivateOktaEndpoints(provider) + + residentialAddress := ghcmessages.Address{ + StreetAddress1: handlers.FmtString("123 New Street"), + City: handlers.FmtString("Newcity"), + State: handlers.FmtString("MA"), + PostalCode: handlers.FmtString("02110"), + } + + backupAddress := ghcmessages.Address{ + StreetAddress1: handlers.FmtString("123 Backup Street"), + City: handlers.FmtString("Backupcity"), + State: handlers.FmtString("MA"), + PostalCode: handlers.FmtString("02115"), + } + + affiliation := ghcmessages.AffiliationCOASTGUARD + + body := &ghcmessages.CreateCustomerPayload{ + LastName: "Last", + FirstName: "First", + Telephone: handlers.FmtString("223-455-3399"), + Affiliation: &affiliation, + Edipi: "1234567890", + PersonalEmail: *handlers.FmtString("email@email.com"), + BackupContact: &ghcmessages.BackupContact{ + Name: handlers.FmtString("New Backup Contact"), + Phone: handlers.FmtString("445-345-1212"), + Email: handlers.FmtString("newbackup@mail.com"), + }, + ResidentialAddress: struct { + ghcmessages.Address + }{ + Address: residentialAddress, + }, + BackupMailingAddress: struct { + ghcmessages.Address + }{ + Address: backupAddress, + }, + CreateOktaAccount: true, + // when CacUser is false, this indicates a non-CAC user so CacValidated is set to true + CacUser: false, + } + + defer goth.ClearProviders() + goth.UseProviders(provider) + + request := httptest.NewRequest("POST", "/customer", nil) + request = suite.AuthenticateOfficeRequest(request, officeUser) + params := customerops.CreateCustomerWithOktaOptionParams{ + HTTPRequest: request, + Body: body, + } + handlerConfig := suite.HandlerConfig() + handler := CreateCustomerWithOktaOptionHandler{ + handlerConfig, + } + response := handler.Handle(params) + suite.Assertions.IsType(&customerops.CreateCustomerWithOktaOptionUnprocessableEntity{}, response) + failedToCreateCustomerPayload := response.(*customerops.CreateCustomerWithOktaOptionUnprocessableEntity).Payload.ClientError.Detail + suite.Equal("ID: 00000000-0000-0000-0000-000000000000 is in a conflicting state Service members from the Coast Guard require an EMPLID for creation.", *failedToCreateCustomerPayload) + }) } func (suite *HandlerSuite) TestSearchCustomersHandler() { diff --git a/swagger-def/ghc.yaml b/swagger-def/ghc.yaml index db8f313233d..f0bc6fb305c 100644 --- a/swagger-def/ghc.yaml +++ b/swagger-def/ghc.yaml @@ -4475,8 +4475,9 @@ definitions: $ref: 'definitions/Affiliation.yaml' edipi: type: string - example: John - x-nullable: true + example: '1234567890' + maxLength: 10 + x-nullable: false emplid: type: string example: '9485155' diff --git a/swagger/ghc.yaml b/swagger/ghc.yaml index a45a403a145..3da9f7fef35 100644 --- a/swagger/ghc.yaml +++ b/swagger/ghc.yaml @@ -4666,8 +4666,9 @@ definitions: $ref: '#/definitions/Affiliation' edipi: type: string - example: John - x-nullable: true + example: '1234567890' + maxLength: 10 + x-nullable: false emplid: type: string example: '9485155' From 3ee86614a122625af0dd1313580a393a4e15b4bf Mon Sep 17 00:00:00 2001 From: cameroncaci Date: Fri, 6 Sep 2024 15:01:22 +0000 Subject: [PATCH 25/68] adjust yup error --- src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx index 3386c8c7e24..519f6e4fbc2 100644 --- a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx +++ b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx @@ -161,7 +161,7 @@ export const CreateCustomerForm = ({ userPrivileges, setFlashMessage }) => { then: () => Yup.string() .matches(/[0-9]{7}/, 'Enter a 7-digit EMPLID number') - .required(`EMPLID is required for ${departmentIndicators.COAST_GUARD}`), + .required(`EMPLID is required for the Coast Guard`), otherwise: Yup.string().notRequired(), }), first_name: Yup.string().required('Required'), From b1714abd696417e79c241ea5d5188d125a3b148e Mon Sep 17 00:00:00 2001 From: Alex Lusk Date: Fri, 6 Sep 2024 16:47:00 +0000 Subject: [PATCH 26/68] improve handling of switching gblocs when user switches roles --- src/components/Table/TableCSVExportButton.jsx | 6 +++++- src/components/Table/TableQueue.jsx | 7 ++++++- src/pages/Office/HeadquartersQueues/HeadquartersQueues.jsx | 4 ++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/components/Table/TableCSVExportButton.jsx b/src/components/Table/TableCSVExportButton.jsx index 9b5937c3642..5d3180ede70 100644 --- a/src/components/Table/TableCSVExportButton.jsx +++ b/src/components/Table/TableCSVExportButton.jsx @@ -18,6 +18,7 @@ const TableCSVExportButton = ({ paramSort, paramFilters, className, + isHeadquartersUser, }) => { const [isLoading, setIsLoading] = useState(false); const [csvRows, setCsvRows] = useState([]); @@ -25,7 +26,7 @@ const TableCSVExportButton = ({ const { id: sortColumn, desc: sortOrder } = paramSort.length ? paramSort[0] : {}; const gblocContext = useContext(SelectedGblocContext); - const { selectedGbloc } = gblocContext || { selectedGbloc: undefined }; + const { selectedGbloc } = isHeadquartersUser && gblocContext ? gblocContext : { selectedGbloc: undefined }; const formatDataForExport = (data, columns = tableColumns) => { const formattedData = []; @@ -110,6 +111,8 @@ TableCSVExportButton.propTypes = { paramSort: PropTypes.array, // paramSort is the filter columns and values currently applied to the queue paramFilters: PropTypes.array, + // isHeadquartersUser identifies if the active role is a headquarters user to allow switching GBLOCs + isHeadquartersUser: PropTypes.bool, }; TableCSVExportButton.defaultProps = { @@ -118,6 +121,7 @@ TableCSVExportButton.defaultProps = { hiddenColumns: [], paramSort: [], paramFilters: [], + isHeadquartersUser: false, }; export default TableCSVExportButton; diff --git a/src/components/Table/TableQueue.jsx b/src/components/Table/TableQueue.jsx index 5182fed428d..d09605914c4 100644 --- a/src/components/Table/TableQueue.jsx +++ b/src/components/Table/TableQueue.jsx @@ -48,6 +48,7 @@ const TableQueue = ({ csvExportQueueFetcher, csvExportQueueFetcherKey, sessionStorageKey, + isHeadquartersUser, }) => { const [isPageReload, setIsPageReload] = useState(true); useEffect(() => { @@ -87,7 +88,7 @@ const TableQueue = ({ const { id, desc } = paramSort.length ? paramSort[0] : {}; const gblocContext = useContext(SelectedGblocContext); - const { selectedGbloc } = gblocContext || { selectedGbloc: undefined }; + const { selectedGbloc } = isHeadquartersUser && gblocContext ? gblocContext : { selectedGbloc: undefined }; const multiSelectValueDelimiter = ','; @@ -312,6 +313,7 @@ const TableQueue = ({ totalCount={totalCount} paramSort={paramSort} paramFilters={paramFilters} + isHeadquartersUser={isHeadquartersUser} /> )}
@@ -381,6 +383,8 @@ TableQueue.propTypes = { csvExportQueueFetcherKey: PropTypes.string, // session storage key to store search filters sessionStorageKey: PropTypes.string, + // isHeadquartersUser identifies if the active role is a headquarters user to allow switching GBLOCs + isHeadquartersUser: PropTypes.bool, }; TableQueue.defaultProps = { @@ -399,5 +403,6 @@ TableQueue.defaultProps = { csvExportQueueFetcher: null, csvExportQueueFetcherKey: null, sessionStorageKey: 'default', + isHeadquartersUser: false, }; export default TableQueue; diff --git a/src/pages/Office/HeadquartersQueues/HeadquartersQueues.jsx b/src/pages/Office/HeadquartersQueues/HeadquartersQueues.jsx index 9c6f613cc7e..8ba085fc464 100644 --- a/src/pages/Office/HeadquartersQueues/HeadquartersQueues.jsx +++ b/src/pages/Office/HeadquartersQueues/HeadquartersQueues.jsx @@ -247,6 +247,7 @@ const HeadquartersQueue = () => { csvExportQueueFetcher={getMovesQueue} csvExportQueueFetcherKey="queueMoves" sessionStorageKey={queueType} + isHeadquartersUser />
); @@ -273,6 +274,7 @@ const HeadquartersQueue = () => { csvExportQueueFetcher={getPaymentRequestsQueue} csvExportQueueFetcherKey="queuePaymentRequests" sessionStorageKey={queueType} + isHeadquartersUser />
); @@ -299,6 +301,7 @@ const HeadquartersQueue = () => { csvExportQueueFetcher={getServicesCounselingPPMQueue} csvExportQueueFetcherKey="queueMoves" sessionStorageKey={queueType} + isHeadquartersUser />
); @@ -326,6 +329,7 @@ const HeadquartersQueue = () => { csvExportQueueFetcher={getServicesCounselingQueue} csvExportQueueFetcherKey="queueMoves" sessionStorageKey={queueType} + isHeadquartersUser />
); From a9ed198a7928fa414fbc0a3683ef84bbdcc18535 Mon Sep 17 00:00:00 2001 From: cameroncaci Date: Fri, 6 Sep 2024 13:55:22 -0400 Subject: [PATCH 27/68] change capitalization --- src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx index 519f6e4fbc2..a5b38dc4ce0 100644 --- a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx +++ b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx @@ -149,7 +149,7 @@ export const CreateCustomerForm = ({ userPrivileges, setFlashMessage }) => { edipi: !isSafetyMove && Yup.string() - .matches(/[0-9]{10}/, 'Enter a 10-digit DOD ID number') + .matches(/[0-9]{10}/, 'Enter a 10-digit DoD ID number') .required('Required'), // Only the coast guard requires both EDIPI and EMPLID // unless it is a safety move From c6b9130bab4914ae2e4f2d0137a89a43d77e2cad Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Sep 2024 11:42:33 +0000 Subject: [PATCH 28/68] Bump alpine from 3.20.2 to 3.20.3 Bumps alpine from 3.20.2 to 3.20.3. --- updated-dependencies: - dependency-name: alpine dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Dockerfile.e2e | 2 +- Dockerfile.migrations | 2 +- Dockerfile.migrations_local | 2 +- Dockerfile.reviewapp | 2 +- Dockerfile.tools | 2 +- Dockerfile.tools_local | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Dockerfile.e2e b/Dockerfile.e2e index d161f38e33d..c88d243eb48 100644 --- a/Dockerfile.e2e +++ b/Dockerfile.e2e @@ -1,4 +1,4 @@ -FROM alpine:3.20.2 +FROM alpine:3.20.3 # hadolint ignore=DL3017 RUN apk upgrade --no-cache busybox diff --git a/Dockerfile.migrations b/Dockerfile.migrations index c3852e777b4..b4187431cd2 100644 --- a/Dockerfile.migrations +++ b/Dockerfile.migrations @@ -1,4 +1,4 @@ -FROM alpine:3.20.2 +FROM alpine:3.20.3 # hadolint ignore=DL3017 RUN apk upgrade --no-cache busybox diff --git a/Dockerfile.migrations_local b/Dockerfile.migrations_local index de2cbbb3481..3b2d11e7444 100644 --- a/Dockerfile.migrations_local +++ b/Dockerfile.migrations_local @@ -18,7 +18,7 @@ RUN rm -f bin/milmove && make bin/milmove # FINAL # ######### -FROM alpine:3.20.2 +FROM alpine:3.20.3 # hadolint ignore=DL3017 RUN apk upgrade --no-cache busybox diff --git a/Dockerfile.reviewapp b/Dockerfile.reviewapp index c27b975a8c9..cd35976410f 100644 --- a/Dockerfile.reviewapp +++ b/Dockerfile.reviewapp @@ -45,7 +45,7 @@ RUN set -x \ && make bin/generate-test-data # define migrations before client build since it doesn't need client -FROM alpine:3.20.2 as migrate +FROM alpine:3.20.3 as migrate COPY --from=server_builder /build/bin/rds-ca-2019-root.pem /bin/rds-ca-2019-root.pem COPY --from=server_builder /build/bin/milmove /bin/milmove diff --git a/Dockerfile.tools b/Dockerfile.tools index c3fd78134b6..97fe065b9d3 100644 --- a/Dockerfile.tools +++ b/Dockerfile.tools @@ -1,4 +1,4 @@ -FROM alpine:3.20.2 +FROM alpine:3.20.3 # hadolint ignore=DL3017 RUN apk upgrade --no-cache busybox diff --git a/Dockerfile.tools_local b/Dockerfile.tools_local index a68970c839d..fa2f95af5f7 100644 --- a/Dockerfile.tools_local +++ b/Dockerfile.tools_local @@ -18,7 +18,7 @@ RUN rm -f bin/prime-api-client && make bin/prime-api-client # FINAL # ######### -FROM alpine:3.20.2 +FROM alpine:3.20.3 # hadolint ignore=DL3017 RUN apk upgrade --no-cache busybox From da09f5e2222d084ec6dc57a3b0033e578717b79b Mon Sep 17 00:00:00 2001 From: loganwc Date: Mon, 9 Sep 2024 16:09:18 +0000 Subject: [PATCH 29/68] moved download button into document viewer component --- .../DocumentViewer/DocumentViewer.jsx | 20 +++++++++++++++-- .../DocumentViewer/DocumentViewer.module.scss | 7 ++++++ .../PaymentRequestReview.jsx | 22 ++----------------- 3 files changed, 27 insertions(+), 22 deletions(-) diff --git a/src/components/DocumentViewer/DocumentViewer.jsx b/src/components/DocumentViewer/DocumentViewer.jsx index 98494e3f163..73daff482d3 100644 --- a/src/components/DocumentViewer/DocumentViewer.jsx +++ b/src/components/DocumentViewer/DocumentViewer.jsx @@ -12,9 +12,10 @@ import Menu from './Menu/Menu'; import { milmoveLogger } from 'utils/milmoveLog'; import { UPLOADS } from 'constants/queryKeys'; -import { updateUpload } from 'services/ghcApi'; +import { bulkDownloadPaymentRequest, updateUpload } from 'services/ghcApi'; import { formatDate } from 'shared/dates'; import { filenameFromPath } from 'utils/formatters'; +import AsyncPacketDownloadLink from 'shared/AsyncPacketDownloadLink/AsyncPacketDownloadLink'; /** * TODO @@ -23,7 +24,7 @@ import { filenameFromPath } from 'utils/formatters'; * - handle fetch doc errors */ -const DocumentViewer = ({ files, allowDownload }) => { +const DocumentViewer = ({ files, allowDownload, paymentRequestId }) => { const [selectedFileIndex, selectFile] = useState(0); const [disableSaveButton, setDisableSaveButton] = useState(false); const [menuIsOpen, setMenuOpen] = useState(false); @@ -117,6 +118,20 @@ const DocumentViewer = ({ files, allowDownload }) => { } }; + const paymentPacketDownload = ( +
+
+

+ +

+
+
+ ); + return (
@@ -133,6 +148,7 @@ const DocumentViewer = ({ files, allowDownload }) => {

)} + {paymentRequestId !== undefined ? paymentPacketDownload : null}
{ const navigate = useNavigate(); @@ -173,28 +172,11 @@ export const PaymentRequestReview = ({ order }) => { navigate(`/moves/${moveCode}/payment-requests`); }; - const paymentPacketDownload = ( -
-
-

- -

-
-
- ); - return (
{uploads.length > 0 ? ( - <> - {paymentPacketDownload} - - + ) : (

No documents provided

)} From f4b2fe63288f3f8129fd707625ad8f7ab2a1dfa0 Mon Sep 17 00:00:00 2001 From: loganwc Date: Mon, 9 Sep 2024 16:33:45 +0000 Subject: [PATCH 30/68] moved bulk download test --- .../DocumentViewer/DocumentViewer.test.jsx | 42 ++++++++++++++++++- .../PaymentRequestReview.test.jsx | 36 ++-------------- 2 files changed, 45 insertions(+), 33 deletions(-) diff --git a/src/components/DocumentViewer/DocumentViewer.test.jsx b/src/components/DocumentViewer/DocumentViewer.test.jsx index 31d705ea4f5..b8306cbdf58 100644 --- a/src/components/DocumentViewer/DocumentViewer.test.jsx +++ b/src/components/DocumentViewer/DocumentViewer.test.jsx @@ -1,6 +1,6 @@ /* eslint-disable react/jsx-props-no-spreading */ import React from 'react'; -import { render, screen } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { QueryClientProvider, QueryClient } from '@tanstack/react-query'; @@ -10,6 +10,8 @@ import sampleJPG from './sample.jpg'; import samplePNG from './sample2.png'; import sampleGIF from './sample3.gif'; +import { bulkDownloadPaymentRequest } from 'services/ghcApi'; + const toggleMenuClass = () => { const container = document.querySelector('[data-testid="menuButtonContainer"]'); if (container) { @@ -50,6 +52,11 @@ const mockFiles = [ }, ]; +jest.mock('services/ghcApi', () => ({ + ...jest.requireActual('services/ghcApi'), + bulkDownloadPaymentRequest: jest.fn(), +})); + jest.mock('./Content/Content', () => ({ __esModule: true, default: ({ id, filename, contentType, url, createdAt, rotation }) => ( @@ -172,4 +179,37 @@ describe('DocumentViewer component', () => { expect(screen.getByText('id: undefined')).toBeInTheDocument(); }); + + describe('when clicking download Download All Files button', () => { + it('downloads a bulk packet', async () => { + const mockResponse = { + ok: true, + headers: { + 'content-disposition': 'filename="test.pdf"', + }, + status: 200, + data: null, + }; + + render( + + + , + ); + + bulkDownloadPaymentRequest.mockImplementation(() => Promise.resolve(mockResponse)); + + const downloadButton = screen.getByText('Download All Files (PDF)', { exact: false }); + await userEvent.click(downloadButton); + await waitFor(() => { + expect(bulkDownloadPaymentRequest).toHaveBeenCalledTimes(1); + }); + }); + }); }); diff --git a/src/pages/Office/PaymentRequestReview/PaymentRequestReview.test.jsx b/src/pages/Office/PaymentRequestReview/PaymentRequestReview.test.jsx index 1426bb27a60..f95bd113559 100644 --- a/src/pages/Office/PaymentRequestReview/PaymentRequestReview.test.jsx +++ b/src/pages/Office/PaymentRequestReview/PaymentRequestReview.test.jsx @@ -6,7 +6,7 @@ import userEvent from '@testing-library/user-event'; import { PaymentRequestReview } from './PaymentRequestReview'; -import { patchPaymentServiceItemStatus, bulkDownloadPaymentRequest } from 'services/ghcApi'; +import { patchPaymentServiceItemStatus } from 'services/ghcApi'; import { SHIPMENT_OPTIONS, PAYMENT_REQUEST_STATUS, PAYMENT_SERVICE_ITEM_STATUS } from 'shared/constants'; import { usePaymentRequestQueries } from 'hooks/queries'; import { ReactQueryWrapper } from 'testUtils'; @@ -57,7 +57,6 @@ jest.mock('hooks/queries', () => ({ jest.mock('services/ghcApi', () => ({ ...jest.requireActual('services/ghcApi'), patchPaymentServiceItemStatus: jest.fn(), - bulkDownloadPaymentRequest: jest.fn(), })); // prevents react-fileviewer from throwing errors without mocking relevant DOM elements @@ -354,34 +353,7 @@ describe('PaymentRequestReview', () => { expect(reviewServiceItems.prop('serviceItemCards')).toEqual(expectedServiceItemCards); }); }); - describe('when clicking download Download All Files button', () => { - it('downloads a bulk packet', async () => { - usePaymentRequestQueries.mockReturnValue(usePaymentRequestQueriesReturnValuePendingFinalReview); - const mockResponse = { - ok: true, - headers: { - 'content-disposition': 'filename="test.pdf"', - }, - status: 200, - data: null, - }; - - render( - - - , - ); - - bulkDownloadPaymentRequest.mockImplementation(() => Promise.resolve(mockResponse)); - - const downloadButton = screen.getByText('Download All Files (PDF)', { exact: false }); - await userEvent.click(downloadButton); - await waitFor(() => { - expect(bulkDownloadPaymentRequest).toHaveBeenCalledTimes(1); - }); - }); - }); describe('clicking the next button', () => { describe('with pending requests', () => { beforeEach(async () => { @@ -512,9 +484,9 @@ describe('PaymentRequestReview', () => { expect(terms[1]).toHaveTextContent('Accepted'); expect(terms[2]).toHaveTextContent('Rejected'); const definitions = screen.getAllByRole('definition'); - expect(definitions[1]).toHaveTextContent('$1,703.10'); - expect(definitions[2]).toHaveTextContent('$1,579.98'); - expect(definitions[3]).toHaveTextContent('$123.12'); + expect(definitions[0]).toHaveTextContent('$1,703.10'); + expect(definitions[1]).toHaveTextContent('$1,579.98'); + expect(definitions[2]).toHaveTextContent('$123.12'); }); it('navigates back, and shows the correct icons for approved and rejected cards', async () => { await userEvent.click(screen.getByRole('button', { name: 'Back' })); From 3d336766b2201c9af36455781c93edc86db4e24f Mon Sep 17 00:00:00 2001 From: Cory Kleinjan Date: Mon, 9 Sep 2024 20:02:38 +0000 Subject: [PATCH 31/68] replacing bad migration --- migrations/app/migrations_manifest.txt | 2 +- ...ces_to_unpriced_cs_ms_service_items.up.sql | 28 ------------------- ...ricing_unpriced_ms_cs_service_items.up.sql | 19 +++++++++++++ 3 files changed, 20 insertions(+), 29 deletions(-) delete mode 100644 migrations/app/schema/20240905192913_adding_prices_to_unpriced_cs_ms_service_items.up.sql create mode 100644 migrations/app/schema/20240909194514_pricing_unpriced_ms_cs_service_items.up.sql diff --git a/migrations/app/migrations_manifest.txt b/migrations/app/migrations_manifest.txt index 7bdf1dce58a..7f3791c05d2 100644 --- a/migrations/app/migrations_manifest.txt +++ b/migrations/app/migrations_manifest.txt @@ -981,4 +981,4 @@ 20240820125856_allow_pptas_migration.up.sql 20240821180447_populating_locked_price_cents_for_ms_and_cs.up.sql 20240822180409_adding_locked_price_cents_service_param.up.sql -20240905192913_adding_prices_to_unpriced_cs_ms_service_items.up.sql +20240909194514_pricing_unpriced_ms_cs_service_items.up.sql diff --git a/migrations/app/schema/20240905192913_adding_prices_to_unpriced_cs_ms_service_items.up.sql b/migrations/app/schema/20240905192913_adding_prices_to_unpriced_cs_ms_service_items.up.sql deleted file mode 100644 index a3172cc10c5..00000000000 --- a/migrations/app/schema/20240905192913_adding_prices_to_unpriced_cs_ms_service_items.up.sql +++ /dev/null @@ -1,28 +0,0 @@ --- Filling in pricing_estimates for unprices services code MS and CS service items. Service items should not be able to reach this state --- but some older data exists where unpriced MS and CS items exist -SET statement_timeout = 300000; -SET lock_timeout = 300000; -SET idle_in_transaction_session_timeout = 300000; - -UPDATE mto_service_items AS ms -SET locked_price_cents = - CASE - when price_cents > 0 then price_cents - when price_cents <= 0 then 0 - END, - pricing_estimate = - CASE - when price_cents > 0 then price_cents - when price_cents <= 0 then 0 - END -FROM re_task_order_fees AS tf -JOIN re_contract_years AS cy -ON tf.contract_year_id = cy.id -JOIN re_contracts AS c -ON cy.contract_id = c.id -JOIN re_services AS s -ON tf.service_id = s.id -WHERE ms.locked_price_cents = null - AND ms.pricing_estimate = null - AND re_service_id = s.id - AND s.code = 'MS' OR s.code = 'CS' \ No newline at end of file diff --git a/migrations/app/schema/20240909194514_pricing_unpriced_ms_cs_service_items.up.sql b/migrations/app/schema/20240909194514_pricing_unpriced_ms_cs_service_items.up.sql new file mode 100644 index 00000000000..d90ca544085 --- /dev/null +++ b/migrations/app/schema/20240909194514_pricing_unpriced_ms_cs_service_items.up.sql @@ -0,0 +1,19 @@ +UPDATE mto_service_items AS ms +SET locked_price_cents = + CASE + when price_cents > 0 AND (s.code = 'MS' OR s.code = 'CS') AND re_service_id = s.id then price_cents + when price_cents = 0 AND (s.code = 'MS' OR s.code = 'CS') AND re_service_id = s.id then 0 + END, + pricing_estimate = + CASE + when price_cents > 0 AND (s.code = 'MS' OR s.code = 'CS') AND re_service_id = s.id then price_cents + when price_cents = 0 AND (s.code = 'MS' OR s.code = 'CS') AND re_service_id = s.id then 0 + END +FROM re_task_order_fees AS tf +JOIN re_services AS s +ON tf.service_id = s.id +JOIN re_contract_years AS cy +ON tf.contract_year_id = cy.id +JOIN re_contracts AS c +ON cy.contract_id = c.id +WHERE (s.code = 'MS' OR s.code = 'CS') AND ms.re_service_id = s.id AND ms.locked_price_cents is null AND ms.pricing_estimate is null; \ No newline at end of file From b145dd244b7858c101c19ae87e3b9d1fe63003a0 Mon Sep 17 00:00:00 2001 From: Cory Kleinjan Date: Mon, 9 Sep 2024 20:31:27 +0000 Subject: [PATCH 32/68] Replacing bad migration --- migrations/app/migrations_manifest.txt | 2 +- ...ating_locked_price_cents_for_ms_and_cs_service_items.up.sql} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename migrations/app/schema/{20240821180447_populating_locked_price_cents_for_ms_and_cs.up.sql => 20240909202158_populating_locked_price_cents_for_ms_and_cs_service_items.up.sql} (82%) diff --git a/migrations/app/migrations_manifest.txt b/migrations/app/migrations_manifest.txt index 7f3791c05d2..936b8fdf45d 100644 --- a/migrations/app/migrations_manifest.txt +++ b/migrations/app/migrations_manifest.txt @@ -979,6 +979,6 @@ 20240807140736_add_locked_price_cents_to_mto_service_items.up.sql 20240814144527_remove_allow_pptas_client.up.sql 20240820125856_allow_pptas_migration.up.sql -20240821180447_populating_locked_price_cents_for_ms_and_cs.up.sql 20240822180409_adding_locked_price_cents_service_param.up.sql 20240909194514_pricing_unpriced_ms_cs_service_items.up.sql +20240909202158_populating_locked_price_cents_for_ms_and_cs_service_items.up.sql diff --git a/migrations/app/schema/20240821180447_populating_locked_price_cents_for_ms_and_cs.up.sql b/migrations/app/schema/20240909202158_populating_locked_price_cents_for_ms_and_cs_service_items.up.sql similarity index 82% rename from migrations/app/schema/20240821180447_populating_locked_price_cents_for_ms_and_cs.up.sql rename to migrations/app/schema/20240909202158_populating_locked_price_cents_for_ms_and_cs_service_items.up.sql index e4f686802a2..bf154feebe5 100644 --- a/migrations/app/schema/20240821180447_populating_locked_price_cents_for_ms_and_cs.up.sql +++ b/migrations/app/schema/20240909202158_populating_locked_price_cents_for_ms_and_cs_service_items.up.sql @@ -6,4 +6,4 @@ SET idle_in_transaction_session_timeout = 300000; UPDATE mto_service_items AS ms SET locked_price_cents = pricing_estimate FROM re_services AS r -WHERE ms.re_service_id = r.id AND r.code = 'MS' OR r.code = 'CS' \ No newline at end of file +WHERE ms.re_service_id = r.id AND (r.code = 'MS' OR r.code = 'CS') \ No newline at end of file From bbf942282aa73229f1341af78ba60f40a94a7743 Mon Sep 17 00:00:00 2001 From: Cory Kleinjan Date: Tue, 10 Sep 2024 02:09:50 +0000 Subject: [PATCH 33/68] Updating new migration --- ...pricing_unpriced_ms_cs_service_items.up.sql | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/migrations/app/schema/20240909194514_pricing_unpriced_ms_cs_service_items.up.sql b/migrations/app/schema/20240909194514_pricing_unpriced_ms_cs_service_items.up.sql index d90ca544085..e8645f9adcc 100644 --- a/migrations/app/schema/20240909194514_pricing_unpriced_ms_cs_service_items.up.sql +++ b/migrations/app/schema/20240909194514_pricing_unpriced_ms_cs_service_items.up.sql @@ -1,19 +1,23 @@ UPDATE mto_service_items AS ms SET locked_price_cents = CASE - when price_cents > 0 AND (s.code = 'MS' OR s.code = 'CS') AND re_service_id = s.id then price_cents - when price_cents = 0 AND (s.code = 'MS' OR s.code = 'CS') AND re_service_id = s.id then 0 + when price_cents > 0 AND (s.code = 'MS' OR s.code = 'CS') AND ms.re_service_id = s.id then price_cents + when price_cents = 0 AND (s.code = 'MS' OR s.code = 'CS') AND ms.re_service_id = s.id then 0 END, pricing_estimate = CASE - when price_cents > 0 AND (s.code = 'MS' OR s.code = 'CS') AND re_service_id = s.id then price_cents - when price_cents = 0 AND (s.code = 'MS' OR s.code = 'CS') AND re_service_id = s.id then 0 + when price_cents > 0 AND (s.code = 'MS' OR s.code = 'CS') AND ms.re_service_id = s.id then price_cents + when price_cents = 0 AND (s.code = 'MS' OR s.code = 'CS') AND ms.re_service_id = s.id then 0 END FROM re_task_order_fees AS tf JOIN re_services AS s ON tf.service_id = s.id JOIN re_contract_years AS cy ON tf.contract_year_id = cy.id -JOIN re_contracts AS c -ON cy.contract_id = c.id -WHERE (s.code = 'MS' OR s.code = 'CS') AND ms.re_service_id = s.id AND ms.locked_price_cents is null AND ms.pricing_estimate is null; \ No newline at end of file +JOIN re_contracts AS ct +ON cy.contract_id = ct.id +JOIN mto_service_items AS msi +ON s.id = msi.re_service_id +JOIN moves AS mo +ON mo.id = msi.move_id +WHERE (s.code = 'MS' OR s.code = 'CS') AND (mo.available_to_prime_at BETWEEN cy.start_date AND cy.end_date) AND ms.re_service_id = s.id AND ms.locked_price_cents is null AND ms.pricing_estimate is null; \ No newline at end of file From 30f00b049dd68429802cb835383b3bd74e7a4770 Mon Sep 17 00:00:00 2001 From: Cory Kleinjan Date: Tue, 10 Sep 2024 02:17:17 +0000 Subject: [PATCH 34/68] Replacing bad migration --- migrations/app/migrations_manifest.txt | 2 +- ...ts_from_pricing_estimate_for_ms_and_cs_service_items.up.sql} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename migrations/app/schema/{20240909202158_populating_locked_price_cents_for_ms_and_cs_service_items.up.sql => 20240910021542_populating_locked_price_cents_from_pricing_estimate_for_ms_and_cs_service_items.up.sql} (100%) diff --git a/migrations/app/migrations_manifest.txt b/migrations/app/migrations_manifest.txt index 936b8fdf45d..dca81608d11 100644 --- a/migrations/app/migrations_manifest.txt +++ b/migrations/app/migrations_manifest.txt @@ -981,4 +981,4 @@ 20240820125856_allow_pptas_migration.up.sql 20240822180409_adding_locked_price_cents_service_param.up.sql 20240909194514_pricing_unpriced_ms_cs_service_items.up.sql -20240909202158_populating_locked_price_cents_for_ms_and_cs_service_items.up.sql +20240910021542_populating_locked_price_cents_from_pricing_estimate_for_ms_and_cs_service_items.up.sql diff --git a/migrations/app/schema/20240909202158_populating_locked_price_cents_for_ms_and_cs_service_items.up.sql b/migrations/app/schema/20240910021542_populating_locked_price_cents_from_pricing_estimate_for_ms_and_cs_service_items.up.sql similarity index 100% rename from migrations/app/schema/20240909202158_populating_locked_price_cents_for_ms_and_cs_service_items.up.sql rename to migrations/app/schema/20240910021542_populating_locked_price_cents_from_pricing_estimate_for_ms_and_cs_service_items.up.sql From 828e3b62ec80d1caa2d63b49ef695928b608a0e8 Mon Sep 17 00:00:00 2001 From: Cory Kleinjan Date: Tue, 10 Sep 2024 02:50:03 +0000 Subject: [PATCH 35/68] Increasing lockout for migration --- ...240909194514_pricing_unpriced_ms_cs_service_items.up.sql | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/migrations/app/schema/20240909194514_pricing_unpriced_ms_cs_service_items.up.sql b/migrations/app/schema/20240909194514_pricing_unpriced_ms_cs_service_items.up.sql index e8645f9adcc..de31bcc645c 100644 --- a/migrations/app/schema/20240909194514_pricing_unpriced_ms_cs_service_items.up.sql +++ b/migrations/app/schema/20240909194514_pricing_unpriced_ms_cs_service_items.up.sql @@ -1,3 +1,9 @@ +-- Filling in pricing_estimates for unprices services code MS and CS service items. Service items should not be able to reach this state +-- but some older data exists where unpriced MS and CS items exist +SET statement_timeout = 300000; +SET lock_timeout = 300000; +SET idle_in_transaction_session_timeout = 300000; + UPDATE mto_service_items AS ms SET locked_price_cents = CASE From 6ca3ba8d5effe34015a16ab51e24f1e707f58426 Mon Sep 17 00:00:00 2001 From: loganwc Date: Tue, 10 Sep 2024 14:38:40 +0000 Subject: [PATCH 36/68] remove unused responses --- pkg/gen/ghcapi/embedded_spec.go | 18 ---- .../bulk_download_responses.go | 90 ------------------- swagger-def/ghc.yaml | 4 - swagger/ghc.yaml | 4 - 4 files changed, 116 deletions(-) diff --git a/pkg/gen/ghcapi/embedded_spec.go b/pkg/gen/ghcapi/embedded_spec.go index fb3aedf106b..417a21286f3 100644 --- a/pkg/gen/ghcapi/embedded_spec.go +++ b/pkg/gen/ghcapi/embedded_spec.go @@ -3202,12 +3202,6 @@ func init() { "400": { "$ref": "#/responses/InvalidRequest" }, - "403": { - "$ref": "#/responses/PermissionDenied" - }, - "404": { - "$ref": "#/responses/NotFound" - }, "500": { "$ref": "#/responses/ServerError" } @@ -17827,18 +17821,6 @@ func init() { "$ref": "#/definitions/Error" } }, - "403": { - "description": "The request was denied", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "The requested resource wasn't found", - "schema": { - "$ref": "#/definitions/Error" - } - }, "500": { "description": "A server error occurred", "schema": { diff --git a/pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download_responses.go b/pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download_responses.go index aa2aa194d11..b00eca8e5c1 100644 --- a/pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download_responses.go +++ b/pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download_responses.go @@ -124,96 +124,6 @@ func (o *BulkDownloadBadRequest) WriteResponse(rw http.ResponseWriter, producer } } -// BulkDownloadForbiddenCode is the HTTP code returned for type BulkDownloadForbidden -const BulkDownloadForbiddenCode int = 403 - -/* -BulkDownloadForbidden The request was denied - -swagger:response bulkDownloadForbidden -*/ -type BulkDownloadForbidden struct { - - /* - In: Body - */ - Payload *ghcmessages.Error `json:"body,omitempty"` -} - -// NewBulkDownloadForbidden creates BulkDownloadForbidden with default headers values -func NewBulkDownloadForbidden() *BulkDownloadForbidden { - - return &BulkDownloadForbidden{} -} - -// WithPayload adds the payload to the bulk download forbidden response -func (o *BulkDownloadForbidden) WithPayload(payload *ghcmessages.Error) *BulkDownloadForbidden { - o.Payload = payload - return o -} - -// SetPayload sets the payload to the bulk download forbidden response -func (o *BulkDownloadForbidden) SetPayload(payload *ghcmessages.Error) { - o.Payload = payload -} - -// WriteResponse to the client -func (o *BulkDownloadForbidden) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { - - rw.WriteHeader(403) - if o.Payload != nil { - payload := o.Payload - if err := producer.Produce(rw, payload); err != nil { - panic(err) // let the recovery middleware deal with this - } - } -} - -// BulkDownloadNotFoundCode is the HTTP code returned for type BulkDownloadNotFound -const BulkDownloadNotFoundCode int = 404 - -/* -BulkDownloadNotFound The requested resource wasn't found - -swagger:response bulkDownloadNotFound -*/ -type BulkDownloadNotFound struct { - - /* - In: Body - */ - Payload *ghcmessages.Error `json:"body,omitempty"` -} - -// NewBulkDownloadNotFound creates BulkDownloadNotFound with default headers values -func NewBulkDownloadNotFound() *BulkDownloadNotFound { - - return &BulkDownloadNotFound{} -} - -// WithPayload adds the payload to the bulk download not found response -func (o *BulkDownloadNotFound) WithPayload(payload *ghcmessages.Error) *BulkDownloadNotFound { - o.Payload = payload - return o -} - -// SetPayload sets the payload to the bulk download not found response -func (o *BulkDownloadNotFound) SetPayload(payload *ghcmessages.Error) { - o.Payload = payload -} - -// WriteResponse to the client -func (o *BulkDownloadNotFound) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { - - rw.WriteHeader(404) - if o.Payload != nil { - payload := o.Payload - if err := producer.Produce(rw, payload); err != nil { - panic(err) // let the recovery middleware deal with this - } - } -} - // BulkDownloadInternalServerErrorCode is the HTTP code returned for type BulkDownloadInternalServerError const BulkDownloadInternalServerErrorCode int = 500 diff --git a/swagger-def/ghc.yaml b/swagger-def/ghc.yaml index 9c6b01ad2c3..5549e7ec03f 100644 --- a/swagger-def/ghc.yaml +++ b/swagger-def/ghc.yaml @@ -3133,10 +3133,6 @@ paths: type: file '400': $ref: '#/responses/InvalidRequest' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' '500': $ref: '#/responses/ServerError' /documents/{documentId}: diff --git a/swagger/ghc.yaml b/swagger/ghc.yaml index 81d1d5571b1..8f1f6cfc876 100644 --- a/swagger/ghc.yaml +++ b/swagger/ghc.yaml @@ -3247,10 +3247,6 @@ paths: type: file '400': $ref: '#/responses/InvalidRequest' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' '500': $ref: '#/responses/ServerError' /documents/{documentId}: From 17202f7625ea95a974296a85f834ff9082ca03da Mon Sep 17 00:00:00 2001 From: Paul Stonebraker Date: Wed, 11 Sep 2024 03:48:15 +0000 Subject: [PATCH 37/68] update prime to allow adding of second/third addresses to shipments --- pkg/gen/primev3api/embedded_spec.go | 84 +++++++++++++++++++ .../primev3messages/update_m_t_o_shipment.go | 52 ++++++++++++ .../primev3messages/update_p_p_m_shipment.go | 60 +++++++++++++ .../primeapiv3/payloads/payload_to_model.go | 34 ++++++++ .../Shipment/PrimeUIShipmentUpdate.jsx | 63 ++++++++++++-- .../Shipment/PrimeUIShipmentUpdateForm.jsx | 16 +++- .../Shipment/PrimeUIShipmentUpdatePPMForm.jsx | 81 +++++++++++++++++- .../PrimeUIShipmentUpdatePPMForm.test.jsx | 78 +++++++++++++++-- src/utils/formatters.js | 13 +++ swagger-def/prime_v3.yaml | 26 ++++++ swagger/prime_v3.yaml | 32 +++++++ 11 files changed, 518 insertions(+), 21 deletions(-) diff --git a/pkg/gen/primev3api/embedded_spec.go b/pkg/gen/primev3api/embedded_spec.go index b62a8e5d3f0..c71502c17ca 100644 --- a/pkg/gen/primev3api/embedded_spec.go +++ b/pkg/gen/primev3api/embedded_spec.go @@ -3100,6 +3100,22 @@ func init() { "$ref": "#/definitions/StorageFacility" } ] + }, + "tertiaryDeliveryAddress": { + "description": "A third delivery address for this shipment, if the customer entered one. An optional field.", + "allOf": [ + { + "$ref": "#/definitions/Address" + } + ] + }, + "tertiaryPickupAddress": { + "description": "A third pickup address for this shipment, if the customer entered one. An optional field.", + "allOf": [ + { + "$ref": "#/definitions/Address" + } + ] } } }, @@ -3154,6 +3170,16 @@ func init() { "x-nullable": true, "x-omitempty": false }, + "hasTertiaryDestinationAddress": { + "type": "boolean", + "x-nullable": true, + "x-omitempty": false + }, + "hasTertiaryPickupAddress": { + "type": "boolean", + "x-nullable": true, + "x-omitempty": false + }, "pickupAddress": { "description": "The address of the origin location where goods are being moved from.\n", "allOf": [ @@ -3220,6 +3246,22 @@ func init() { "description": "The estimated weight of the pro-gear being moved belonging to a spouse.", "type": "integer", "x-nullable": true + }, + "tertiaryDestinationAddress": { + "description": "An optional third address near the destination where goods will be dropped off.\n", + "allOf": [ + { + "$ref": "#/definitions/Address" + } + ] + }, + "tertiaryPickupAddress": { + "description": "An optional second pickup location near the origin where additional goods exist.\n", + "allOf": [ + { + "$ref": "#/definitions/Address" + } + ] } } }, @@ -6544,6 +6586,22 @@ func init() { "$ref": "#/definitions/StorageFacility" } ] + }, + "tertiaryDeliveryAddress": { + "description": "A third delivery address for this shipment, if the customer entered one. An optional field.", + "allOf": [ + { + "$ref": "#/definitions/Address" + } + ] + }, + "tertiaryPickupAddress": { + "description": "A third pickup address for this shipment, if the customer entered one. An optional field.", + "allOf": [ + { + "$ref": "#/definitions/Address" + } + ] } } }, @@ -6598,6 +6656,16 @@ func init() { "x-nullable": true, "x-omitempty": false }, + "hasTertiaryDestinationAddress": { + "type": "boolean", + "x-nullable": true, + "x-omitempty": false + }, + "hasTertiaryPickupAddress": { + "type": "boolean", + "x-nullable": true, + "x-omitempty": false + }, "pickupAddress": { "description": "The address of the origin location where goods are being moved from.\n", "allOf": [ @@ -6664,6 +6732,22 @@ func init() { "description": "The estimated weight of the pro-gear being moved belonging to a spouse.", "type": "integer", "x-nullable": true + }, + "tertiaryDestinationAddress": { + "description": "An optional third address near the destination where goods will be dropped off.\n", + "allOf": [ + { + "$ref": "#/definitions/Address" + } + ] + }, + "tertiaryPickupAddress": { + "description": "An optional second pickup location near the origin where additional goods exist.\n", + "allOf": [ + { + "$ref": "#/definitions/Address" + } + ] } } }, diff --git a/pkg/gen/primev3messages/update_m_t_o_shipment.go b/pkg/gen/primev3messages/update_m_t_o_shipment.go index 037dbf4557f..df0c18d8763 100644 --- a/pkg/gen/primev3messages/update_m_t_o_shipment.go +++ b/pkg/gen/primev3messages/update_m_t_o_shipment.go @@ -112,6 +112,16 @@ type UpdateMTOShipment struct { // storage facility StorageFacility *StorageFacility `json:"storageFacility,omitempty"` + + // A third delivery address for this shipment, if the customer entered one. An optional field. + TertiaryDeliveryAddress struct { + Address + } `json:"tertiaryDeliveryAddress,omitempty"` + + // A third pickup address for this shipment, if the customer entered one. An optional field. + TertiaryPickupAddress struct { + Address + } `json:"tertiaryPickupAddress,omitempty"` } // Validate validates this update m t o shipment @@ -178,6 +188,14 @@ func (m *UpdateMTOShipment) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validateTertiaryDeliveryAddress(formats); err != nil { + res = append(res, err) + } + + if err := m.validateTertiaryPickupAddress(formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { return errors.CompositeValidationError(res...) } @@ -374,6 +392,22 @@ func (m *UpdateMTOShipment) validateStorageFacility(formats strfmt.Registry) err return nil } +func (m *UpdateMTOShipment) validateTertiaryDeliveryAddress(formats strfmt.Registry) error { + if swag.IsZero(m.TertiaryDeliveryAddress) { // not required + return nil + } + + return nil +} + +func (m *UpdateMTOShipment) validateTertiaryPickupAddress(formats strfmt.Registry) error { + if swag.IsZero(m.TertiaryPickupAddress) { // not required + return nil + } + + return nil +} + // ContextValidate validate this update m t o shipment based on the context it is used func (m *UpdateMTOShipment) ContextValidate(ctx context.Context, formats strfmt.Registry) error { var res []error @@ -410,6 +444,14 @@ func (m *UpdateMTOShipment) ContextValidate(ctx context.Context, formats strfmt. res = append(res, err) } + if err := m.contextValidateTertiaryDeliveryAddress(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateTertiaryPickupAddress(ctx, formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { return errors.CompositeValidationError(res...) } @@ -517,6 +559,16 @@ func (m *UpdateMTOShipment) contextValidateStorageFacility(ctx context.Context, return nil } +func (m *UpdateMTOShipment) contextValidateTertiaryDeliveryAddress(ctx context.Context, formats strfmt.Registry) error { + + return nil +} + +func (m *UpdateMTOShipment) contextValidateTertiaryPickupAddress(ctx context.Context, formats strfmt.Registry) error { + + return nil +} + // MarshalBinary interface implementation func (m *UpdateMTOShipment) MarshalBinary() ([]byte, error) { if m == nil { diff --git a/pkg/gen/primev3messages/update_p_p_m_shipment.go b/pkg/gen/primev3messages/update_p_p_m_shipment.go index 6778ab0cb05..2fb250ddc38 100644 --- a/pkg/gen/primev3messages/update_p_p_m_shipment.go +++ b/pkg/gen/primev3messages/update_p_p_m_shipment.go @@ -44,6 +44,12 @@ type UpdatePPMShipment struct { // has secondary pickup address HasSecondaryPickupAddress *bool `json:"hasSecondaryPickupAddress"` + // has tertiary destination address + HasTertiaryDestinationAddress *bool `json:"hasTertiaryDestinationAddress"` + + // has tertiary pickup address + HasTertiaryPickupAddress *bool `json:"hasTertiaryPickupAddress"` + // The address of the origin location where goods are being moved from. // PickupAddress struct { @@ -88,6 +94,18 @@ type UpdatePPMShipment struct { // The estimated weight of the pro-gear being moved belonging to a spouse. SpouseProGearWeight *int64 `json:"spouseProGearWeight,omitempty"` + + // An optional third address near the destination where goods will be dropped off. + // + TertiaryDestinationAddress struct { + Address + } `json:"tertiaryDestinationAddress,omitempty"` + + // An optional second pickup location near the origin where additional goods exist. + // + TertiaryPickupAddress struct { + Address + } `json:"tertiaryPickupAddress,omitempty"` } // Validate validates this update p p m shipment @@ -126,6 +144,14 @@ func (m *UpdatePPMShipment) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validateTertiaryDestinationAddress(formats); err != nil { + res = append(res, err) + } + + if err := m.validateTertiaryPickupAddress(formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { return errors.CompositeValidationError(res...) } @@ -219,6 +245,22 @@ func (m *UpdatePPMShipment) validateSitLocation(formats strfmt.Registry) error { return nil } +func (m *UpdatePPMShipment) validateTertiaryDestinationAddress(formats strfmt.Registry) error { + if swag.IsZero(m.TertiaryDestinationAddress) { // not required + return nil + } + + return nil +} + +func (m *UpdatePPMShipment) validateTertiaryPickupAddress(formats strfmt.Registry) error { + if swag.IsZero(m.TertiaryPickupAddress) { // not required + return nil + } + + return nil +} + // ContextValidate validate this update p p m shipment based on the context it is used func (m *UpdatePPMShipment) ContextValidate(ctx context.Context, formats strfmt.Registry) error { var res []error @@ -243,6 +285,14 @@ func (m *UpdatePPMShipment) ContextValidate(ctx context.Context, formats strfmt. res = append(res, err) } + if err := m.contextValidateTertiaryDestinationAddress(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateTertiaryPickupAddress(ctx, formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { return errors.CompositeValidationError(res...) } @@ -290,6 +340,16 @@ func (m *UpdatePPMShipment) contextValidateSitLocation(ctx context.Context, form return nil } +func (m *UpdatePPMShipment) contextValidateTertiaryDestinationAddress(ctx context.Context, formats strfmt.Registry) error { + + return nil +} + +func (m *UpdatePPMShipment) contextValidateTertiaryPickupAddress(ctx context.Context, formats strfmt.Registry) error { + + return nil +} + // MarshalBinary interface implementation func (m *UpdatePPMShipment) MarshalBinary() ([]byte, error) { if m == nil { diff --git a/pkg/handlers/primeapiv3/payloads/payload_to_model.go b/pkg/handlers/primeapiv3/payloads/payload_to_model.go index 28be5539b61..50588acfb28 100644 --- a/pkg/handlers/primeapiv3/payloads/payload_to_model.go +++ b/pkg/handlers/primeapiv3/payloads/payload_to_model.go @@ -341,6 +341,14 @@ func MTOShipmentModelFromUpdate(mtoShipment *primev3messages.UpdateMTOShipment, model.HasSecondaryPickupAddress = handlers.FmtBool(true) } + addressModel = AddressModel(&mtoShipment.TertiaryPickupAddress.Address) + if addressModel != nil { + model.TertiaryPickupAddress = addressModel + tertiaryPickupAddressID := uuid.FromStringOrNil(addressModel.ID.String()) + model.TertiaryPickupAddressID = &tertiaryPickupAddressID + model.HasTertiaryPickupAddress = handlers.FmtBool(true) + } + addressModel = AddressModel(&mtoShipment.SecondaryDeliveryAddress.Address) if addressModel != nil { model.SecondaryDeliveryAddress = addressModel @@ -349,6 +357,14 @@ func MTOShipmentModelFromUpdate(mtoShipment *primev3messages.UpdateMTOShipment, model.HasSecondaryDeliveryAddress = handlers.FmtBool(true) } + addressModel = AddressModel(&mtoShipment.TertiaryDeliveryAddress.Address) + if addressModel != nil { + model.TertiaryDeliveryAddress = addressModel + tertiaryDeliveryAddressID := uuid.FromStringOrNil(addressModel.ID.String()) + model.TertiaryDeliveryAddressID = &tertiaryDeliveryAddressID + model.HasTertiaryDeliveryAddress = handlers.FmtBool(true) + } + if mtoShipment.PpmShipment != nil { model.PPMShipment = PPMShipmentModelFromUpdate(mtoShipment.PpmShipment) model.PPMShipment.Shipment = *model @@ -391,6 +407,15 @@ func PPMShipmentModelFromUpdate(ppmShipment *primev3messages.UpdatePPMShipment) } } + if ppmShipment.HasTertiaryPickupAddress != nil && *ppmShipment.HasTertiaryPickupAddress { + addressModel = AddressModel(&ppmShipment.TertiaryPickupAddress.Address) + if addressModel != nil { + model.TertiaryPickupAddress = addressModel + tertiaryPickupAddressID := uuid.FromStringOrNil(addressModel.ID.String()) + model.TertiaryPickupAddressID = &tertiaryPickupAddressID + } + } + addressModel = AddressModel(&ppmShipment.DestinationAddress.Address) if addressModel != nil { model.DestinationAddress = addressModel @@ -406,6 +431,15 @@ func PPMShipmentModelFromUpdate(ppmShipment *primev3messages.UpdatePPMShipment) } } + if ppmShipment.HasTertiaryDestinationAddress != nil && *ppmShipment.HasTertiaryDestinationAddress { + addressModel = AddressModel(&ppmShipment.TertiaryDestinationAddress.Address) + if addressModel != nil { + model.TertiaryDestinationAddress = addressModel + tertiaryDestinationAddressID := uuid.FromStringOrNil(addressModel.ID.String()) + model.TertiaryDestinationAddressID = &tertiaryDestinationAddressID + } + } + expectedDepartureDate := handlers.FmtDatePtrToPopPtr(ppmShipment.ExpectedDepartureDate) if expectedDepartureDate != nil && !expectedDepartureDate.IsZero() { model.ExpectedDepartureDate = *expectedDepartureDate diff --git a/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdate.jsx b/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdate.jsx index 699700e7ee8..d202e0a9e3d 100644 --- a/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdate.jsx +++ b/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdate.jsx @@ -20,7 +20,12 @@ import formStyles from 'styles/form.module.scss'; import WizardNavigation from 'components/Customer/WizardNavigation/WizardNavigation'; import { requiredAddressSchema, addressSchema } from 'utils/validation'; import { isEmpty, isValidWeight } from 'shared/utils'; -import { formatAddressForPrimeAPI, formatSwaggerDate, fromPrimeAPIAddressFormat } from 'utils/formatters'; +import { + formatAddressForPrimeAPI, + formatExtraAddressForPrimeAPI, + formatSwaggerDate, + fromPrimeAPIAddressFormat, +} from 'utils/formatters'; import PrimeUIShipmentUpdateForm from 'pages/PrimeUI/Shipment/PrimeUIShipmentUpdateForm'; import PrimeUIShipmentUpdatePPMForm from 'pages/PrimeUI/Shipment/PrimeUIShipmentUpdatePPMForm'; import { setFlashMessage as setFlashMessageAction } from 'store/flash/actions'; @@ -128,7 +133,11 @@ const PrimeUIShipmentUpdate = ({ setFlashMessage }) => { const reformatPrimeApiSecondaryDeliveryAddress = fromPrimeAPIAddressFormat(shipment.secondaryDeliveryAddress); const reformatPrimeApiTertiaryDeliveryAddress = fromPrimeAPIAddressFormat(shipment.tertiaryDeliveryAddress); const editablePickupAddress = isEmpty(reformatPrimeApiPickupAddress); + const editableSecondaryPickupAddress = isEmpty(reformatPrimeApiSecondaryPickupAddress); + const editableTertiaryPickupAddress = isEmpty(reformatPrimeApiTertiaryPickupAddress); const editableDestinationAddress = isEmpty(reformatPrimeApiDestinationAddress); + const editableSecondaryDeliveryAddress = isEmpty(reformatPrimeApiSecondaryDeliveryAddress); + const editableTertiaryDeliveryAddress = isEmpty(reformatPrimeApiTertiaryDeliveryAddress); const onCancelShipmentClick = () => { mutateMTOShipmentStatus({ mtoShipmentID: shipmentId, ifMatchETag: shipment.eTag }).then(() => { @@ -144,8 +153,10 @@ const PrimeUIShipmentUpdate = ({ setFlashMessage }) => { expectedDepartureDate, pickupAddress, secondaryPickupAddress, + tertiaryPickupAddress, destinationAddress, secondaryDestinationAddress, + tertiaryDestinationAddress, sitExpected, sitLocation, sitEstimatedWeight, @@ -156,7 +167,9 @@ const PrimeUIShipmentUpdate = ({ setFlashMessage }) => { proGearWeight, spouseProGearWeight, hasSecondaryPickupAddress, + hasTertiaryPickupAddress, hasSecondaryDestinationAddress, + hasTertiaryDestinationAddress, }, counselorRemarks, } = values; @@ -167,10 +180,16 @@ const PrimeUIShipmentUpdate = ({ setFlashMessage }) => { secondaryPickupAddress: isEmpty(secondaryPickupAddress) ? emptyAddress : formatAddressForPrimeAPI(secondaryPickupAddress), + tertiaryPickupAddress: isEmpty(tertiaryPickupAddress) + ? emptyAddress + : formatAddressForPrimeAPI(tertiaryPickupAddress), destinationAddress: isEmpty(destinationAddress) ? null : formatAddressForPrimeAPI(destinationAddress), secondaryDestinationAddress: isEmpty(secondaryDestinationAddress) ? emptyAddress : formatAddressForPrimeAPI(secondaryDestinationAddress), + tertiaryDestinationAddress: isEmpty(tertiaryDestinationAddress) + ? emptyAddress + : formatAddressForPrimeAPI(tertiaryDestinationAddress), sitExpected, ...(sitExpected && { sitLocation: sitLocation || null, @@ -185,7 +204,9 @@ const PrimeUIShipmentUpdate = ({ setFlashMessage }) => { spouseProGearWeight: spouseProGearWeight ? parseInt(spouseProGearWeight, 10) : null, }), hasSecondaryPickupAddress: hasSecondaryPickupAddress === 'true', + hasTertiaryPickupAddress: hasTertiaryPickupAddress === 'true', hasSecondaryDestinationAddress: hasSecondaryDestinationAddress === 'true', + hasTertiaryDestinationAddress: hasTertiaryDestinationAddress === 'true', }, counselorRemarks: counselorRemarks || null, }; @@ -200,7 +221,11 @@ const PrimeUIShipmentUpdate = ({ setFlashMessage }) => { actualDeliveryDate, scheduledDeliveryDate, pickupAddress, + secondaryPickupAddress, + tertiaryPickupAddress, destinationAddress, + secondaryDeliveryAddress, + tertiaryDeliveryAddress, destinationType, diversion, } = values; @@ -215,7 +240,19 @@ const PrimeUIShipmentUpdate = ({ setFlashMessage }) => { scheduledDeliveryDate: scheduledDeliveryDate ? formatSwaggerDate(scheduledDeliveryDate) : null, actualDeliveryDate: actualDeliveryDate ? formatSwaggerDate(actualDeliveryDate) : null, pickupAddress: editablePickupAddress ? formatAddressForPrimeAPI(pickupAddress) : null, + secondaryPickupAddress: editableSecondaryPickupAddress + ? formatExtraAddressForPrimeAPI(secondaryPickupAddress) + : null, + tertiaryPickupAddress: editableTertiaryPickupAddress + ? formatExtraAddressForPrimeAPI(tertiaryPickupAddress) + : null, destinationAddress: editableDestinationAddress ? formatAddressForPrimeAPI(destinationAddress) : null, + secondaryDeliveryAddress: editableSecondaryDeliveryAddress + ? formatExtraAddressForPrimeAPI(secondaryDeliveryAddress) + : null, + tertiaryDeliveryAddress: editableTertiaryDeliveryAddress + ? formatExtraAddressForPrimeAPI(tertiaryDeliveryAddress) + : null, destinationType, diversion, }; @@ -237,12 +274,18 @@ const PrimeUIShipmentUpdate = ({ setFlashMessage }) => { secondaryPickupAddress: shipment.ppmShipment.secondaryPickupAddress ? formatAddressForPrimeAPI(shipment.ppmShipment.secondaryPickupAddress) : emptyAddress, + tertiaryPickupAddress: shipment.ppmShipment.tertiaryPickupAddress + ? formatAddressForPrimeAPI(shipment.ppmShipment.tertiaryPickupAddress) + : emptyAddress, destinationAddress: shipment.ppmShipment.destinationAddress ? formatAddressForPrimeAPI(shipment.ppmShipment.destinationAddress) : emptyAddress, secondaryDestinationAddress: shipment.ppmShipment.secondaryDestinationAddress ? formatAddressForPrimeAPI(shipment.ppmShipment.secondaryDestinationAddress) : emptyAddress, + tertiaryDestinationAddress: shipment.ppmShipment.tertiaryDestinationAddress + ? formatAddressForPrimeAPI(shipment.ppmShipment.tertiaryDestinationAddress) + : emptyAddress, sitExpected: shipment.ppmShipment.sitExpected, sitLocation: shipment.ppmShipment.sitLocation, sitEstimatedWeight: shipment.ppmShipment.sitEstimatedWeight?.toString(), @@ -254,7 +297,9 @@ const PrimeUIShipmentUpdate = ({ setFlashMessage }) => { proGearWeight: shipment.ppmShipment.proGearWeight?.toString(), spouseProGearWeight: shipment.ppmShipment.spouseProGearWeight?.toString(), hasSecondaryPickupAddress: shipment.ppmShipment.hasSecondaryPickupAddress ? 'true' : 'false', + hasTertiaryPickupAddress: shipment.ppmShipment.hasTertiaryPickupAddress ? 'true' : 'false', hasSecondaryDestinationAddress: shipment.ppmShipment.hasSecondaryDestinationAddress ? 'true' : 'false', + hasTertiaryDestinationAddress: shipment.ppmShipment.hasTertiaryDestinationAddress ? 'true' : 'false', }, counselorRemarks: shipment.counselorRemarks || '', }; @@ -265,8 +310,10 @@ const PrimeUIShipmentUpdate = ({ setFlashMessage }) => { .typeError('Invalid date. Must be in the format: DD MMM YYYY'), pickupAddress: requiredAddressSchema.required('Required'), secondaryPickupAddress: OptionalAddressSchema, + tertiaryPickupAddress: OptionalAddressSchema, destinationAddress: requiredAddressSchema.required('Required'), secondaryDestinationAddress: OptionalAddressSchema, + tertiaryDestinationAddress: OptionalAddressSchema, sitExpected: Yup.boolean().required('Required'), sitLocation: Yup.string().when('sitExpected', { is: true, @@ -313,11 +360,13 @@ const PrimeUIShipmentUpdate = ({ setFlashMessage }) => { scheduledDeliveryDate: shipment.scheduledDeliveryDate, actualDeliveryDate: shipment.actualDeliveryDate, pickupAddress: editablePickupAddress ? emptyAddress : reformatPrimeApiPickupAddress, - secondaryPickupAddress: reformatPrimeApiSecondaryPickupAddress, - tertiaryPickupAddress: reformatPrimeApiTertiaryPickupAddress, + secondaryPickupAddress: editableSecondaryPickupAddress ? emptyAddress : reformatPrimeApiSecondaryPickupAddress, + tertiaryPickupAddress: editableTertiaryPickupAddress ? emptyAddress : reformatPrimeApiTertiaryPickupAddress, destinationAddress: editableDestinationAddress ? emptyAddress : reformatPrimeApiDestinationAddress, - secondaryDeliveryAddress: reformatPrimeApiSecondaryDeliveryAddress, - tertiaryDeliveryAddress: reformatPrimeApiTertiaryDeliveryAddress, + secondaryDeliveryAddress: editableSecondaryDeliveryAddress + ? emptyAddress + : reformatPrimeApiSecondaryDeliveryAddress, + tertiaryDeliveryAddress: editableTertiaryDeliveryAddress ? emptyAddress : reformatPrimeApiTertiaryDeliveryAddress, destinationType: shipment.destinationType, diversion: shipment.diversion, }; @@ -365,7 +414,11 @@ const PrimeUIShipmentUpdate = ({ setFlashMessage }) => { editableProGearWeightActualField={editableProGearWeightActualField} editableSpouseProGearWeightActualField={editableSpouseProGearWeightActualField} editablePickupAddress={editablePickupAddress} + editableSecondaryPickupAddress={editableSecondaryPickupAddress} + editableTertiaryPickupAddress={editableTertiaryPickupAddress} editableDestinationAddress={editableDestinationAddress} + editableSecondaryDeliveryAddress={editableSecondaryDeliveryAddress} + editableTertiaryDeliveryAddress={editableTertiaryDeliveryAddress} estimatedWeight={initialValues.estimatedWeight} actualWeight={initialValues.actualWeight} actualProGearWeight={initialValues.actualProGearWeight} diff --git a/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateForm.jsx b/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateForm.jsx index 27159eab32a..bffe0afda70 100644 --- a/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateForm.jsx +++ b/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateForm.jsx @@ -28,7 +28,11 @@ const PrimeUIShipmentUpdateForm = ({ editableProGearWeightActualField, editableSpouseProGearWeightActualField, editablePickupAddress, + editableSecondaryPickupAddress, + editableTertiaryPickupAddress, editableDestinationAddress, + editableSecondaryDeliveryAddress, + editableTertiaryDeliveryAddress, requestedPickupDate, estimatedWeight, actualWeight, @@ -147,16 +151,20 @@ const PrimeUIShipmentUpdateForm = ({ {editablePickupAddress && } {!editablePickupAddress && formatAddress(pickupAddress)}
Second Pickup Address
- {formatAddress(secondaryPickupAddress)} + {editableSecondaryPickupAddress && } + {!editableSecondaryPickupAddress && formatAddress(secondaryPickupAddress)}
Third Pickup Address
- {formatAddress(tertiaryPickupAddress)} + {editableTertiaryPickupAddress && } + {!editableTertiaryPickupAddress && formatAddress(tertiaryPickupAddress)}
Destination Address
{editableDestinationAddress && } {!editableDestinationAddress && formatAddress(destinationAddress)}
Second Delivery Address
- {formatAddress(secondaryDeliveryAddress)} + {editableSecondaryDeliveryAddress && } + {!editableSecondaryDeliveryAddress && formatAddress(secondaryDeliveryAddress)}
Third Delivery Address
- {formatAddress(tertiaryDeliveryAddress)} + {editableTertiaryDeliveryAddress && } + {!editableTertiaryDeliveryAddress && formatAddress(tertiaryDeliveryAddress)} { const { values } = useFormikContext(); - const { sitExpected, hasProGear, hasSecondaryPickupAddress, hasSecondaryDestinationAddress } = values.ppmShipment; + const { + sitExpected, + hasProGear, + hasSecondaryPickupAddress, + hasTertiaryPickupAddress, + hasSecondaryDestinationAddress, + hasTertiaryDestinationAddress, + } = values.ppmShipment; return ( @@ -62,7 +69,41 @@ const PrimeUIShipmentUpdatePPMForm = () => { />
- {hasSecondaryPickupAddress === 'true' && } + {hasSecondaryPickupAddress === 'true' && ( + <> + +

Third pickup location

+ +

+ Will the movers pick up any belongings from a third address? (Must be near the pickup address. + Subject to approval.) +

+
+ + +
+
+ {hasTertiaryPickupAddress === 'true' && } + + )} )} /> @@ -103,7 +144,41 @@ const PrimeUIShipmentUpdatePPMForm = () => {
{hasSecondaryDestinationAddress === 'true' && ( - + <> + +

Third destination location

+ +

+ Will the movers pick up any belongings from a third address? (Must be near the Destination address. + Subject to approval.) +

+
+ + +
+
+ {hasTertiaryDestinationAddress === 'true' && ( + + )} + )} )} diff --git a/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdatePPMForm.test.jsx b/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdatePPMForm.test.jsx index e6ca000f04a..be3ea189cd7 100644 --- a/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdatePPMForm.test.jsx +++ b/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdatePPMForm.test.jsx @@ -69,6 +69,14 @@ const shipment = { state: 'KY', postalCode: '42702', }, + tertiaryPickupAddress: { + streetAddress1: '123 Test Lane', + streetAddress2: '234 Test Lane', + streetAddress3: 'Test Woman', + city: 'Missoula', + state: 'MT', + postalCode: '59801', + }, destinationAddress: { streetAddress1: '222 Test Street', streetAddress2: '333 Test Street', @@ -85,8 +93,18 @@ const shipment = { state: 'KY', postalCode: '42701', }, + tertiaryDestinationAddress: { + streetAddress1: '321 Test Lane', + streetAddress2: '432 Test Lane', + streetAddress3: 'Test Woman', + city: 'Silver Spring', + state: 'MD', + postalCode: '20906', + }, hasSecondaryPickupAddress: 'true', hasSecondaryDestinationAddress: 'true', + hasTertiaryPickupAddress: 'true', + hasTertiaryDestinationAddress: 'true', submittedAt: '2022-07-01T13:41:33.252Z', updatedAt: '2022-07-01T14:23:19.780Z', }, @@ -106,6 +124,18 @@ const shipment = { state: null, streetAddress1: null, }, + tertiaryDeliveryAddress: { + city: null, + postalCode: null, + state: null, + streetAddress1: null, + }, + tertiaryPickupAddress: { + city: null, + postalCode: null, + state: null, + streetAddress1: null, + }, shipmentType: 'PPM', status: 'APPROVED', updatedAt: '2022-07-01T14:23:19.738Z', @@ -173,33 +203,63 @@ describe('PrimeUIShipmentUpdatePPMForm', () => { ); expect(await screen.getAllByLabelText('Address 1')[2]).toHaveValue( - initialValues.ppmShipment.destinationAddress.streetAddress1, + initialValues.ppmShipment.tertiaryPickupAddress.streetAddress1, ); expect(await screen.getAllByLabelText(/Address 2/)[2]).toHaveValue( - initialValues.ppmShipment.destinationAddress.streetAddress2, + initialValues.ppmShipment.tertiaryPickupAddress.streetAddress2, + ); + expect(await screen.getAllByLabelText('City')[2]).toHaveValue(initialValues.ppmShipment.tertiaryPickupAddress.city); + expect(await screen.getAllByLabelText('State')[2]).toHaveValue( + initialValues.ppmShipment.tertiaryPickupAddress.state, ); - expect(await screen.getAllByLabelText('City')[2]).toHaveValue(initialValues.ppmShipment.destinationAddress.city); - expect(await screen.getAllByLabelText('State')[2]).toHaveValue(initialValues.ppmShipment.destinationAddress.state); expect(await screen.getAllByLabelText('ZIP')[2]).toHaveValue( - initialValues.ppmShipment.destinationAddress.postalCode, + initialValues.ppmShipment.tertiaryPickupAddress.postalCode, ); expect(await screen.getAllByLabelText('Address 1')[3]).toHaveValue( - initialValues.ppmShipment.secondaryDestinationAddress.streetAddress1, + initialValues.ppmShipment.destinationAddress.streetAddress1, ); expect(await screen.getAllByLabelText(/Address 2/)[3]).toHaveValue( + initialValues.ppmShipment.destinationAddress.streetAddress2, + ); + expect(await screen.getAllByLabelText('City')[3]).toHaveValue(initialValues.ppmShipment.destinationAddress.city); + expect(await screen.getAllByLabelText('State')[3]).toHaveValue(initialValues.ppmShipment.destinationAddress.state); + expect(await screen.getAllByLabelText('ZIP')[3]).toHaveValue( + initialValues.ppmShipment.destinationAddress.postalCode, + ); + + expect(await screen.getAllByLabelText('Address 1')[4]).toHaveValue( + initialValues.ppmShipment.secondaryDestinationAddress.streetAddress1, + ); + expect(await screen.getAllByLabelText(/Address 2/)[4]).toHaveValue( initialValues.ppmShipment.secondaryDestinationAddress.streetAddress2, ); - expect(await screen.getAllByLabelText('City')[3]).toHaveValue( + expect(await screen.getAllByLabelText('City')[4]).toHaveValue( initialValues.ppmShipment.secondaryDestinationAddress.city, ); - expect(await screen.getAllByLabelText('State')[3]).toHaveValue( + expect(await screen.getAllByLabelText('State')[4]).toHaveValue( initialValues.ppmShipment.secondaryDestinationAddress.state, ); - expect(await screen.getAllByLabelText('ZIP')[3]).toHaveValue( + expect(await screen.getAllByLabelText('ZIP')[4]).toHaveValue( initialValues.ppmShipment.secondaryDestinationAddress.postalCode, ); + expect(await screen.getAllByLabelText('Address 1')[5]).toHaveValue( + initialValues.ppmShipment.tertiaryDestinationAddress.streetAddress1, + ); + expect(await screen.getAllByLabelText(/Address 2/)[5]).toHaveValue( + initialValues.ppmShipment.tertiaryDestinationAddress.streetAddress2, + ); + expect(await screen.getAllByLabelText('City')[5]).toHaveValue( + initialValues.ppmShipment.tertiaryDestinationAddress.city, + ); + expect(await screen.getAllByLabelText('State')[5]).toHaveValue( + initialValues.ppmShipment.tertiaryDestinationAddress.state, + ); + expect(await screen.getAllByLabelText('ZIP')[5]).toHaveValue( + initialValues.ppmShipment.tertiaryDestinationAddress.postalCode, + ); + expect(await screen.findByText('Storage In Transit (SIT)')).toBeInTheDocument(); expect(await screen.findByLabelText('SIT Expected')).toBeChecked(); expect(await screen.findByLabelText('SIT Location')).toHaveValue(initialValues.ppmShipment.sitLocation); diff --git a/src/utils/formatters.js b/src/utils/formatters.js index 139ef5b179f..50b54459236 100644 --- a/src/utils/formatters.js +++ b/src/utils/formatters.js @@ -406,6 +406,19 @@ export const formatAddressForPrimeAPI = (address) => { }; }; +export const formatExtraAddressForPrimeAPI = (address) => { + const { streetAddress1, city, state, postalCode } = address; + if (streetAddress1 === '' || city === '' || state === '' || postalCode === '') return null; + return { + streetAddress1: address.streetAddress1, + streetAddress2: address.streetAddress2, + streetAddress3: address.streetAddress3, + city: address.city, + state: address.state, + postalCode: address.postalCode, + }; +}; + const emptyAddress = { streetAddress1: '', streetAddress2: '', diff --git a/swagger-def/prime_v3.yaml b/swagger-def/prime_v3.yaml index ebefad72192..9b77551d857 100644 --- a/swagger-def/prime_v3.yaml +++ b/swagger-def/prime_v3.yaml @@ -561,6 +561,15 @@ definitions: An optional secondary pickup location near the origin where additional goods exist. allOf: - $ref: 'definitions/Address.yaml' + hasTertiaryPickupAddress: + type: boolean + x-omitempty: false + x-nullable: true + tertiaryPickupAddress: + description: > + An optional second pickup location near the origin where additional goods exist. + allOf: + - $ref: 'definitions/Address.yaml' destinationAddress: description: > The address of the destination location where goods are being delivered to. @@ -575,6 +584,15 @@ definitions: An optional secondary address near the destination where goods will be dropped off. allOf: - $ref: 'definitions/Address.yaml' + hasTertiaryDestinationAddress: + type: boolean + x-omitempty: false + x-nullable: true + tertiaryDestinationAddress: + description: > + An optional third address near the destination where goods will be dropped off. + allOf: + - $ref: 'definitions/Address.yaml' sitExpected: description: | Captures whether some or all of the PPM shipment will require temporary storage at the origin or destination. @@ -709,6 +727,14 @@ definitions: description: A second delivery address for this shipment, if the customer entered one. An optional field. allOf: - $ref: 'definitions/Address.yaml' + tertiaryPickupAddress: + description: A third pickup address for this shipment, if the customer entered one. An optional field. + allOf: + - $ref: 'definitions/Address.yaml' + tertiaryDeliveryAddress: + description: A third delivery address for this shipment, if the customer entered one. An optional field. + allOf: + - $ref: 'definitions/Address.yaml' storageFacility: allOf: - x-nullable: true diff --git a/swagger/prime_v3.yaml b/swagger/prime_v3.yaml index c7fd03f69c5..c96094663eb 100644 --- a/swagger/prime_v3.yaml +++ b/swagger/prime_v3.yaml @@ -864,6 +864,16 @@ definitions: goods exist. allOf: - $ref: '#/definitions/Address' + hasTertiaryPickupAddress: + type: boolean + x-omitempty: false + x-nullable: true + tertiaryPickupAddress: + description: > + An optional second pickup location near the origin where additional + goods exist. + allOf: + - $ref: '#/definitions/Address' destinationAddress: description: > The address of the destination location where goods are being @@ -880,6 +890,16 @@ definitions: dropped off. allOf: - $ref: '#/definitions/Address' + hasTertiaryDestinationAddress: + type: boolean + x-omitempty: false + x-nullable: true + tertiaryDestinationAddress: + description: > + An optional third address near the destination where goods will be + dropped off. + allOf: + - $ref: '#/definitions/Address' sitExpected: description: > Captures whether some or all of the PPM shipment will require @@ -1047,6 +1067,18 @@ definitions: one. An optional field. allOf: - $ref: '#/definitions/Address' + tertiaryPickupAddress: + description: >- + A third pickup address for this shipment, if the customer entered one. + An optional field. + allOf: + - $ref: '#/definitions/Address' + tertiaryDeliveryAddress: + description: >- + A third delivery address for this shipment, if the customer entered + one. An optional field. + allOf: + - $ref: '#/definitions/Address' storageFacility: allOf: - x-nullable: true From 4a8b9e1045336ff7ebc0eb2eaae4b33dde3915ec Mon Sep 17 00:00:00 2001 From: Paul Stonebraker Date: Wed, 11 Sep 2024 05:13:04 +0000 Subject: [PATCH 38/68] update for NTS and NTSR --- .../Shipment/PrimeUIShipmentUpdate.jsx | 1 + .../Shipment/PrimeUIShipmentUpdateForm.jsx | 36 ++++++++++++------- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdate.jsx b/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdate.jsx index d202e0a9e3d..bb408d21f55 100644 --- a/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdate.jsx +++ b/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdate.jsx @@ -431,6 +431,7 @@ const PrimeUIShipmentUpdate = ({ setFlashMessage }) => { secondaryDeliveryAddress={initialValues.secondaryDeliveryAddress} tertiaryDeliveryAddress={initialValues.tertiaryDeliveryAddress} diversion={initialValues.diversion} + shipmentType={shipment.shipmentType} /> )}
diff --git a/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateForm.jsx b/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateForm.jsx index bffe0afda70..795e6caf525 100644 --- a/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateForm.jsx +++ b/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateForm.jsx @@ -11,6 +11,7 @@ import { AddressFields } from 'components/form/AddressFields/AddressFields'; import SectionWrapper from 'components/Customer/SectionWrapper'; import formStyles from 'styles/form.module.scss'; import { shipmentDestinationTypes } from 'constants/shipments'; +import { SHIPMENT_OPTIONS } from 'shared/constants'; const emptyAddressShape = { streetAddress1: '', @@ -44,7 +45,10 @@ const PrimeUIShipmentUpdateForm = ({ destinationAddress, secondaryDeliveryAddress, tertiaryDeliveryAddress, + shipmentType, }) => { + const isNTS = shipmentType === SHIPMENT_OPTIONS.NTS; + const isNTSR = shipmentType === SHIPMENT_OPTIONS.NTSR; return (

Shipment Dates

@@ -150,21 +154,29 @@ const PrimeUIShipmentUpdateForm = ({
Pickup Address
{editablePickupAddress && } {!editablePickupAddress && formatAddress(pickupAddress)} -
Second Pickup Address
- {editableSecondaryPickupAddress && } - {!editableSecondaryPickupAddress && formatAddress(secondaryPickupAddress)} -
Third Pickup Address
- {editableTertiaryPickupAddress && } - {!editableTertiaryPickupAddress && formatAddress(tertiaryPickupAddress)} + {!isNTSR && ( + <> +
Second Pickup Address
+ {editableSecondaryPickupAddress && } + {!editableSecondaryPickupAddress && formatAddress(secondaryPickupAddress)} +
Third Pickup Address
+ {editableTertiaryPickupAddress && } + {!editableTertiaryPickupAddress && formatAddress(tertiaryPickupAddress)} + + )}
Destination Address
{editableDestinationAddress && } {!editableDestinationAddress && formatAddress(destinationAddress)} -
Second Delivery Address
- {editableSecondaryDeliveryAddress && } - {!editableSecondaryDeliveryAddress && formatAddress(secondaryDeliveryAddress)} -
Third Delivery Address
- {editableTertiaryDeliveryAddress && } - {!editableTertiaryDeliveryAddress && formatAddress(tertiaryDeliveryAddress)} + {!isNTS && ( + <> +
Second Delivery Address
+ {editableSecondaryDeliveryAddress && } + {!editableSecondaryDeliveryAddress && formatAddress(secondaryDeliveryAddress)} +
Third Delivery Address
+ {editableTertiaryDeliveryAddress && } + {!editableTertiaryDeliveryAddress && formatAddress(tertiaryDeliveryAddress)} + + )} Date: Tue, 10 Sep 2024 16:19:57 +0000 Subject: [PATCH 39/68] initial commit, should all be good, tests added --- .../ServicesCounselingTabNav.jsx | 12 +- .../ServicesCounselingTabNav.test.jsx | 6 +- src/components/Office/TXOTabNav/TXOTabNav.jsx | 4 + .../Office/TXOTabNav/TXOTabNav.test.jsx | 3 +- src/pages/Office/MoveDetails/MoveDetails.jsx | 37 +++++-- .../Office/MoveDetails/MoveDetails.test.jsx | 26 +++++ .../ServicesCounselingMoveDetails.jsx | 44 ++++++-- .../ServicesCounselingMoveDetails.test.jsx | 104 +++++++++++++++++- .../ServicesCounselingMoveInfo.jsx | 4 + src/pages/Office/TXOMoveInfo/TXOMoveInfo.jsx | 4 + 10 files changed, 213 insertions(+), 31 deletions(-) diff --git a/src/components/Office/ServicesCounselingTabNav/ServicesCounselingTabNav.jsx b/src/components/Office/ServicesCounselingTabNav/ServicesCounselingTabNav.jsx index 3dc4c9dadab..2d557e2c874 100644 --- a/src/components/Office/ServicesCounselingTabNav/ServicesCounselingTabNav.jsx +++ b/src/components/Office/ServicesCounselingTabNav/ServicesCounselingTabNav.jsx @@ -10,7 +10,7 @@ import 'styles/office.scss'; import TabNav from 'components/TabNav'; import { isBooleanFlagEnabled } from 'utils/featureFlags'; -const ServicesCounselingTabNav = ({ unapprovedShipmentCount = 0, moveCode }) => { +const ServicesCounselingTabNav = ({ unapprovedShipmentCount = 0, missingOrdersInfoCount, moveCode }) => { const [supportingDocsFF, setSupportingDocsFF] = React.useState(false); React.useEffect(() => { const fetchData = async () => { @@ -19,6 +19,14 @@ const ServicesCounselingTabNav = ({ unapprovedShipmentCount = 0, moveCode }) => fetchData(); }, []); + let moveDetailsTagCount = 0; + if (unapprovedShipmentCount > 0) { + moveDetailsTagCount += unapprovedShipmentCount; + } + if (missingOrdersInfoCount > 0) { + moveDetailsTagCount += missingOrdersInfoCount; + } + const items = [ data-testid="MoveDetails-Tab" > Move details - {unapprovedShipmentCount > 0 && {unapprovedShipmentCount}} + {moveDetailsTagCount > 0 && {moveDetailsTagCount}} , { expect(within(moveDetailsTab).queryByTestId('tag')).not.toBeInTheDocument(); }); - it('should render the move details tab container with a tag that shows the count of unapproved shipments', () => { + it('should render the move details tab container with a tag that shows the count of action items', () => { const moveDetailsShipmentAndAmendedOrders = { ...basicNavProps, unapprovedShipmentCount: 6, + missingOrdersInfoCount: 4, }; render(, { wrapper: MemoryRouter }); const moveDetailsTab = screen.getByTestId('MoveDetails-Tab'); - expect(within(moveDetailsTab).getByTestId('tag')).toHaveTextContent('6'); + expect(within(moveDetailsTab).getByTestId('tag')).toHaveTextContent('10'); }); }); diff --git a/src/components/Office/TXOTabNav/TXOTabNav.jsx b/src/components/Office/TXOTabNav/TXOTabNav.jsx index 9732c46407e..d3db9fbd1b9 100644 --- a/src/components/Office/TXOTabNav/TXOTabNav.jsx +++ b/src/components/Office/TXOTabNav/TXOTabNav.jsx @@ -17,6 +17,7 @@ const TXOTabNav = ({ excessWeightRiskCount, pendingPaymentRequestCount, unapprovedSITExtensionCount, + missingOrdersInfoCount, shipmentsWithDeliveryAddressUpdateRequestedCount, order, moveCode, @@ -39,6 +40,9 @@ const TXOTabNav = ({ if (shipmentsWithDeliveryAddressUpdateRequestedCount) { moveDetailsTagCount += shipmentsWithDeliveryAddressUpdateRequestedCount; } + if (missingOrdersInfoCount > 0) { + moveDetailsTagCount += missingOrdersInfoCount; + } let moveTaskOrderTagCount = 0; if (unapprovedServiceItemCount > 0) { diff --git a/src/components/Office/TXOTabNav/TXOTabNav.test.jsx b/src/components/Office/TXOTabNav/TXOTabNav.test.jsx index 7298a9d4d08..2779c53b11e 100644 --- a/src/components/Office/TXOTabNav/TXOTabNav.test.jsx +++ b/src/components/Office/TXOTabNav/TXOTabNav.test.jsx @@ -41,11 +41,12 @@ describe('Move details tag rendering', () => { const moveDetailsOneShipment = { ...basicNavProps, unapprovedShipmentCount: 1, + missingOrdersInfoCount: 4, }; render(, { wrapper: MemoryRouter }); const moveDetailsTab = screen.getByTestId('MoveDetails-Tab'); - expect(within(moveDetailsTab).getByTestId('tag')).toHaveTextContent('1'); + expect(within(moveDetailsTab).getByTestId('tag')).toHaveTextContent('5'); }); it('should render the move details tab container with a tag that shows the count of items that need attention when there are approved shipments with a destination address update requiring TXO review', () => { diff --git a/src/pages/Office/MoveDetails/MoveDetails.jsx b/src/pages/Office/MoveDetails/MoveDetails.jsx index 8d6a4c359ac..f6397c94061 100644 --- a/src/pages/Office/MoveDetails/MoveDetails.jsx +++ b/src/pages/Office/MoveDetails/MoveDetails.jsx @@ -57,6 +57,8 @@ const MoveDetails = ({ setExcessWeightRiskCount, setUnapprovedSITExtensionCount, setShipmentsWithDeliveryAddressUpdateRequestedCount, + missingOrdersInfoCount, + setMissingOrdersInfoCount, isMoveLocked, }) => { const { moveCode } = useParams(); @@ -238,6 +240,28 @@ const MoveDetails = ({ setShipmentMissingRequiredInformation(shipmentIsMissingInformation); }, [mtoShipments]); + // using useMemo here due to this being used in a useEffect + // using useMemo prevents the useEffect from being rendered on ever render by memoizing the object + // so that it only recognizes the change when the orders object changes + const requiredOrdersInfo = useMemo( + () => ({ + ordersNumber: order?.order_number || '', + ordersType: order?.order_type || '', + ordersTypeDetail: order?.order_type_detail || '', + tacMDC: order?.tac || '', + departmentIndicator: order?.department_indicator || '', + }), + [order], + ); + + // Keep num of missing orders info synced up + useEffect(() => { + const ordersInfoCount = Object.values(requiredOrdersInfo).reduce((count, value) => { + return !value ? count + 1 : count; + }, 0); + setMissingOrdersInfoCount(ordersInfoCount); + }, [order, requiredOrdersInfo, setMissingOrdersInfoCount]); + if (isLoading) return ; if (isError) return ; @@ -294,13 +318,6 @@ const MoveDetails = ({ backupContact: customer.backup_contact, }; - const requiredOrdersInfo = { - ordersNumber: order.order_number, - ordersType: order.order_type, - ordersTypeDetail: order.order_type_detail, - tacMDC: order.tac, - }; - const hasMissingOrdersRequiredInfo = Object.values(requiredOrdersInfo).some((value) => !value || value === ''); const hasAmendedOrders = ordersInfo.uploadedAmendedOrderID && !ordersInfo.amendedOrdersAcknowledgedAt; const hasDestinationAddressUpdate = @@ -311,12 +328,12 @@ const MoveDetails = ({
- + {missingOrdersInfoCount} ({ ...jest.requireActual('react-router-dom'), @@ -360,6 +361,7 @@ const requestedMoveDetailsAmendedOrdersQuery = { }, order: { id: '1', + department_indicator: 'ARMY', originDutyLocation: { address: { streetAddress1: '', @@ -783,6 +785,8 @@ describe('MoveDetails page', () => { setUnapprovedServiceItemCount={setUnapprovedServiceItemCount} setExcessWeightRiskCount={setExcessWeightRiskCount} setUnapprovedSITExtensionCount={setUnapprovedSITExtensionCount} + missingOrdersInfoCount={0} + setMissingOrdersInfoCount={setMissingOrdersInfoCount} /> , ); @@ -801,6 +805,8 @@ describe('MoveDetails page', () => { setUnapprovedServiceItemCount={setUnapprovedServiceItemCount} setExcessWeightRiskCount={setExcessWeightRiskCount} setUnapprovedSITExtensionCount={setUnapprovedSITExtensionCount} + missingOrdersInfoCount={0} + setMissingOrdersInfoCount={setMissingOrdersInfoCount} /> , ); @@ -819,6 +825,8 @@ describe('MoveDetails page', () => { setUnapprovedServiceItemCount={setUnapprovedServiceItemCount} setExcessWeightRiskCount={setExcessWeightRiskCount} setUnapprovedSITExtensionCount={setUnapprovedSITExtensionCount} + missingOrdersInfoCount={0} + setMissingOrdersInfoCount={setMissingOrdersInfoCount} /> , ); @@ -885,6 +893,8 @@ describe('MoveDetails page', () => { setUnapprovedServiceItemCount={setUnapprovedServiceItemCount} setExcessWeightRiskCount={setExcessWeightRiskCount} setUnapprovedSITExtensionCount={setUnapprovedSITExtensionCount} + missingOrdersInfoCount={0} + setMissingOrdersInfoCount={setMissingOrdersInfoCount} /> , ); @@ -905,6 +915,8 @@ describe('MoveDetails page', () => { setUnapprovedServiceItemCount={setUnapprovedServiceItemCount} setExcessWeightRiskCount={setExcessWeightRiskCount} setUnapprovedSITExtensionCount={setUnapprovedSITExtensionCount} + missingOrdersInfoCount={0} + setMissingOrdersInfoCount={setMissingOrdersInfoCount} /> , ); @@ -928,6 +940,8 @@ describe('MoveDetails page', () => { setUnapprovedServiceItemCount={setUnapprovedServiceItemCount} setExcessWeightRiskCount={setExcessWeightRiskCount} setUnapprovedSITExtensionCount={setUnapprovedSITExtensionCount} + missingOrdersInfoCount={0} + setMissingOrdersInfoCount={setMissingOrdersInfoCount} /> , ); @@ -967,6 +981,8 @@ describe('MoveDetails page', () => { setUnapprovedServiceItemCount={setUnapprovedServiceItemCount} setExcessWeightRiskCount={setExcessWeightRiskCount} setUnapprovedSITExtensionCount={setUnapprovedServiceItemCount} + missingOrdersInfoCount={0} + setMissingOrdersInfoCount={setMissingOrdersInfoCount} /> , ); @@ -986,12 +1002,15 @@ describe('MoveDetails page', () => { setUnapprovedServiceItemCount={setUnapprovedServiceItemCount} setExcessWeightRiskCount={setExcessWeightRiskCount} setUnapprovedSITExtensionCount={setUnapprovedSITExtensionCount} + missingOrdersInfoCount={2} + setMissingOrdersInfoCount={setMissingOrdersInfoCount} /> , ); it('renders an error indicator in the sidebar', () => { expect(wrapper.find('a[href="#orders"] span[data-testid="tag"]').exists()).toBe(true); + expect(wrapper.find('a[href="#orders"] span[data-testid="tag"]').text()).toBe('2'); }); }); @@ -1006,6 +1025,8 @@ describe('MoveDetails page', () => { setUnapprovedServiceItemCount={setUnapprovedServiceItemCount} setExcessWeightRiskCount={setExcessWeightRiskCount} setUnapprovedSITExtensionCount={setUnapprovedSITExtensionCount} + missingOrdersInfoCount={0} + setMissingOrdersInfoCount={setMissingOrdersInfoCount} /> , ); @@ -1025,6 +1046,8 @@ describe('MoveDetails page', () => { setUnapprovedServiceItemCount={setUnapprovedServiceItemCount} setExcessWeightRiskCount={setExcessWeightRiskCount} setUnapprovedSITExtensionCount={setUnapprovedSITExtensionCount} + missingOrdersInfoCount={0} + setMissingOrdersInfoCount={setMissingOrdersInfoCount} /> , ); @@ -1039,6 +1062,7 @@ describe('MoveDetails page', () => { setUnapprovedServiceItemCount, setExcessWeightRiskCount, setUnapprovedSITExtensionCount, + setMissingOrdersInfoCount, }; it('renders the financial review flag button when user has permission', async () => { @@ -1150,6 +1174,8 @@ describe('MoveDetails page', () => { setUnapprovedServiceItemCount={setUnapprovedServiceItemCount} setExcessWeightRiskCount={setExcessWeightRiskCount} setUnapprovedSITExtensionCount={setUnapprovedSITExtensionCount} + missingOrdersInfoCount={0} + setMissingOrdersInfoCount={setMissingOrdersInfoCount} /> , ); diff --git a/src/pages/Office/ServicesCounselingMoveDetails/ServicesCounselingMoveDetails.jsx b/src/pages/Office/ServicesCounselingMoveDetails/ServicesCounselingMoveDetails.jsx index 6c065a37083..dd5e25bab4c 100644 --- a/src/pages/Office/ServicesCounselingMoveDetails/ServicesCounselingMoveDetails.jsx +++ b/src/pages/Office/ServicesCounselingMoveDetails/ServicesCounselingMoveDetails.jsx @@ -1,7 +1,6 @@ import React, { useState, useEffect, useMemo } from 'react'; import { Link, useParams, useNavigate, generatePath } from 'react-router-dom'; import { useQueryClient, useMutation } from '@tanstack/react-query'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { func } from 'prop-types'; import classnames from 'classnames'; import 'styles/office.scss'; @@ -42,7 +41,13 @@ import { objectIsMissingFieldWithCondition } from 'utils/displayFlags'; import { ReviewButton } from 'components/form/IconButtons'; import { calculateWeightRequested } from 'hooks/custom'; -const ServicesCounselingMoveDetails = ({ infoSavedAlert, setUnapprovedShipmentCount, isMoveLocked }) => { +const ServicesCounselingMoveDetails = ({ + infoSavedAlert, + setUnapprovedShipmentCount, + isMoveLocked, + missingOrdersInfoCount, + setMissingOrdersInfoCount, +}) => { const { moveCode } = useParams(); const navigate = useNavigate(); const [alertMessage, setAlertMessage] = useState(null); @@ -345,6 +350,20 @@ const ServicesCounselingMoveDetails = ({ infoSavedAlert, setUnapprovedShipmentCo ntsSac: order.ntsSac, }; + // using useMemo here due to this being used in a useEffect + // using useMemo prevents the useEffect from being rendered on ever render by memoizing the object + // so that it only recognizes the change when the orders object changes + const requiredOrdersInfo = useMemo( + () => ({ + ordersNumber: order?.order_number || '', + ordersType: order?.order_type || '', + ordersTypeDetail: order?.order_type_detail || '', + tacMDC: order?.tac || '', + departmentIndicator: order?.department_indicator || '', + }), + [order], + ); + const handleButtonDropdownChange = (e) => { const selectedOption = e.target.value; @@ -412,6 +431,14 @@ const ServicesCounselingMoveDetails = ({ infoSavedAlert, setUnapprovedShipmentCo setUnapprovedShipmentCount, ]); + // Keep num of missing orders info synced up + useEffect(() => { + const ordersInfoCount = Object.values(requiredOrdersInfo).reduce((count, value) => { + return !value ? count + 1 : count; + }, 0); + setMissingOrdersInfoCount(ordersInfoCount); + }, [order, requiredOrdersInfo, setMissingOrdersInfoCount]); + if (isLoading) return ; if (isError) return ; @@ -454,13 +481,6 @@ const ServicesCounselingMoveDetails = ({ infoSavedAlert, setUnapprovedShipmentCo return false; }; - const requiredOrdersInfo = { - ordersNumber: order.order_number, - ordersType: order.order_type, - ordersTypeDetail: order.order_type_detail, - tacMDC: order.tac, - }; - const allShipmentsDeleted = mtoShipments.every((shipment) => !!shipment.deletedAt); const hasMissingOrdersRequiredInfo = Object.values(requiredOrdersInfo).some((value) => !value || value === ''); const hasAmendedOrders = ordersInfo.uploadedAmendedOrderID && !ordersInfo.amendedOrdersAcknowledgedAt; @@ -477,12 +497,12 @@ const ServicesCounselingMoveDetails = ({ infoSavedAlert, setUnapprovedShipmentCo {shipmentConcernCount} - + {missingOrdersInfoCount} { return render( - + , ); }; @@ -570,6 +638,23 @@ describe('MoveDetails page', () => { expect(mockSetUpapprovedShipmentCount).toHaveBeenCalledWith(3); }); + it('shares the number of missing orders information', () => { + const moveDetailsQuery = { + ...newMoveDetailsQuery, + order: orderMissingRequiredInfo, + }; + + useMoveDetailsQueries.mockReturnValue(moveDetailsQuery); + useOrdersDocumentQueries.mockReturnValue(moveDetailsQuery); + + const mockSetMissingOrdersInfoCount = jest.fn(); + renderComponent({ setMissingOrdersInfoCount: mockSetMissingOrdersInfoCount }); + + // Should have called `setMissingOrdersInfoCount` with 4 missing fields + expect(mockSetMissingOrdersInfoCount).toHaveBeenCalledTimes(1); + expect(mockSetMissingOrdersInfoCount).toHaveBeenCalledWith(4); + }); + /* eslint-disable camelcase */ it('renders shipments info', async () => { useMoveDetailsQueries.mockReturnValue(newMoveDetailsQuery); @@ -836,7 +921,11 @@ describe('MoveDetails page', () => { permissions={[permissionTypes.updateShipment, permissionTypes.updateCustomer]} {...mockRoutingOptions} > - + , ); @@ -933,7 +1022,11 @@ describe('MoveDetails page', () => { path={servicesCounselingRoutes.BASE_SHIPMENT_ADD_PATH} params={{ moveCode: mockRequestedMoveCode, shipmentType }} > - , + + , , ); @@ -1031,7 +1124,10 @@ describe('MoveDetails page', () => { it('renders the financial review flag button when user has permission', async () => { render( - + , ); diff --git a/src/pages/Office/ServicesCounselingMoveInfo/ServicesCounselingMoveInfo.jsx b/src/pages/Office/ServicesCounselingMoveInfo/ServicesCounselingMoveInfo.jsx index 8646b252640..eaedf6eca8a 100644 --- a/src/pages/Office/ServicesCounselingMoveInfo/ServicesCounselingMoveInfo.jsx +++ b/src/pages/Office/ServicesCounselingMoveInfo/ServicesCounselingMoveInfo.jsx @@ -42,6 +42,7 @@ const ServicesCounselingMoveInfo = () => { const [unapprovedServiceItemCount, setUnapprovedServiceItemCount] = React.useState(0); const [excessWeightRiskCount, setExcessWeightRiskCount] = React.useState(0); const [unapprovedSITExtensionCount, setUnApprovedSITExtensionCount] = React.useState(0); + const [missingOrdersInfoCount, setMissingOrdersInfoCount] = useState(0); const [infoSavedAlert, setInfoSavedAlert] = useState(null); const { hasRecentError, traceId } = useSelector((state) => state.interceptor); const [moveLockFlag, setMoveLockFlag] = useState(false); @@ -195,6 +196,7 @@ const ServicesCounselingMoveInfo = () => { unapprovedServiceItemCount={unapprovedServiceItemCount} excessWeightRiskCount={excessWeightRiskCount} unapprovedSITExtensionCount={unapprovedSITExtensionCount} + missingOrdersInfoCount={missingOrdersInfoCount} /> )} @@ -214,6 +216,8 @@ const ServicesCounselingMoveInfo = () => { } diff --git a/src/pages/Office/TXOMoveInfo/TXOMoveInfo.jsx b/src/pages/Office/TXOMoveInfo/TXOMoveInfo.jsx index a931a110286..bbe6c9c4f09 100644 --- a/src/pages/Office/TXOMoveInfo/TXOMoveInfo.jsx +++ b/src/pages/Office/TXOMoveInfo/TXOMoveInfo.jsx @@ -39,6 +39,7 @@ const TXOMoveInfo = () => { const [excessWeightRiskCount, setExcessWeightRiskCount] = React.useState(0); const [pendingPaymentRequestCount, setPendingPaymentRequestCount] = React.useState(0); const [unapprovedSITExtensionCount, setUnApprovedSITExtensionCount] = React.useState(0); + const [missingOrdersInfoCount, setMissingOrdersInfoCount] = useState(0); const [moveLockFlag, setMoveLockFlag] = useState(false); const [isMoveLocked, setIsMoveLocked] = useState(false); @@ -150,6 +151,7 @@ const TXOMoveInfo = () => { excessWeightRiskCount={excessWeightRiskCount} pendingPaymentRequestCount={pendingPaymentRequestCount} unapprovedSITExtensionCount={unapprovedSITExtensionCount} + missingOrdersInfoCount={missingOrdersInfoCount} moveCode={moveCode} reportId={reportId} order={order} @@ -177,6 +179,8 @@ const TXOMoveInfo = () => { } setExcessWeightRiskCount={setExcessWeightRiskCount} setUnapprovedSITExtensionCount={setUnApprovedSITExtensionCount} + missingOrdersInfoCount={missingOrdersInfoCount} + setMissingOrdersInfoCount={setMissingOrdersInfoCount} isMoveLocked={isMoveLocked} /> } From 1d9d1de6225d2bddeab5c784f52902d0511830a6 Mon Sep 17 00:00:00 2001 From: Daniel Jordan Date: Wed, 4 Sep 2024 16:23:58 +0000 Subject: [PATCH 40/68] initial changes --- .../CustomerOnboarding/CreateCustomerForm.jsx | 26 +++++++++++++++++-- src/utils/customer.js | 25 ++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx index b8a6ed6a6fa..94266ee376a 100644 --- a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx +++ b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx @@ -27,11 +27,14 @@ import { setFlashMessage as setFlashMessageAction } from 'store/flash/actions'; import { elevatedPrivilegeTypes } from 'constants/userPrivileges'; import { isBooleanFlagEnabled } from 'utils/featureFlags'; import departmentIndicators from 'constants/departmentIndicators'; +import { generateUniqueDodid, generateUniqueEmplid } from 'utils/customer'; +import Hint from 'components/Hint'; export const CreateCustomerForm = ({ userPrivileges, setFlashMessage }) => { const [serverError, setServerError] = useState(null); const [showEmplid, setShowEmplid] = useState(false); const [isSafetyMove, setIsSafetyMove] = useState(false); + const [showSafetyMoveHint, setShowSafetyMoveHint] = useState(false); const navigate = useNavigate(); const branchOptions = dropdownInputOptions(SERVICE_MEMBER_AGENCY_LABELS); @@ -42,6 +45,9 @@ export const CreateCustomerForm = ({ userPrivileges, setFlashMessage }) => { const [isSafetyMoveFF, setSafetyMoveFF] = useState(false); + const uniqueDodid = generateUniqueDodid(); + const uniqueEmplid = generateUniqueEmplid(); + useEffect(() => { isBooleanFlagEnabled('safety_move')?.then((enabled) => { setSafetyMoveFF(enabled); @@ -55,6 +61,7 @@ export const CreateCustomerForm = ({ userPrivileges, setFlashMessage }) => { const initialValues = { affiliation: '', edipi: '', + emplid: '', first_name: '', middle_name: '', last_name: '', @@ -193,11 +200,10 @@ export const CreateCustomerForm = ({ userPrivileges, setFlashMessage }) => { const { value } = e.target; if (value === 'true') { setIsSafetyMove(true); + setShowSafetyMoveHint(true); // clear out DoDID, emplid, and OKTA fields setValues({ ...values, - edipi: '', - emplid: '', create_okta_account: '', cac_user: 'true', is_safety_move: 'true', @@ -211,10 +217,23 @@ export const CreateCustomerForm = ({ userPrivileges, setFlashMessage }) => { } }; const handleBranchChange = (e) => { + setShowSafetyMoveHint(false); if (e.target.value === departmentIndicators.COAST_GUARD) { setShowEmplid(true); + setValues({ + ...values, + affiliation: e.target.value, + edipi: '', + emplid: uniqueEmplid, + }); } else { setShowEmplid(false); + setValues({ + ...values, + affiliation: e.target.value, + edipi: uniqueDodid, + emplid: '', + }); } }; return ( @@ -266,6 +285,9 @@ export const CreateCustomerForm = ({ userPrivileges, setFlashMessage }) => { maxLength="10" isDisabled={isSafetyMove} /> + {isSafetyMove && showSafetyMoveHint && ( + Once a branch is selected, this will generate a random safety move identifier + )} {showEmplid && ( { return generalRoutes.HOME_PATH; } }; + +export const generateUniqueDodid = () => { + const prefix = 'SM'; + + // Custom epoch start date (e.g., 2024-01-01), generates something like 1704067200000 + const customEpoch = new Date('2024-01-01').getTime(); + const now = Date.now(); + + // Calculate milliseconds since custom epoch, then convert to an 8-digit integer + const uniqueNumber = Math.floor((now - customEpoch) / 1000); // Dividing by 1000 to reduce to seconds + + // Convert the unique number to a string, ensuring it has 8 digits + const uniqueStr = uniqueNumber.toString().slice(0, 8).padStart(8, '0'); + + return prefix + uniqueStr; +}; + +export const generateUniqueEmplid = () => { + const prefix = 'SM'; + const customEpoch = new Date('2024-01-01').getTime(); + const now = Date.now(); + const uniqueNumber = Math.floor((now - customEpoch) / 1000) % 100000; // Modulo 100000 ensures it's 5 digits + const uniqueStr = uniqueNumber.toString().padStart(5, '0'); + return prefix + uniqueStr; +}; From df6d5e4ace4f37557a31952008d6192cbb9c1ab4 Mon Sep 17 00:00:00 2001 From: Daniel Jordan Date: Thu, 5 Sep 2024 19:26:16 +0000 Subject: [PATCH 41/68] initial commit, tests added, working as intended --- pkg/gen/ghcapi/embedded_spec.go | 8 +-- pkg/gen/ghcmessages/customer.go | 6 +- pkg/handlers/ghcapi/customer_test.go | 2 +- .../internal/payloads/model_to_payload.go | 2 +- pkg/handlers/ghcapi/orders.go | 14 +--- pkg/handlers/ghcapi/queues_test.go | 4 +- .../CustomerHeader/CustomerHeader.test.jsx | 8 +-- src/components/CustomerHeader/index.jsx | 4 +- .../DefinitionLists/CustomerInfoList.jsx | 4 +- .../DefinitionLists/CustomerInfoList.test.jsx | 2 +- .../CustomerOnboarding/CreateCustomerForm.jsx | 49 ++++++++++---- .../CreateCustomerForm.test.jsx | 67 ++++++++++++++++++- src/pages/Office/MoveDetails/MoveDetails.jsx | 2 +- swagger-def/ghc.yaml | 2 +- swagger/ghc.yaml | 2 +- 15 files changed, 125 insertions(+), 51 deletions(-) diff --git a/pkg/gen/ghcapi/embedded_spec.go b/pkg/gen/ghcapi/embedded_spec.go index 7388e1b61d5..03e37ade392 100644 --- a/pkg/gen/ghcapi/embedded_spec.go +++ b/pkg/gen/ghcapi/embedded_spec.go @@ -7174,10 +7174,10 @@ func init() { "current_address": { "$ref": "#/definitions/Address" }, - "dodID": { + "eTag": { "type": "string" }, - "eTag": { + "edipi": { "type": "string" }, "email": { @@ -22542,10 +22542,10 @@ func init() { "current_address": { "$ref": "#/definitions/Address" }, - "dodID": { + "eTag": { "type": "string" }, - "eTag": { + "edipi": { "type": "string" }, "email": { diff --git a/pkg/gen/ghcmessages/customer.go b/pkg/gen/ghcmessages/customer.go index 2bac3e7d0b3..c4034e3cfe2 100644 --- a/pkg/gen/ghcmessages/customer.go +++ b/pkg/gen/ghcmessages/customer.go @@ -34,12 +34,12 @@ type Customer struct { // current address CurrentAddress *Address `json:"current_address,omitempty"` - // dod ID - DodID string `json:"dodID,omitempty"` - // e tag ETag string `json:"eTag,omitempty"` + // edipi + Edipi string `json:"edipi,omitempty"` + // email // Pattern: ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ Email *string `json:"email,omitempty"` diff --git a/pkg/handlers/ghcapi/customer_test.go b/pkg/handlers/ghcapi/customer_test.go index ca46f85b2a5..4e8564af27c 100644 --- a/pkg/handlers/ghcapi/customer_test.go +++ b/pkg/handlers/ghcapi/customer_test.go @@ -53,7 +53,7 @@ func (suite *HandlerSuite) TestGetCustomerHandlerIntegration() { suite.NoError(getCustomerPayload.Validate(strfmt.Default)) suite.Equal(strfmt.UUID(customer.ID.String()), getCustomerPayload.ID) - suite.Equal(*customer.Edipi, getCustomerPayload.DodID) + suite.Equal(*customer.Edipi, getCustomerPayload.Edipi) suite.Equal(strfmt.UUID(customer.UserID.String()), getCustomerPayload.UserID) suite.Equal(customer.Affiliation.String(), getCustomerPayload.Agency) suite.Equal(customer.PersonalEmail, getCustomerPayload.Email) diff --git a/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go b/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go index f024da83510..9d6fc33e81c 100644 --- a/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go +++ b/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go @@ -490,7 +490,7 @@ func Customer(customer *models.ServiceMember) *ghcmessages.Customer { payload := ghcmessages.Customer{ Agency: swag.StringValue((*string)(customer.Affiliation)), CurrentAddress: Address(customer.ResidentialAddress), - DodID: swag.StringValue(customer.Edipi), + Edipi: swag.StringValue(customer.Edipi), Email: customer.PersonalEmail, FirstName: swag.StringValue(customer.FirstName), ID: strfmt.UUID(customer.ID.String()), diff --git a/pkg/handlers/ghcapi/orders.go b/pkg/handlers/ghcapi/orders.go index fb6d627c2dd..c8ea763b501 100644 --- a/pkg/handlers/ghcapi/orders.go +++ b/pkg/handlers/ghcapi/orders.go @@ -315,24 +315,12 @@ func (h CreateOrderHandler) Handle(params orderop.CreateOrderParams) middleware. } if newOrder.OrdersType == "SAFETY" { - // if creating a Safety move, clear out the DoDID and OktaID for the customer + // if creating a Safety move, clear out the OktaID for the customer since they won't log into MilMove err = models.UpdateUserOktaID(appCtx.DB(), &newOrder.ServiceMember.User, "") if err != nil { appCtx.Logger().Error("Authorization error updating user", zap.Error(err)) return orderop.NewUpdateOrderInternalServerError(), err } - - err = models.UpdateServiceMemberDoDID(appCtx.DB(), &newOrder.ServiceMember, nil) - if err != nil { - appCtx.Logger().Error("Authorization error updating service member", zap.Error(err)) - return orderop.NewUpdateOrderInternalServerError(), err - } - - err = models.UpdateServiceMemberEMPLID(appCtx.DB(), &newOrder.ServiceMember, nil) - if err != nil { - appCtx.Logger().Error("Authorization error updating service member", zap.Error(err)) - return orderop.NewUpdateOrderInternalServerError(), err - } } newMove, verrs, err := newOrder.CreateNewMove(appCtx.DB(), moveOptions) diff --git a/pkg/handlers/ghcapi/queues_test.go b/pkg/handlers/ghcapi/queues_test.go index f1efe5028fe..4cc6aaaa1da 100644 --- a/pkg/handlers/ghcapi/queues_test.go +++ b/pkg/handlers/ghcapi/queues_test.go @@ -833,7 +833,7 @@ func (suite *HandlerSuite) TestGetMoveQueuesHandlerCustomerInfoFilters() { result := payload.QueueMoves[0] suite.Len(payload.QueueMoves, 1) - suite.Equal("11111", result.Customer.DodID) + suite.Equal("11111", result.Customer.Edipi) }) suite.Run("returns results matching Move ID search term", func() { @@ -1525,7 +1525,7 @@ func (suite *HandlerSuite) TestGetServicesCounselingQueueHandler() { suite.Len(payload.QueueMoves, 2) suite.Equal(order.ServiceMember.ID.String(), result1.Customer.ID.String()) - suite.Equal(*order.ServiceMember.Edipi, result1.Customer.DodID) + suite.Equal(*order.ServiceMember.Edipi, result1.Customer.Edipi) suite.Equal(subtestData.needsCounselingMove.Locator, result1.Locator) suite.EqualValues(subtestData.needsCounselingMove.Status, result1.Status) suite.Equal(subtestData.needsCounselingEarliestShipment.RequestedPickupDate.Format(time.RFC3339Nano), (time.Time)(*result1.RequestedMoveDate).Format(time.RFC3339Nano)) diff --git a/src/components/CustomerHeader/CustomerHeader.test.jsx b/src/components/CustomerHeader/CustomerHeader.test.jsx index 1668afb9f29..fec1c7c7b73 100644 --- a/src/components/CustomerHeader/CustomerHeader.test.jsx +++ b/src/components/CustomerHeader/CustomerHeader.test.jsx @@ -5,7 +5,7 @@ import { mount } from 'enzyme'; import CustomerHeader from './index'; const props = { - customer: { last_name: 'Kerry', first_name: 'Smith', dodID: '999999999', emplid: '7777777', agency: 'COAST_GUARD' }, + customer: { last_name: 'Kerry', first_name: 'Smith', edipi: '999999999', emplid: '7777777', agency: 'COAST_GUARD' }, order: { agency: 'COAST_GUARD', grade: 'E_6', @@ -25,7 +25,7 @@ const props = { }; const propsRetiree = { - customer: { last_name: 'Kerry', first_name: 'Smith', dodID: '999999999' }, + customer: { last_name: 'Kerry', first_name: 'Smith', edipi: '999999999' }, order: { agency: 'NAVY', grade: 'E_6', @@ -45,7 +45,7 @@ const propsRetiree = { }; const propsUSMC = { - customer: { last_name: 'Kerry', first_name: 'Smith', dodID: '999999999' }, + customer: { last_name: 'Kerry', first_name: 'Smith', edipi: '999999999' }, order: { agency: 'MARINES', grade: 'E_6', @@ -77,7 +77,7 @@ describe('CustomerHeader component', () => { expect(wrapper.find('[data-testid="nameBlock"]').text()).toContain('Kerry, Smith'); expect(wrapper.find('[data-testid="nameBlock"]').text()).toContain('FKLCTR'); expect(wrapper.find('[data-testid="deptPayGrade"]').text()).toContain('Coast Guard E-6'); - expect(wrapper.find('[data-testid="dodId"]').text()).toContain('DoD ID 999999999'); + expect(wrapper.find('[data-testid="edipi"]').text()).toContain('DoD ID 999999999'); expect(wrapper.find('[data-testid="emplid"]').text()).toContain('EMPLID 7777777'); expect(wrapper.find('[data-testid="infoBlock"]').text()).toContain('JBSA Lackland'); expect(wrapper.find('[data-testid="infoBlock"]').text()).toContain('JB Lewis-McChord'); diff --git a/src/components/CustomerHeader/index.jsx b/src/components/CustomerHeader/index.jsx index 36d8ebc037a..857e2469997 100644 --- a/src/components/CustomerHeader/index.jsx +++ b/src/components/CustomerHeader/index.jsx @@ -55,8 +55,8 @@ const CustomerHeader = ({ customer, order, moveCode, move, userRole }) => { {ORDERS_BRANCH_OPTIONS[`${order.agency}`]} {ORDERS_PAY_GRADE_OPTIONS[`${order.grade}`]} | - - DoD ID {customer.dodID} + + DoD ID {customer.edipi} {isCoastGuard && ( <> diff --git a/src/components/Office/DefinitionLists/CustomerInfoList.jsx b/src/components/Office/DefinitionLists/CustomerInfoList.jsx index b1b79d9ac01..16a698529f4 100644 --- a/src/components/Office/DefinitionLists/CustomerInfoList.jsx +++ b/src/components/Office/DefinitionLists/CustomerInfoList.jsx @@ -19,7 +19,7 @@ const CustomerInfoList = ({ customerInfo }) => {
DoD ID
-
{customerInfo.dodId}
+
{customerInfo.edipi}
{customerInfo.agency === departmentIndicators.COAST_GUARD && (
@@ -81,7 +81,7 @@ const CustomerInfoList = ({ customerInfo }) => { CustomerInfoList.propTypes = { customerInfo: PropTypes.shape({ name: PropTypes.string, - dodId: PropTypes.string, + edipi: PropTypes.string, phone: PropTypes.string, email: PropTypes.string, currentAddress: AddressShape, diff --git a/src/components/Office/DefinitionLists/CustomerInfoList.test.jsx b/src/components/Office/DefinitionLists/CustomerInfoList.test.jsx index 18682850c78..d957be52b4f 100644 --- a/src/components/Office/DefinitionLists/CustomerInfoList.test.jsx +++ b/src/components/Office/DefinitionLists/CustomerInfoList.test.jsx @@ -6,7 +6,7 @@ import CustomerInfoList from './CustomerInfoList'; const info = { name: 'Smith, Kerry', agency: 'COAST_GUARD', - dodId: '9999999999', + edipi: '9999999999', emplid: '7777777', phone: '999-999-9999', altPhone: '888-888-8888', diff --git a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx index 94266ee376a..72f27a84ca5 100644 --- a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx +++ b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx @@ -94,7 +94,7 @@ export const CreateCustomerForm = ({ userPrivileges, setFlashMessage }) => { }, create_okta_account: '', cac_user: '', - is_safety_move: false, + is_safety_move: 'false', }; const handleBack = () => { @@ -151,10 +151,8 @@ export const CreateCustomerForm = ({ userPrivileges, setFlashMessage }) => { const validationSchema = Yup.object().shape({ affiliation: Yup.mixed().oneOf(Object.keys(SERVICE_MEMBER_AGENCY_LABELS)).required('Required'), - edipi: Yup.string().matches(/[0-9]{10}/, 'Enter a 10-digit DOD ID number'), - emplid: Yup.string() - .notRequired() - .matches(/[0-9]{7}/, 'Enter a 7-digit EMPLID number'), + edipi: Yup.string().matches(/^(SM[0-9]{8}|[0-9]{10})$/, 'Enter a 10-digit DOD ID number'), + emplid: Yup.string().matches(/^(SM[0-9]{5}|[0-9]{7})$/, 'Enter a 7-digit EMPLID number'), first_name: Yup.string().required('Required'), middle_name: Yup.string(), last_name: Yup.string().required('Required'), @@ -201,9 +199,9 @@ export const CreateCustomerForm = ({ userPrivileges, setFlashMessage }) => { if (value === 'true') { setIsSafetyMove(true); setShowSafetyMoveHint(true); - // clear out DoDID, emplid, and OKTA fields setValues({ ...values, + affiliation: '', create_okta_account: '', cac_user: 'true', is_safety_move: 'true', @@ -212,21 +210,32 @@ export const CreateCustomerForm = ({ userPrivileges, setFlashMessage }) => { setIsSafetyMove(false); setValues({ ...values, + affiliation: '', + edipi: '', + emplid: '', is_safety_move: 'false', }); } }; const handleBranchChange = (e) => { setShowSafetyMoveHint(false); - if (e.target.value === departmentIndicators.COAST_GUARD) { + if (e.target.value === departmentIndicators.COAST_GUARD && isSafetyMove) { setShowEmplid(true); setValues({ ...values, affiliation: e.target.value, - edipi: '', + edipi: uniqueDodid, emplid: uniqueEmplid, }); - } else { + } else if (e.target.value === departmentIndicators.COAST_GUARD && !isSafetyMove) { + setShowEmplid(true); + setValues({ + ...values, + affiliation: e.target.value, + edipi: '', + emplid: '', + }); + } else if (e.target.value !== departmentIndicators.COAST_GUARD && isSafetyMove) { setShowEmplid(false); setValues({ ...values, @@ -234,6 +243,14 @@ export const CreateCustomerForm = ({ userPrivileges, setFlashMessage }) => { edipi: uniqueDodid, emplid: '', }); + } else { + setShowEmplid(false); + setValues({ + ...values, + affiliation: e.target.value, + edipi: '', + emplid: '', + }); } }; return ( @@ -253,6 +270,7 @@ export const CreateCustomerForm = ({ userPrivileges, setFlashMessage }) => { value="true" data-testid="is-safety-move-yes" onChange={handleIsSafetyMove} + checked={values.is_safety_move === 'true'} /> { value="false" data-testid="is-safety-move-no" onChange={handleIsSafetyMove} + checked={values.is_safety_move === 'false'} />
@@ -281,25 +300,27 @@ export const CreateCustomerForm = ({ userPrivileges, setFlashMessage }) => { label="DoD ID number" name="edipi" id="edipi" - labelHint="Optional" maxLength="10" isDisabled={isSafetyMove} + data-testid="edipiInput" /> - {isSafetyMove && showSafetyMoveHint && ( - Once a branch is selected, this will generate a random safety move identifier - )} {showEmplid && ( )} + {isSafetyMove && showSafetyMoveHint && ( + + Once a branch is selected, this will generate a random safety move identifier + + )}

Customer Name

diff --git a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx index 7167c40da84..c98c254b36e 100644 --- a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx +++ b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx @@ -69,7 +69,7 @@ const fakePayload = { }, create_okta_account: 'true', cac_user: 'false', - is_safety_move: 'false', + is_safety_move: false, }; const fakeResponse = { @@ -335,6 +335,71 @@ describe('CreateCustomerForm', () => { }); }, 10000); + it('disables and populates DODID and EMPLID inputs when safety move is selected', async () => { + createCustomerWithOktaOption.mockImplementation(() => Promise.resolve(fakeResponse)); + isBooleanFlagEnabled.mockImplementation(() => Promise.resolve(true)); + + const { getByLabelText, getByTestId, getByRole } = render( + + + , + ); + + const user = userEvent.setup(); + + const safetyMove = await screen.findByTestId('is-safety-move-no'); + expect(safetyMove).toBeChecked(); + + // check the safety move box + await userEvent.type(getByTestId('is-safety-move-yes'), safetyPayload.is_safety_move); + + expect(await screen.findByTestId('safetyMoveHint')).toBeInTheDocument(); + + await user.selectOptions(getByLabelText('Branch of service'), ['COAST_GUARD']); + + // the input boxes should now be disabled + expect(await screen.findByTestId('edipiInput')).toBeDisabled(); + expect(await screen.findByTestId('emplidInput')).toBeDisabled(); + + // should be able to submit the form + await user.type(getByLabelText('First name'), safetyPayload.first_name); + await user.type(getByLabelText('Last name'), safetyPayload.last_name); + + await user.type(getByLabelText('Best contact phone'), safetyPayload.telephone); + await user.type(getByLabelText('Personal email'), safetyPayload.personal_email); + + await userEvent.type(getByTestId('res-add-street1'), safetyPayload.residential_address.streetAddress1); + await userEvent.type(getByTestId('res-add-city'), safetyPayload.residential_address.city); + await userEvent.selectOptions(getByTestId('res-add-state'), [safetyPayload.residential_address.state]); + await userEvent.type(getByTestId('res-add-zip'), safetyPayload.residential_address.postalCode); + + await userEvent.type(getByTestId('backup-add-street1'), safetyPayload.backup_mailing_address.streetAddress1); + await userEvent.type(getByTestId('backup-add-city'), safetyPayload.backup_mailing_address.city); + await userEvent.selectOptions(getByTestId('backup-add-state'), [safetyPayload.backup_mailing_address.state]); + await userEvent.type(getByTestId('backup-add-zip'), safetyPayload.backup_mailing_address.postalCode); + + await userEvent.type(getByLabelText('Name'), safetyPayload.backup_contact.name); + await userEvent.type(getByRole('textbox', { name: 'Email' }), safetyPayload.backup_contact.email); + await userEvent.type(getByRole('textbox', { name: 'Phone' }), safetyPayload.backup_contact.telephone); + + const saveBtn = await screen.findByRole('button', { name: 'Save' }); + expect(saveBtn).toBeInTheDocument(); + + await waitFor(() => { + expect(saveBtn).toBeEnabled(); + }); + await userEvent.click(saveBtn); + + await waitFor(() => { + expect(createCustomerWithOktaOption).toHaveBeenCalled(); + expect(mockNavigate).toHaveBeenCalledWith(ordersPath, { + state: { + isSafetyMoveSelected: true, + }, + }); + }); + }, 10000); + it('submits the form and tests for unsupported state validation', async () => { createCustomerWithOktaOption.mockImplementation(() => Promise.resolve(fakeResponse)); diff --git a/src/pages/Office/MoveDetails/MoveDetails.jsx b/src/pages/Office/MoveDetails/MoveDetails.jsx index 8d6a4c359ac..06422417bc2 100644 --- a/src/pages/Office/MoveDetails/MoveDetails.jsx +++ b/src/pages/Office/MoveDetails/MoveDetails.jsx @@ -284,7 +284,7 @@ const MoveDetails = ({ const customerInfo = { name: formattedCustomerName(customer.last_name, customer.first_name, customer.suffix, customer.middle_name), agency: customer.agency, - dodId: customer.dodID, + edipi: customer.edipi, emplid: customer.emplid, phone: customer.phone, altPhone: customer.secondaryTelephone, diff --git a/swagger-def/ghc.yaml b/swagger-def/ghc.yaml index 0bb252d0846..5d48eaff405 100644 --- a/swagger-def/ghc.yaml +++ b/swagger-def/ghc.yaml @@ -4336,7 +4336,7 @@ definitions: type: string format: uuid example: c56a4180-65aa-42ec-a945-5fd21dec0538 - dodID: + edipi: type: string userID: type: string diff --git a/swagger/ghc.yaml b/swagger/ghc.yaml index 62feecb4062..77d9993c7c9 100644 --- a/swagger/ghc.yaml +++ b/swagger/ghc.yaml @@ -4527,7 +4527,7 @@ definitions: type: string format: uuid example: c56a4180-65aa-42ec-a945-5fd21dec0538 - dodID: + edipi: type: string userID: type: string From eb93add3145dcb41c96dc07f4b22071ea86bfb40 Mon Sep 17 00:00:00 2001 From: Paul Stonebraker Date: Thu, 12 Sep 2024 15:37:43 +0000 Subject: [PATCH 42/68] add update second and third addresses to prime api --- .../mto_shipment_address_updater.go | 2 + src/components/PrimeUI/Shipment/Shipment.jsx | 50 ++++++-- .../Shipment/PrimeUIShipmentUpdateAddress.jsx | 89 +++++--------- .../PrimeUIShipmentUpdateAddress.test.jsx | 116 +----------------- src/shared/constants.js | 20 +++ 5 files changed, 99 insertions(+), 178 deletions(-) diff --git a/pkg/services/mto_shipment/mto_shipment_address_updater.go b/pkg/services/mto_shipment/mto_shipment_address_updater.go index 1f9ea1d08ba..edab362940f 100644 --- a/pkg/services/mto_shipment/mto_shipment_address_updater.go +++ b/pkg/services/mto_shipment/mto_shipment_address_updater.go @@ -36,6 +36,8 @@ func isAddressOnShipment(address *models.Address, mtoShipment *models.MTOShipmen mtoShipment.DestinationAddressID, mtoShipment.SecondaryDeliveryAddressID, mtoShipment.SecondaryPickupAddressID, + mtoShipment.TertiaryDeliveryAddressID, + mtoShipment.TertiaryPickupAddressID, } for _, id := range addressIDs { diff --git a/src/components/PrimeUI/Shipment/Shipment.jsx b/src/components/PrimeUI/Shipment/Shipment.jsx index ba59e51a98e..f7849d7585a 100644 --- a/src/components/PrimeUI/Shipment/Shipment.jsx +++ b/src/components/PrimeUI/Shipment/Shipment.jsx @@ -13,7 +13,7 @@ import { ShipmentShape } from 'types/shipment'; import { primeSimulatorRoutes } from 'constants/routes'; import { ppmShipmentStatuses, shipmentDestinationTypes } from 'constants/shipments'; import styles from 'pages/PrimeUI/MoveTaskOrder/MoveDetails.module.scss'; -import { SHIPMENT_OPTIONS } from 'shared/constants'; +import { ADDRESS_TYPES, SHIPMENT_OPTIONS } from 'shared/constants'; const Shipment = ({ shipment, moveId, onDelete, mtoServiceItems }) => { const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false); @@ -188,32 +188,68 @@ const Shipment = ({ shipment, moveId, onDelete, mtoServiceItems }) => {
Pickup Address:
{formatPrimeAPIShipmentAddress(shipment.pickupAddress)}
-
{shipment.pickupAddress?.id && moveId && Edit}
+
+ {shipment.pickupAddress?.id && moveId && ( + + Edit + + )} +
Second Pickup Address:
{formatPrimeAPIShipmentAddress(shipment.secondaryPickupAddress)}
-
{shipment.secondaryPickupAddress?.id && moveId && Edit}
+
+ {shipment.secondaryPickupAddress?.id && moveId && ( + + Edit + + )} +
Third Pickup Address:
{formatPrimeAPIShipmentAddress(shipment.tertiaryPickupAddress)}
-
{shipment.tertiaryPickupAddress?.id && moveId && Edit}
+
+ {shipment.tertiaryPickupAddress?.id && moveId && ( + + Edit + + )} +
Destination Address:
{formatPrimeAPIShipmentAddress(shipment.destinationAddress)}
-
{shipment.destinationAddress?.id && moveId && Edit}
+
+ {shipment.destinationAddress?.id && moveId && ( + + Edit + + )} +
Second Destination Address:
{formatPrimeAPIShipmentAddress(shipment.secondaryDeliveryAddress)}
-
{shipment.secondaryDeliveryAddress?.id && moveId && Edit}
+
+ {shipment.secondaryDeliveryAddress?.id && moveId && ( + + Edit + + )} +
Third Destination Address:
{formatPrimeAPIShipmentAddress(shipment.tertiaryDeliveryAddress)}
-
{shipment.tertiaryDeliveryAddress?.id && moveId && Edit}
+
+ {shipment.tertiaryDeliveryAddress?.id && moveId && ( + + Edit + + )} +
Destination type:
diff --git a/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateAddress.jsx b/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateAddress.jsx index ee1bf418c34..1eb45c91d17 100644 --- a/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateAddress.jsx +++ b/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateAddress.jsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { useNavigate, useParams, generatePath } from 'react-router-dom'; +import { useNavigate, useParams, generatePath, useLocation } from 'react-router-dom'; import { useQueryClient, useMutation } from '@tanstack/react-query'; import { Alert, Grid, GridContainer } from '@trussworks/react-uswds'; import * as Yup from 'yup'; @@ -18,8 +18,9 @@ import primeStyles from 'pages/PrimeUI/Prime.module.scss'; import { isEmpty } from 'shared/utils'; import { fromPrimeAPIAddressFormat } from 'utils/formatters'; import { PRIME_SIMULATOR_MOVE } from 'constants/queryKeys'; +import { getAddressLabel } from 'shared/constants'; -const updatePickupAddressSchema = Yup.object().shape({ +const updateAddressSchema = Yup.object().shape({ addressID: Yup.string(), pickupAddress: Yup.object().shape({ address: addressSchema, @@ -27,14 +28,6 @@ const updatePickupAddressSchema = Yup.object().shape({ eTag: Yup.string(), }); -const updateDestinationAddressSchema = Yup.object().shape({ - addressID: Yup.string(), - destinationAddress: Yup.object().shape({ - address: addressSchema, - }), - eTag: Yup.string(), -}); - const PrimeUIShipmentUpdateAddress = () => { const [errorMessage, setErrorMessage] = useState(); const { moveCodeOrID, shipmentId } = useParams(); @@ -42,6 +35,11 @@ const PrimeUIShipmentUpdateAddress = () => { const mtoShipments = moveTaskOrder?.mtoShipments; const shipment = mtoShipments?.find((mtoShipment) => mtoShipment?.id === shipmentId); const navigate = useNavigate(); + const location = useLocation(); + + const addressType = location?.state?.addressType; + const addressLabel = getAddressLabel(addressType); + const addressData = shipment ? shipment[addressType] : null; const handleClose = () => { navigate(generatePath(primeSimulatorRoutes.VIEW_MOVE_PATH, { moveCodeOrID })); @@ -52,12 +50,10 @@ const PrimeUIShipmentUpdateAddress = () => { onSuccess: (updatedMTOShipmentAddress) => { const shipmentIndex = mtoShipments.findIndex((mtoShipment) => mtoShipment.id === shipmentId); let updateQuery = false; - ['pickupAddress', 'destinationAddress'].forEach((key) => { - if (updatedMTOShipmentAddress.id === mtoShipments[shipmentIndex][key].id) { - mtoShipments[shipmentIndex][key] = updatedMTOShipmentAddress; - updateQuery = true; - } - }); + if (updatedMTOShipmentAddress.id === mtoShipments[shipmentIndex][addressType].id) { + mtoShipments[shipmentIndex][addressType] = updatedMTOShipmentAddress; + updateQuery = true; + } if (updateQuery) { moveTaskOrder.mtoShipments = mtoShipments; queryClient.setQueryData([PRIME_SIMULATOR_MOVE, moveCodeOrID], moveTaskOrder); @@ -87,19 +83,16 @@ const PrimeUIShipmentUpdateAddress = () => { if (isError) return ; const onSubmit = (values, { setSubmitting }) => { - // Choose pickupAddress or destinationAddress by the presence of the object - // by the same name. It's possible that these values are blank and set to - // `undefined` or an empty string `""`. - const address = values.pickupAddress ? values.pickupAddress.address : values.destinationAddress.address; + const { streetAddress1, streetAddress2, streetAddress3, city, state, postalCode } = values.address; const body = { id: values.addressID, - streetAddress1: address.streetAddress1, - streetAddress2: address.streetAddress2, - streetAddress3: address.streetAddress3, - city: address.city, - state: address.state, - postalCode: address.postalCode, + streetAddress1, + streetAddress2, + streetAddress3, + city, + state, + postalCode, }; // Check if the address payload contains any blank properties and remove @@ -121,24 +114,13 @@ const PrimeUIShipmentUpdateAddress = () => { }); }; - const reformatPrimeApiPickupAddress = fromPrimeAPIAddressFormat(shipment.pickupAddress); - const reformatPrimeApiDestinationAddress = fromPrimeAPIAddressFormat(shipment.destinationAddress); - const editablePickupAddress = !isEmpty(reformatPrimeApiPickupAddress); - const editableDestinationAddress = !isEmpty(reformatPrimeApiDestinationAddress); + const reformatPriApiAddress = fromPrimeAPIAddressFormat(addressData); + const editableAddress = !isEmpty(reformatPriApiAddress); - const initialValuesPickupAddress = { - addressID: shipment.pickupAddress?.id, - pickupAddress: { - address: reformatPrimeApiPickupAddress, - }, - eTag: shipment.pickupAddress?.eTag, - }; - const initialValuesDestinationAddress = { - addressID: shipment.destinationAddress?.id, - destinationAddress: { - address: reformatPrimeApiDestinationAddress, - }, - eTag: shipment.destinationAddress?.eTag, + const initialValues = { + addressID: addressData?.id, + address: reformatPriApiAddress, + eTag: addressData?.eTag, }; return ( @@ -155,23 +137,14 @@ const PrimeUIShipmentUpdateAddress = () => {
)} -

Update Existing Pickup & Destination Address

- {editablePickupAddress && ( - - )} - {editableDestinationAddress && ( +

Update Existing {`${addressLabel}`}

+ {editableAddress && ( )} diff --git a/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateAddress.test.jsx b/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateAddress.test.jsx index 3f0f87a8e67..5e2083a7fd7 100644 --- a/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateAddress.test.jsx +++ b/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateAddress.test.jsx @@ -15,6 +15,7 @@ const mockNavigate = jest.fn(); jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), useNavigate: () => mockNavigate, + useLocation: () => ({ state: { addressType: 'pickupAddress' } }), })); jest.mock('hooks/queries', () => ({ @@ -72,54 +73,6 @@ const moveReturnValue = { isError: false, }; -const noDestinationAddressReturnValue = { - moveTaskOrder: { - id: '1', - moveCode: 'LN4T89', - mtoShipments: [ - { - id: '4', - shipmentType: 'HHG', - requestedPickupDate: '2021-12-01', - pickupAddress: { - id: '1', - streetAddress1: '800 Madison Avenue', - city: 'New York', - state: 'NY', - postalCode: '10002', - }, - destinationAddress: null, - }, - ], - }, - isLoading: false, - isError: false, -}; - -const noPickupAddressReturnValue = { - moveTaskOrder: { - id: '1', - moveCode: 'LN4T89', - mtoShipments: [ - { - id: '4', - shipmentType: 'HHG', - requestedPickupDate: '2021-12-01', - pickupAddress: null, - destinationAddress: { - id: '2', - streetAddress1: '100 1st Avenue', - city: 'New York', - state: 'NY', - postalCode: '10001', - }, - }, - ], - }, - isLoading: false, - isError: false, -}; - const renderComponent = () => { render( @@ -168,7 +121,7 @@ describe('PrimeUIShipmentUpdateAddress page', () => { renderComponent(); const pageHeading = await screen.getByRole('heading', { - name: 'Update Existing Pickup & Destination Address', + name: 'Update Existing Pickup Address', level: 1, }); expect(pageHeading).toBeInTheDocument(); @@ -179,74 +132,11 @@ describe('PrimeUIShipmentUpdateAddress page', () => { const shipment = moveTaskOrder.mtoShipments[shipmentIndex]; await waitFor(() => { - expect(screen.getByRole('heading', { name: /Pickup address/, level: 2 })); expect(screen.getAllByLabelText('Address 1')[0]).toHaveValue(shipment.pickupAddress.streetAddress1); expect(screen.getAllByLabelText(/Address 2/)[0]).toHaveValue(''); expect(screen.getAllByLabelText('City')[0]).toHaveValue(shipment.pickupAddress.city); expect(screen.getAllByLabelText('State')[0]).toHaveValue(shipment.pickupAddress.state); expect(screen.getAllByLabelText('ZIP')[0]).toHaveValue(shipment.pickupAddress.postalCode); - expect(screen.getByRole('heading', { name: /Destination address/, level: 2 })); - expect(screen.getAllByLabelText('Address 1')[1]).toHaveValue(shipment.destinationAddress.streetAddress1); - expect(screen.getAllByLabelText(/Address 2/)[1]).toHaveValue(''); - expect(screen.getAllByLabelText('City')[1]).toHaveValue(shipment.destinationAddress.city); - expect(screen.getAllByLabelText('State')[1]).toHaveValue(shipment.destinationAddress.state); - expect(screen.getAllByLabelText('ZIP')[1]).toHaveValue(shipment.destinationAddress.postalCode); - }); - }); - - it('displays only pickup address form', async () => { - usePrimeSimulatorGetMove.mockReturnValue(noDestinationAddressReturnValue); - - renderComponent(); - - const pageHeading = await screen.getByRole('heading', { - name: 'Update Existing Pickup & Destination Address', - level: 1, - }); - expect(pageHeading).toBeInTheDocument(); - - const shipmentIndex = noDestinationAddressReturnValue.moveTaskOrder.mtoShipments.findIndex( - (mtoShipment) => mtoShipment.id === routingParams.shipmentId, - ); - const shipment = noDestinationAddressReturnValue.moveTaskOrder.mtoShipments[shipmentIndex]; - - await waitFor(() => { - expect(screen.getByRole('heading', { name: /Pickup address/, level: 2 })); - expect(screen.getAllByLabelText('Address 1').length).toBe(1); - expect(screen.getAllByLabelText('Address 1')[0]).toHaveValue(shipment.pickupAddress.streetAddress1); - expect(screen.getAllByLabelText(/Address 2/)[0]).toHaveValue(''); - expect(screen.getAllByLabelText('City')[0]).toHaveValue(shipment.pickupAddress.city); - expect(screen.getAllByLabelText('State')[0]).toHaveValue(shipment.pickupAddress.state); - expect(screen.getAllByLabelText('ZIP')[0]).toHaveValue(shipment.pickupAddress.postalCode); - expect(shipment.destinationAddress).toBeNull(); - }); - }); - - it('displays only destination address form', async () => { - usePrimeSimulatorGetMove.mockReturnValue(noPickupAddressReturnValue); - - renderComponent(); - - const pageHeading = await screen.getByRole('heading', { - name: 'Update Existing Pickup & Destination Address', - level: 1, - }); - expect(pageHeading).toBeInTheDocument(); - - const shipmentIndex = noPickupAddressReturnValue.moveTaskOrder.mtoShipments.findIndex( - (mtoShipment) => mtoShipment.id === routingParams.shipmentId, - ); - const shipment = noPickupAddressReturnValue.moveTaskOrder.mtoShipments[shipmentIndex]; - - await waitFor(() => { - expect(shipment.pickupAddress).toBeNull(); - expect(screen.getByRole('heading', { name: /Destination address/, level: 2 })); - expect(screen.getAllByLabelText('Address 1').length).toBe(1); - expect(screen.getAllByLabelText('Address 1')[0]).toHaveValue(shipment.destinationAddress.streetAddress1); - expect(screen.getAllByLabelText(/Address 2/)[0]).toHaveValue(''); - expect(screen.getAllByLabelText('City')[0]).toHaveValue(shipment.destinationAddress.city); - expect(screen.getAllByLabelText('State')[0]).toHaveValue(shipment.destinationAddress.state); - expect(screen.getAllByLabelText('ZIP')[0]).toHaveValue(shipment.destinationAddress.postalCode); }); }); }); @@ -304,7 +194,7 @@ describe('PrimeUIShipmentUpdateAddress page', () => { renderComponent(); await act(async () => { - expect(screen.getAllByRole('button', { name: 'Save' }).length).toBe(2); + expect(screen.getAllByRole('button', { name: 'Save' }).length).toBe(1); await userEvent.click(screen.getAllByRole('button', { name: 'Save' })[0]); }); diff --git a/src/shared/constants.js b/src/shared/constants.js index 4fb0ab134a8..7852275dc1b 100644 --- a/src/shared/constants.js +++ b/src/shared/constants.js @@ -198,3 +198,23 @@ export const MOVE_DOCUMENT_TYPE = { AMENDMENTS: 'AMENDMENTS', SUPPORTING: 'SUPPORTING', }; + +export const ADDRESS_TYPES = { + PICKUP: 'pickupAddress', + SECOND_PICKUP: 'secondaryPickupAddress', + THIRD_PICKUP: 'tertiaryPickupAddress', + DESTINATION: 'destinationAddress', + SECOND_DESTINATION: 'secondaryDeliveryAddress', + THIRD_DESTINATION: 'tertiaryDeliveryAddress', +}; + +const ADDRESS_LABELS_MAP = { + [ADDRESS_TYPES.PICKUP]: 'Pickup Address', + [ADDRESS_TYPES.SECOND_PICKUP]: 'Second Pickup Address', + [ADDRESS_TYPES.THIRD_PICKUP]: 'Third Pickup Address', + [ADDRESS_TYPES.DESTINATION]: 'Destination Address', + [ADDRESS_TYPES.SECOND_DESTINATION]: 'Second Destination Address', + [ADDRESS_TYPES.THIRD_DESTINATION]: 'Third Destination Address', +}; + +export const getAddressLabel = (type) => ADDRESS_LABELS_MAP[type]; From d6f7a92ddcdbdd99256449fc188098f3b7968736 Mon Sep 17 00:00:00 2001 From: Paul Stonebraker Date: Thu, 12 Sep 2024 17:59:58 +0000 Subject: [PATCH 43/68] update validation --- .../PrimeUI/Shipment/PrimeUIShipmentUpdateAddress.jsx | 11 ++++++++--- .../Shipment/PrimeUIShipmentUpdateAddressForm.jsx | 9 ++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateAddress.jsx b/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateAddress.jsx index 1eb45c91d17..0d234eb1136 100644 --- a/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateAddress.jsx +++ b/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateAddress.jsx @@ -11,7 +11,7 @@ import { usePrimeSimulatorGetMove } from 'hooks/queries'; import LoadingPlaceholder from 'shared/LoadingPlaceholder'; import SomethingWentWrong from 'shared/SomethingWentWrong'; import { primeSimulatorRoutes } from 'constants/routes'; -import { addressSchema } from 'utils/validation'; +import { ZIP_CODE_REGEX } from 'utils/validation'; import scrollToTop from 'shared/scrollToTop'; import { updatePrimeMTOShipmentAddress } from 'services/primeApi'; import primeStyles from 'pages/PrimeUI/Prime.module.scss'; @@ -22,8 +22,13 @@ import { getAddressLabel } from 'shared/constants'; const updateAddressSchema = Yup.object().shape({ addressID: Yup.string(), - pickupAddress: Yup.object().shape({ - address: addressSchema, + address: Yup.object().shape({ + id: Yup.string(), + streetAddress1: Yup.string().required('Required'), + streetAddress2: Yup.string(), + city: Yup.string().required('Required'), + state: Yup.string().required('Required').length(2, 'Must use state abbreviation'), + postalCode: Yup.string().required('Required').matches(ZIP_CODE_REGEX, 'Must be valid zip code'), }), eTag: Yup.string(), }); diff --git a/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateAddressForm.jsx b/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateAddressForm.jsx index 7045a671fb0..ec188502922 100644 --- a/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateAddressForm.jsx +++ b/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateAddressForm.jsx @@ -54,12 +54,7 @@ const PrimeUIShipmentUpdateAddressForm = ({ PrimeUIShipmentUpdateAddressForm.propTypes = { initialValues: PropTypes.shape({ - pickupAddress: PropTypes.shape({ - address: ResidentialAddressShape, - }), - destinationAddress: PropTypes.shape({ - address: ResidentialAddressShape, - }), + address: ResidentialAddressShape, addressID: PropTypes.string, eTag: PropTypes.string, }).isRequired, @@ -69,7 +64,7 @@ PrimeUIShipmentUpdateAddressForm.propTypes = { addressID: PropTypes.string, eTag: PropTypes.string, }).isRequired, - addressLocation: PropTypes.oneOf(['Pickup address', 'Destination address']).isRequired, + addressLocation: PropTypes.string.isRequired, name: PropTypes.string.isRequired, }; From 615a9083317d8440a03bf025dac3214c5f912d8b Mon Sep 17 00:00:00 2001 From: Paul Stonebraker Date: Thu, 12 Sep 2024 18:41:12 +0000 Subject: [PATCH 44/68] update playwright test to account for new fields --- .../tests/office/primesimulator/primeSimulatorFlows.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playwright/tests/office/primesimulator/primeSimulatorFlows.spec.js b/playwright/tests/office/primesimulator/primeSimulatorFlows.spec.js index 5b6f56fbb57..a6cc84e9f04 100644 --- a/playwright/tests/office/primesimulator/primeSimulatorFlows.spec.js +++ b/playwright/tests/office/primesimulator/primeSimulatorFlows.spec.js @@ -148,7 +148,7 @@ test.describe('Prime simulator user', () => { await page.locator('input[name="destinationAddress.city"]').fill('Joshua Tree'); await page.locator('select[name="destinationAddress.state"]').selectOption({ label: 'CA' }); await page.locator('input[name="destinationAddress.postalCode"]').fill('92252'); - await page.getByTestId('dropdown').nth(1).selectOption('Home of record (HOR)'); + await page.getByTestId('dropdown').nth(5).selectOption('Home of record (HOR)'); await page.getByText('Save').click(); await expect(page.getByText('Successfully updated shipment')).toHaveCount(1); From 2211099de2e0cf39c8eba6ce487cd49a42160e02 Mon Sep 17 00:00:00 2001 From: Paul Stonebraker Date: Thu, 12 Sep 2024 18:45:15 +0000 Subject: [PATCH 45/68] update yaml comment --- pkg/gen/primev3api/embedded_spec.go | 4 ++-- pkg/gen/primev3messages/update_p_p_m_shipment.go | 2 +- swagger-def/prime_v3.yaml | 2 +- swagger/prime_v3.yaml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/gen/primev3api/embedded_spec.go b/pkg/gen/primev3api/embedded_spec.go index c71502c17ca..4a38b1362b2 100644 --- a/pkg/gen/primev3api/embedded_spec.go +++ b/pkg/gen/primev3api/embedded_spec.go @@ -3256,7 +3256,7 @@ func init() { ] }, "tertiaryPickupAddress": { - "description": "An optional second pickup location near the origin where additional goods exist.\n", + "description": "An optional third pickup location near the origin where additional goods exist.\n", "allOf": [ { "$ref": "#/definitions/Address" @@ -6742,7 +6742,7 @@ func init() { ] }, "tertiaryPickupAddress": { - "description": "An optional second pickup location near the origin where additional goods exist.\n", + "description": "An optional third pickup location near the origin where additional goods exist.\n", "allOf": [ { "$ref": "#/definitions/Address" diff --git a/pkg/gen/primev3messages/update_p_p_m_shipment.go b/pkg/gen/primev3messages/update_p_p_m_shipment.go index 2fb250ddc38..ead98493601 100644 --- a/pkg/gen/primev3messages/update_p_p_m_shipment.go +++ b/pkg/gen/primev3messages/update_p_p_m_shipment.go @@ -101,7 +101,7 @@ type UpdatePPMShipment struct { Address } `json:"tertiaryDestinationAddress,omitempty"` - // An optional second pickup location near the origin where additional goods exist. + // An optional third pickup location near the origin where additional goods exist. // TertiaryPickupAddress struct { Address diff --git a/swagger-def/prime_v3.yaml b/swagger-def/prime_v3.yaml index 9b77551d857..9a28d0380e2 100644 --- a/swagger-def/prime_v3.yaml +++ b/swagger-def/prime_v3.yaml @@ -567,7 +567,7 @@ definitions: x-nullable: true tertiaryPickupAddress: description: > - An optional second pickup location near the origin where additional goods exist. + An optional third pickup location near the origin where additional goods exist. allOf: - $ref: 'definitions/Address.yaml' destinationAddress: diff --git a/swagger/prime_v3.yaml b/swagger/prime_v3.yaml index c96094663eb..c37b6cf7778 100644 --- a/swagger/prime_v3.yaml +++ b/swagger/prime_v3.yaml @@ -870,7 +870,7 @@ definitions: x-nullable: true tertiaryPickupAddress: description: > - An optional second pickup location near the origin where additional + An optional third pickup location near the origin where additional goods exist. allOf: - $ref: '#/definitions/Address' From dedfb584daa9cf251f67acfcea2ee664b7d11245 Mon Sep 17 00:00:00 2001 From: deandreJones Date: Fri, 13 Sep 2024 13:57:08 +0000 Subject: [PATCH 46/68] test prev cache key --- .circleci/config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 775872e9ec1..efed73dd820 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1498,7 +1498,7 @@ jobs: # # The trailing hyphen in restore_cache seems important # according to the page linked above - - v8-server-tests-coverage- + - v7-server-tests-coverage- - run: name: Ensure Test Coverage Increasing command: | @@ -1632,7 +1632,7 @@ jobs: # The trailing hyphen in restore_cache seems important # according to the page linked above keys: - - v6-client-tests-coverage- + - v5-client-tests-coverage- - run: name: Ensure Test Coverage Increasing command: | From a02927429ec0dcb7749acdebfe2c4558a1aaafef Mon Sep 17 00:00:00 2001 From: deandreJones Date: Fri, 13 Sep 2024 14:42:40 +0000 Subject: [PATCH 47/68] correct cache keys --- .circleci/config.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index efed73dd820..b0f66811d27 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1498,7 +1498,7 @@ jobs: # # The trailing hyphen in restore_cache seems important # according to the page linked above - - v7-server-tests-coverage- + - v8-server-tests-coverage- - run: name: Ensure Test Coverage Increasing command: | @@ -1554,7 +1554,7 @@ jobs: # Use the BuildNum to update the cache key so that the # coverage cache is always updated - save_cache: - key: v7-server-tests-coverage-{{ .BuildNum }} + key: v8-server-tests-coverage-{{ .BuildNum }} paths: - ~/transcom/mymove/tmp/baseline-go-coverage when: always @@ -1632,7 +1632,7 @@ jobs: # The trailing hyphen in restore_cache seems important # according to the page linked above keys: - - v5-client-tests-coverage- + - v6-client-tests-coverage- - run: name: Ensure Test Coverage Increasing command: | @@ -1684,7 +1684,7 @@ jobs: # Use the BuildNum to update the cache key so that the # coverage cache is always updated - save_cache: - key: v5-client-tests-coverage-{{ .BuildNum }} + key: v6-client-tests-coverage-{{ .BuildNum }} paths: - ~/transcom/mymove/tmp/baseline-jest-coverage when: always From ecd1b33df8482b3a658faecafb43c54243250cbe Mon Sep 17 00:00:00 2001 From: Maria Traskowsky Date: Fri, 13 Sep 2024 15:40:12 +0000 Subject: [PATCH 48/68] update red color to meet 508 compliance --- src/pages/Office/MoveDetails/MoveDetails.jsx | 2 +- .../ServicesCounselingMoveDetails.jsx | 2 +- src/shared/styles/colors.scss | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pages/Office/MoveDetails/MoveDetails.jsx b/src/pages/Office/MoveDetails/MoveDetails.jsx index f6397c94061..cf28e9a42ef 100644 --- a/src/pages/Office/MoveDetails/MoveDetails.jsx +++ b/src/pages/Office/MoveDetails/MoveDetails.jsx @@ -328,7 +328,7 @@ const MoveDetails = ({
Date: Fri, 13 Sep 2024 22:13:11 +0000 Subject: [PATCH 49/68] Commits from PR 13578 --- pkg/gen/internalapi/embedded_spec.go | 12 ++-- .../create_service_member_payload.go | 4 +- .../patch_service_member_payload.go | 4 +- .../service_member_payload.go | 4 +- pkg/handlers/internalapi/service_members.go | 5 +- src/pages/MyMove/Profile/EditContactInfo.jsx | 4 +- .../CustomerOnboarding/CreateCustomerForm.jsx | 14 ++++- .../CreateCustomerForm.test.jsx | 60 +++++++++++++++++++ swagger-def/internal.yaml | 6 +- swagger/internal.yaml | 6 +- 10 files changed, 94 insertions(+), 25 deletions(-) diff --git a/pkg/gen/internalapi/embedded_spec.go b/pkg/gen/internalapi/embedded_spec.go index 794f7f59f10..3ba15e834fa 100644 --- a/pkg/gen/internalapi/embedded_spec.go +++ b/pkg/gen/internalapi/embedded_spec.go @@ -3987,7 +3987,7 @@ func init() { "type": "string", "format": "telephone", "title": "Alternate phone", - "pattern": "^[2-9]\\d{2}-\\d{3}-\\d{4}$", + "pattern": "^([2-9]\\d{2}-\\d{3}-\\d{4})?$", "x-nullable": true, "example": "212-555-5555" }, @@ -6429,7 +6429,7 @@ func init() { "type": "string", "format": "telephone", "title": "Alternate Phone", - "pattern": "^[2-9]\\d{2}-\\d{3}-\\d{4}$", + "pattern": "^([2-9]\\d{2}-\\d{3}-\\d{4})?$", "x-nullable": true, "example": "212-555-5555" }, @@ -6905,7 +6905,7 @@ func init() { "type": "string", "format": "telephone", "title": "Secondary Phone", - "pattern": "^[2-9]\\d{2}-\\d{3}-\\d{4}$", + "pattern": "^([2-9]\\d{2}-\\d{3}-\\d{4})?$", "x-nullable": true, "example": "212-555-5555" }, @@ -12588,7 +12588,7 @@ func init() { "type": "string", "format": "telephone", "title": "Alternate phone", - "pattern": "^[2-9]\\d{2}-\\d{3}-\\d{4}$", + "pattern": "^([2-9]\\d{2}-\\d{3}-\\d{4})?$", "x-nullable": true, "example": "212-555-5555" }, @@ -15034,7 +15034,7 @@ func init() { "type": "string", "format": "telephone", "title": "Alternate Phone", - "pattern": "^[2-9]\\d{2}-\\d{3}-\\d{4}$", + "pattern": "^([2-9]\\d{2}-\\d{3}-\\d{4})?$", "x-nullable": true, "example": "212-555-5555" }, @@ -15512,7 +15512,7 @@ func init() { "type": "string", "format": "telephone", "title": "Secondary Phone", - "pattern": "^[2-9]\\d{2}-\\d{3}-\\d{4}$", + "pattern": "^([2-9]\\d{2}-\\d{3}-\\d{4})?$", "x-nullable": true, "example": "212-555-5555" }, diff --git a/pkg/gen/internalmessages/create_service_member_payload.go b/pkg/gen/internalmessages/create_service_member_payload.go index a2b48dce311..737d5a0a897 100644 --- a/pkg/gen/internalmessages/create_service_member_payload.go +++ b/pkg/gen/internalmessages/create_service_member_payload.go @@ -68,7 +68,7 @@ type CreateServiceMemberPayload struct { // Alternate phone // Example: 212-555-5555 - // Pattern: ^[2-9]\d{2}-\d{3}-\d{4}$ + // Pattern: ^([2-9]\d{2}-\d{3}-\d{4})?$ SecondaryTelephone *string `json:"secondary_telephone,omitempty"` // Suffix @@ -261,7 +261,7 @@ func (m *CreateServiceMemberPayload) validateSecondaryTelephone(formats strfmt.R return nil } - if err := validate.Pattern("secondary_telephone", "body", *m.SecondaryTelephone, `^[2-9]\d{2}-\d{3}-\d{4}$`); err != nil { + if err := validate.Pattern("secondary_telephone", "body", *m.SecondaryTelephone, `^([2-9]\d{2}-\d{3}-\d{4})?$`); err != nil { return err } diff --git a/pkg/gen/internalmessages/patch_service_member_payload.go b/pkg/gen/internalmessages/patch_service_member_payload.go index ec9b127dcda..a85374ad1eb 100644 --- a/pkg/gen/internalmessages/patch_service_member_payload.go +++ b/pkg/gen/internalmessages/patch_service_member_payload.go @@ -72,7 +72,7 @@ type PatchServiceMemberPayload struct { // Alternate Phone // Example: 212-555-5555 - // Pattern: ^[2-9]\d{2}-\d{3}-\d{4}$ + // Pattern: ^([2-9]\d{2}-\d{3}-\d{4})?$ SecondaryTelephone *string `json:"secondary_telephone,omitempty"` // Suffix @@ -266,7 +266,7 @@ func (m *PatchServiceMemberPayload) validateSecondaryTelephone(formats strfmt.Re return nil } - if err := validate.Pattern("secondary_telephone", "body", *m.SecondaryTelephone, `^[2-9]\d{2}-\d{3}-\d{4}$`); err != nil { + if err := validate.Pattern("secondary_telephone", "body", *m.SecondaryTelephone, `^([2-9]\d{2}-\d{3}-\d{4})?$`); err != nil { return err } diff --git a/pkg/gen/internalmessages/service_member_payload.go b/pkg/gen/internalmessages/service_member_payload.go index 463243292ff..a288cfba291 100644 --- a/pkg/gen/internalmessages/service_member_payload.go +++ b/pkg/gen/internalmessages/service_member_payload.go @@ -95,7 +95,7 @@ type ServiceMemberPayload struct { // Secondary Phone // Example: 212-555-5555 - // Pattern: ^[2-9]\d{2}-\d{3}-\d{4}$ + // Pattern: ^([2-9]\d{2}-\d{3}-\d{4})?$ SecondaryTelephone *string `json:"secondary_telephone,omitempty"` // Suffix @@ -411,7 +411,7 @@ func (m *ServiceMemberPayload) validateSecondaryTelephone(formats strfmt.Registr return nil } - if err := validate.Pattern("secondary_telephone", "body", *m.SecondaryTelephone, `^[2-9]\d{2}-\d{3}-\d{4}$`); err != nil { + if err := validate.Pattern("secondary_telephone", "body", *m.SecondaryTelephone, `^([2-9]\d{2}-\d{3}-\d{4})?$`); err != nil { return err } diff --git a/pkg/handlers/internalapi/service_members.go b/pkg/handlers/internalapi/service_members.go index cd257025cef..bd91b21f4fc 100644 --- a/pkg/handlers/internalapi/service_members.go +++ b/pkg/handlers/internalapi/service_members.go @@ -235,9 +235,8 @@ func (h PatchServiceMemberHandler) patchServiceMemberWithPayload(serviceMember * if payload.Telephone != nil { serviceMember.Telephone = payload.Telephone } - if payload.SecondaryTelephone != nil { - serviceMember.SecondaryTelephone = payload.SecondaryTelephone - } + // Need to be able to accept a nil value for this optional field + serviceMember.SecondaryTelephone = payload.SecondaryTelephone if payload.PersonalEmail != nil { serviceMember.PersonalEmail = payload.PersonalEmail } diff --git a/src/pages/MyMove/Profile/EditContactInfo.jsx b/src/pages/MyMove/Profile/EditContactInfo.jsx index aa27b30eae0..898031e46b8 100644 --- a/src/pages/MyMove/Profile/EditContactInfo.jsx +++ b/src/pages/MyMove/Profile/EditContactInfo.jsx @@ -73,9 +73,7 @@ export const EditContactInfo = ({ backup_mailing_address: values[backupAddressName.toString()], }; - if (values?.secondary_telephone) { - serviceMemberPayload.secondary_telephone = values?.secondary_telephone; - } + serviceMemberPayload.secondary_telephone = values?.secondary_telephone; const backupContactPayload = { id: currentBackupContacts[0].id, diff --git a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx index b8a6ed6a6fa..0bb4255d3e6 100644 --- a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx +++ b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx @@ -41,6 +41,7 @@ export const CreateCustomerForm = ({ userPrivileges, setFlashMessage }) => { const backupContactName = 'backup_contact'; const [isSafetyMoveFF, setSafetyMoveFF] = useState(false); + const [secondaryTelephoneNum, setSecondaryTelephoneNum] = useState(''); useEffect(() => { isBooleanFlagEnabled('safety_move')?.then((enabled) => { @@ -189,6 +190,16 @@ export const CreateCustomerForm = ({ userPrivileges, setFlashMessage }) => { {({ isValid, handleSubmit, setValues, values, handleChange }) => { + const handleSubmitNext = () => { + setValues({ + ...values, + secondary_telephone: secondaryTelephoneNum, + }); + handleSubmit(); + }; + const handlePhoneNumChange = (value) => { + setSecondaryTelephoneNum(value); + }; const handleIsSafetyMove = (e) => { const { value } = e.target; if (value === 'true') { @@ -305,6 +316,7 @@ export const CreateCustomerForm = ({ userPrivileges, setFlashMessage }) => { type="tel" minimum="12" mask="000{-}000{-}0000" + onChange={handlePhoneNumChange} /> @@ -479,7 +491,7 @@ export const CreateCustomerForm = ({ userPrivileges, setFlashMessage }) => { editMode onCancelClick={handleBack} disableNext={!isValid} - onNextClick={handleSubmit} + onNextClick={handleSubmitNext} />
diff --git a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx index 7167c40da84..cf97cda4d5d 100644 --- a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx +++ b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx @@ -215,6 +215,66 @@ describe('CreateCustomerForm', () => { expect(screen.getByText('EMPLID')).toBeInTheDocument(); }); + it('payload can have an empty secondary phone number', async () => { + createCustomerWithOktaOption.mockImplementation(() => Promise.resolve(fakeResponse)); + + const { getByLabelText, getByTestId, getByRole } = render( + + + , + ); + + const user = userEvent.setup(); + + const saveBtn = await screen.findByRole('button', { name: 'Save' }); + expect(saveBtn).toBeInTheDocument(); + + await user.selectOptions(getByLabelText('Branch of service'), [fakePayload.affiliation]); + + await user.type(getByLabelText('First name'), fakePayload.first_name); + await user.type(getByLabelText('Last name'), fakePayload.last_name); + + await user.type(getByLabelText('Best contact phone'), fakePayload.telephone); + await user.type(getByLabelText('Personal email'), fakePayload.personal_email); + + await user.type(getByTestId('res-add-street1'), fakePayload.residential_address.streetAddress1); + await user.type(getByTestId('res-add-city'), fakePayload.residential_address.city); + await user.selectOptions(getByTestId('res-add-state'), [fakePayload.residential_address.state]); + await user.type(getByTestId('res-add-zip'), fakePayload.residential_address.postalCode); + + await user.type(getByTestId('backup-add-street1'), fakePayload.backup_mailing_address.streetAddress1); + await user.type(getByTestId('backup-add-city'), fakePayload.backup_mailing_address.city); + await user.selectOptions(getByTestId('backup-add-state'), [fakePayload.backup_mailing_address.state]); + await user.type(getByTestId('backup-add-zip'), fakePayload.backup_mailing_address.postalCode); + + await user.type(getByLabelText('Name'), fakePayload.backup_contact.name); + await user.type(getByRole('textbox', { name: 'Email' }), fakePayload.backup_contact.email); + await user.type(getByRole('textbox', { name: 'Phone' }), fakePayload.backup_contact.telephone); + + await userEvent.type(getByTestId('create-okta-account-yes'), fakePayload.create_okta_account); + + await userEvent.type(getByTestId('cac-user-no'), fakePayload.cac_user); + + await waitFor(() => { + expect(saveBtn).toBeEnabled(); + }); + + const waiter = waitFor(() => { + expect(createCustomerWithOktaOption).toHaveBeenCalled(); + expect(mockNavigate).toHaveBeenCalledWith(ordersPath, { + state: { + isSafetyMoveSelected: false, + }, + }); + }); + + await user.click(saveBtn); + await waiter; + expect(mockNavigate).toHaveBeenCalled(); + + expect(createCustomerWithOktaOption.mock.calls[0][0]).not.toHaveProperty('secondary_number'); + }, 10000); + it('navigates the user on cancel click', async () => { const { getByText } = render( diff --git a/swagger-def/internal.yaml b/swagger-def/internal.yaml index 679236a358b..134d05f587a 100644 --- a/swagger-def/internal.yaml +++ b/swagger-def/internal.yaml @@ -705,7 +705,7 @@ definitions: secondary_telephone: type: string format: telephone - pattern: '^[2-9]\d{2}-\d{3}-\d{4}$' + pattern: '^([2-9]\d{2}-\d{3}-\d{4})?$' example: 212-555-5555 x-nullable: true title: Secondary Phone @@ -799,7 +799,7 @@ definitions: secondary_telephone: type: string format: telephone - pattern: '^[2-9]\d{2}-\d{3}-\d{4}$' + pattern: '^([2-9]\d{2}-\d{3}-\d{4})?$' example: 212-555-5555 x-nullable: true title: Alternate phone @@ -883,7 +883,7 @@ definitions: secondary_telephone: type: string format: telephone - pattern: '^[2-9]\d{2}-\d{3}-\d{4}$' + pattern: '^([2-9]\d{2}-\d{3}-\d{4})?$' example: 212-555-5555 x-nullable: true title: Alternate Phone diff --git a/swagger/internal.yaml b/swagger/internal.yaml index 2ddb5861003..ca80ccc7816 100644 --- a/swagger/internal.yaml +++ b/swagger/internal.yaml @@ -726,7 +726,7 @@ definitions: secondary_telephone: type: string format: telephone - pattern: ^[2-9]\d{2}-\d{3}-\d{4}$ + pattern: ^([2-9]\d{2}-\d{3}-\d{4})?$ example: 212-555-5555 x-nullable: true title: Secondary Phone @@ -820,7 +820,7 @@ definitions: secondary_telephone: type: string format: telephone - pattern: ^[2-9]\d{2}-\d{3}-\d{4}$ + pattern: ^([2-9]\d{2}-\d{3}-\d{4})?$ example: 212-555-5555 x-nullable: true title: Alternate phone @@ -904,7 +904,7 @@ definitions: secondary_telephone: type: string format: telephone - pattern: ^[2-9]\d{2}-\d{3}-\d{4}$ + pattern: ^([2-9]\d{2}-\d{3}-\d{4})?$ example: 212-555-5555 x-nullable: true title: Alternate Phone From 0ceb94d02d9bbaf497e8047552aec616ba36b702 Mon Sep 17 00:00:00 2001 From: AaronW Date: Fri, 13 Sep 2024 22:16:12 +0000 Subject: [PATCH 50/68] Commits from PR 13616 --- pkg/handlers/internalapi/service_members.go | 5 +++-- src/pages/MyMove/Profile/ContactInfo.jsx | 6 +++--- .../CustomerOnboarding/CreateCustomerForm.jsx | 14 +------------- 3 files changed, 7 insertions(+), 18 deletions(-) diff --git a/pkg/handlers/internalapi/service_members.go b/pkg/handlers/internalapi/service_members.go index bd91b21f4fc..cd257025cef 100644 --- a/pkg/handlers/internalapi/service_members.go +++ b/pkg/handlers/internalapi/service_members.go @@ -235,8 +235,9 @@ func (h PatchServiceMemberHandler) patchServiceMemberWithPayload(serviceMember * if payload.Telephone != nil { serviceMember.Telephone = payload.Telephone } - // Need to be able to accept a nil value for this optional field - serviceMember.SecondaryTelephone = payload.SecondaryTelephone + if payload.SecondaryTelephone != nil { + serviceMember.SecondaryTelephone = payload.SecondaryTelephone + } if payload.PersonalEmail != nil { serviceMember.PersonalEmail = payload.PersonalEmail } diff --git a/src/pages/MyMove/Profile/ContactInfo.jsx b/src/pages/MyMove/Profile/ContactInfo.jsx index 2b25f520797..298789034a0 100644 --- a/src/pages/MyMove/Profile/ContactInfo.jsx +++ b/src/pages/MyMove/Profile/ContactInfo.jsx @@ -37,13 +37,13 @@ export const ContactInfo = ({ serviceMember, updateServiceMember, userEmail }) = const payload = { id: serviceMember.id, telephone: values?.telephone, - secondary_telephone: values?.secondary_telephone, + secondary_telephone: values?.secondary_telephone || '', personal_email: values?.personal_email, phone_is_preferred: values?.phone_is_preferred, email_is_preferred: values?.email_is_preferred, }; - if (!payload.secondary_telephone) { - delete payload.secondary_telephone; + if (!payload.secondary_telephone || payload.secondary_telephone === '') { + payload.secondary_telephone = ''; } return patchServiceMember(payload) diff --git a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx index 0bb4255d3e6..b8a6ed6a6fa 100644 --- a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx +++ b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx @@ -41,7 +41,6 @@ export const CreateCustomerForm = ({ userPrivileges, setFlashMessage }) => { const backupContactName = 'backup_contact'; const [isSafetyMoveFF, setSafetyMoveFF] = useState(false); - const [secondaryTelephoneNum, setSecondaryTelephoneNum] = useState(''); useEffect(() => { isBooleanFlagEnabled('safety_move')?.then((enabled) => { @@ -190,16 +189,6 @@ export const CreateCustomerForm = ({ userPrivileges, setFlashMessage }) => { {({ isValid, handleSubmit, setValues, values, handleChange }) => { - const handleSubmitNext = () => { - setValues({ - ...values, - secondary_telephone: secondaryTelephoneNum, - }); - handleSubmit(); - }; - const handlePhoneNumChange = (value) => { - setSecondaryTelephoneNum(value); - }; const handleIsSafetyMove = (e) => { const { value } = e.target; if (value === 'true') { @@ -316,7 +305,6 @@ export const CreateCustomerForm = ({ userPrivileges, setFlashMessage }) => { type="tel" minimum="12" mask="000{-}000{-}0000" - onChange={handlePhoneNumChange} /> @@ -491,7 +479,7 @@ export const CreateCustomerForm = ({ userPrivileges, setFlashMessage }) => { editMode onCancelClick={handleBack} disableNext={!isValid} - onNextClick={handleSubmitNext} + onNextClick={handleSubmit} />
From 8c1dd98052c7794c9613bee090a7957c345a953b Mon Sep 17 00:00:00 2001 From: Michael Inthavongsay Date: Tue, 17 Sep 2024 15:52:51 +0000 Subject: [PATCH 51/68] add migration file to update 7 records to transportation_offices table to align with Danny M's spreadsheet. Update GHC handler to pass in forPpm parameter to ensure filteriing on ppm_closeout col. --- migrations/app/migrations_manifest.txt | 1 + ...ppm_closeout_transportation_offices.up.sql | 20 +++++++++++++++++++ pkg/handlers/ghcapi/tranportation_offices.go | 5 ++++- 3 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 migrations/app/schema/20240917132411_update_provides_ppm_closeout_transportation_offices.up.sql diff --git a/migrations/app/migrations_manifest.txt b/migrations/app/migrations_manifest.txt index ab0cc2e13f5..44e34a7170c 100644 --- a/migrations/app/migrations_manifest.txt +++ b/migrations/app/migrations_manifest.txt @@ -993,3 +993,4 @@ 20240819164156_update_pws_violations_pt3.up.sql 20240820125856_allow_pptas_migration.up.sql 20240820151043_add_gsr_role.up.sql +20240917132411_update_provides_ppm_closeout_transportation_offices.up.sql diff --git a/migrations/app/schema/20240917132411_update_provides_ppm_closeout_transportation_offices.up.sql b/migrations/app/schema/20240917132411_update_provides_ppm_closeout_transportation_offices.up.sql new file mode 100644 index 00000000000..07cc0505623 --- /dev/null +++ b/migrations/app/schema/20240917132411_update_provides_ppm_closeout_transportation_offices.up.sql @@ -0,0 +1,20 @@ +-- PPPO Fort Belvoir - USA +UPDATE transportation_offices SET provides_ppm_closeout = true WHERE id = 'a877a317-be5f-482b-a126-c91a34be9290'; + +-- Personal Property Activity HQ (PPA HQ) - USAF +UPDATE transportation_offices SET provides_ppm_closeout = false WHERE id = 'ebdacf64-353a-4014-91db-0d04d88320f0'; + +-- PPPO Base Miami - USCG +UPDATE transportation_offices SET provides_ppm_closeout = true WHERE id = '1b3e7496-efa7-48aa-ba22-b630d6fea98b'; + +-- JPPSO - North West (JEAT) - USA +UPDATE transportation_offices SET provides_ppm_closeout = false WHERE id = '5a3388e1-6d46-4639-ac8f-a8937dc26938'; + +-- PPPO NSWC Panama City Division - USN +UPDATE transportation_offices SET provides_ppm_closeout = true WHERE id = '57cf1e81-8113-4a52-bc50-3cb8902c2efd'; + +-- JPPSO - Mid Atlantic (BGAC) - USA +UPDATE transportation_offices SET provides_ppm_closeout = false WHERE id = '8e25ccc1-7891-4146-a9d0-cd0d48b59a50'; + +-- JPPSO - South West (LKNQ) - USN +UPDATE transportation_offices SET provides_ppm_closeout = false WHERE id = '27002d34-e9ea-4ef5-a086-f23d07c4088c'; \ No newline at end of file diff --git a/pkg/handlers/ghcapi/tranportation_offices.go b/pkg/handlers/ghcapi/tranportation_offices.go index d60f4b0a70a..405580923bb 100644 --- a/pkg/handlers/ghcapi/tranportation_offices.go +++ b/pkg/handlers/ghcapi/tranportation_offices.go @@ -20,7 +20,10 @@ func (h GetTransportationOfficesHandler) Handle(params transportationofficeop.Ge return h.AuditableAppContextFromRequestWithErrors(params.HTTPRequest, func(appCtx appcontext.AppContext) (middleware.Responder, error) { - transportationOffices, err := h.TransportationOfficesFetcher.GetTransportationOffices(appCtx, params.Search, false) + // B-21022: forPpm param is set true. This is used by PPM closeout widget. Need to ensure certain offices are included/excluded + // if location has ppm closedout enabled. + transportationOffices, err := h.TransportationOfficesFetcher.GetTransportationOffices(appCtx, params.Search, true) + if err != nil { appCtx.Logger().Error("Error searching for Transportation Offices: ", zap.Error(err)) return transportationofficeop.NewGetTransportationOfficesInternalServerError(), err From a743456c28f852f1f169e04290f02c3938441d6d Mon Sep 17 00:00:00 2001 From: deandreJones Date: Tue, 17 Sep 2024 12:24:10 -0500 Subject: [PATCH 52/68] test out accessibility tests in circle --- playwright/tests/utils/waitForPage.js | 2 +- scripts/run-e2e-test | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/playwright/tests/utils/waitForPage.js b/playwright/tests/utils/waitForPage.js index 7a79f419f38..114a1cfd125 100644 --- a/playwright/tests/utils/waitForPage.js +++ b/playwright/tests/utils/waitForPage.js @@ -27,7 +27,7 @@ export class WaitForPage { detailedReport: true, }, // skip failures - true, + false, 'default', ); } diff --git a/scripts/run-e2e-test b/scripts/run-e2e-test index 92cce285789..e0272b245c0 100755 --- a/scripts/run-e2e-test +++ b/scripts/run-e2e-test @@ -31,4 +31,4 @@ done trap cleanup SIGINT trap cleanup exit -yarn playwright test "$@" +A11Y_AUDIT=true yarn playwright test "$@" From 7779205587ca7e703277a45e16e6e5c262b32f96 Mon Sep 17 00:00:00 2001 From: deandreJones Date: Tue, 17 Sep 2024 13:06:08 -0500 Subject: [PATCH 53/68] try again --- package.json | 2 +- playwright/tests/utils/waitForPage.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 8670451fad2..117476ac088 100644 --- a/package.json +++ b/package.json @@ -112,7 +112,7 @@ "test": "react-app-rewired test --env=jsdom", "test:debug": "react-app-rewired --inspect-brk test --runInBand --env=jsdom", "test:coverage": "react-app-rewired test --coverage --reporters=default --reporters=jest-junit --env=jsdom --coverageDirectory=coverage --maxWorkers=4 --watchAll=false", - "test:e2e": "playwright test --trace=on", + "test:e2e": "A11Y_AUDIT=true playwright test --trace=on", "prettier": "prettier --write --loglevel warn 'src/**/*.{js,jsx}' playwright/tests/**/*.js'", "prettier-ci": "prettier --check 'src/**/*.{js,jsx}' playwright/tests/**/*.js", "lint": "eslint --ext .js,.jsx --max-warnings=0 src playwright/tests", diff --git a/playwright/tests/utils/waitForPage.js b/playwright/tests/utils/waitForPage.js index 114a1cfd125..72316848dc0 100644 --- a/playwright/tests/utils/waitForPage.js +++ b/playwright/tests/utils/waitForPage.js @@ -28,7 +28,7 @@ export class WaitForPage { }, // skip failures false, - 'default', + 'html', ); } } From 547326e7d352205672b0c4fb096c303c92367ad1 Mon Sep 17 00:00:00 2001 From: deandreJones Date: Tue, 17 Sep 2024 13:26:10 -0500 Subject: [PATCH 54/68] detail report --- playwright/tests/utils/waitForPage.js | 1 + 1 file changed, 1 insertion(+) diff --git a/playwright/tests/utils/waitForPage.js b/playwright/tests/utils/waitForPage.js index 72316848dc0..a4217aa9688 100644 --- a/playwright/tests/utils/waitForPage.js +++ b/playwright/tests/utils/waitForPage.js @@ -25,6 +25,7 @@ export class WaitForPage { undefined, { detailedReport: true, + detailedReportOptions: { html: true }, }, // skip failures false, From aef165faac2a88e35d2e26fb552061c4f2a6be52 Mon Sep 17 00:00:00 2001 From: deandreJones Date: Tue, 17 Sep 2024 14:06:18 -0500 Subject: [PATCH 55/68] run always --- playwright/tests/utils/waitForPage.js | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/playwright/tests/utils/waitForPage.js b/playwright/tests/utils/waitForPage.js index a4217aa9688..08f582ce3f9 100644 --- a/playwright/tests/utils/waitForPage.js +++ b/playwright/tests/utils/waitForPage.js @@ -19,19 +19,17 @@ export class WaitForPage { */ async runAccessibilityAudit() { - if (process.env.A11Y_AUDIT) { - await checkA11y( - this.page, - undefined, - { - detailedReport: true, - detailedReportOptions: { html: true }, - }, - // skip failures - false, - 'html', - ); - } + await checkA11y( + this.page, + undefined, + { + detailedReport: true, + detailedReportOptions: { html: true }, + }, + // skip failures + false, + 'html', + ); } /** From b11e81711c041ff4286c0d18f14bed07f54757a7 Mon Sep 17 00:00:00 2001 From: Michael Inthavongsay Date: Wed, 18 Sep 2024 14:12:29 +0000 Subject: [PATCH 56/68] update swagger description and summary for API to align with change related to making forPPM param to true. --- pkg/gen/ghcapi/embedded_spec.go | 8 ++++---- .../transportation_office/get_transportation_offices.go | 4 ++-- swagger-def/ghc.yaml | 4 ++-- swagger/ghc.yaml | 8 ++++++-- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/pkg/gen/ghcapi/embedded_spec.go b/pkg/gen/ghcapi/embedded_spec.go index 7388e1b61d5..a0a15f445da 100644 --- a/pkg/gen/ghcapi/embedded_spec.go +++ b/pkg/gen/ghcapi/embedded_spec.go @@ -5607,14 +5607,14 @@ func init() { }, "/transportation-offices": { "get": { - "description": "Returns the transportation offices matching the search query", + "description": "Returns the transportation offices matching the search query that is enabled for PPM closeout", "produces": [ "application/json" ], "tags": [ "transportationOffice" ], - "summary": "Returns the transportation offices matching the search query", + "summary": "Returns the transportation offices matching the search query that is enabled for PPM closeout", "operationId": "getTransportationOffices", "parameters": [ { @@ -20925,14 +20925,14 @@ func init() { }, "/transportation-offices": { "get": { - "description": "Returns the transportation offices matching the search query", + "description": "Returns the transportation offices matching the search query that is enabled for PPM closeout", "produces": [ "application/json" ], "tags": [ "transportationOffice" ], - "summary": "Returns the transportation offices matching the search query", + "summary": "Returns the transportation offices matching the search query that is enabled for PPM closeout", "operationId": "getTransportationOffices", "parameters": [ { diff --git a/pkg/gen/ghcapi/ghcoperations/transportation_office/get_transportation_offices.go b/pkg/gen/ghcapi/ghcoperations/transportation_office/get_transportation_offices.go index 13c260a5655..fd19356220f 100644 --- a/pkg/gen/ghcapi/ghcoperations/transportation_office/get_transportation_offices.go +++ b/pkg/gen/ghcapi/ghcoperations/transportation_office/get_transportation_offices.go @@ -32,9 +32,9 @@ func NewGetTransportationOffices(ctx *middleware.Context, handler GetTransportat /* GetTransportationOffices swagger:route GET /transportation-offices transportationOffice getTransportationOffices -# Returns the transportation offices matching the search query +# Returns the transportation offices matching the search query that is enabled for PPM closeout -Returns the transportation offices matching the search query +Returns the transportation offices matching the search query that is enabled for PPM closeout */ type GetTransportationOffices struct { Context *middleware.Context diff --git a/swagger-def/ghc.yaml b/swagger-def/ghc.yaml index 0bb252d0846..36ed6d93795 100644 --- a/swagger-def/ghc.yaml +++ b/swagger-def/ghc.yaml @@ -3756,8 +3756,8 @@ paths: get: produces: - application/json - summary: Returns the transportation offices matching the search query - description: Returns the transportation offices matching the search query + summary: Returns the transportation offices matching the search query that is enabled for PPM closeout + description: Returns the transportation offices matching the search query that is enabled for PPM closeout operationId: getTransportationOffices tags: - transportationOffice diff --git a/swagger/ghc.yaml b/swagger/ghc.yaml index 62feecb4062..73987c93126 100644 --- a/swagger/ghc.yaml +++ b/swagger/ghc.yaml @@ -3931,8 +3931,12 @@ paths: get: produces: - application/json - summary: Returns the transportation offices matching the search query - description: Returns the transportation offices matching the search query + summary: >- + Returns the transportation offices matching the search query that is + enabled for PPM closeout + description: >- + Returns the transportation offices matching the search query that is + enabled for PPM closeout operationId: getTransportationOffices tags: - transportationOffice From b1cbf436eb7855037174026529ac017b83ea3082 Mon Sep 17 00:00:00 2001 From: Cory Kleinjan Date: Wed, 18 Sep 2024 19:53:29 +0000 Subject: [PATCH 57/68] Adding where not exists to inserts for lockedpricecents service params --- ...ng_locked_price_cents_service_param.up.sql | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/migrations/app/schema/20240822180409_adding_locked_price_cents_service_param.up.sql b/migrations/app/schema/20240822180409_adding_locked_price_cents_service_param.up.sql index 648d8be964f..baa1337fac5 100644 --- a/migrations/app/schema/20240822180409_adding_locked_price_cents_service_param.up.sql +++ b/migrations/app/schema/20240822180409_adding_locked_price_cents_service_param.up.sql @@ -1,14 +1,26 @@ INSERT INTO service_item_param_keys (id,key,description,type,origin,created_at,updated_at) -VALUES -('7ec5cf87-a446-4dd6-89d3-50bbc0d2c206','LockedPriceCents', 'Locked price when move was made available to prime', 'INTEGER', 'SYSTEM', now(), now()); +SELECT '7ec5cf87-a446-4dd6-89d3-50bbc0d2c206','LockedPriceCents', 'Locked price when move was made available to prime', 'INTEGER', 'SYSTEM', now(), now() +WHERE NOT EXISTS + (SELECT 1 + FROM service_item_param_keys s + WHERE s.id = '7ec5cf87-a446-4dd6-89d3-50bbc0d2c206' + ); INSERT INTO service_params (id,service_id,service_item_param_key_id,created_at,updated_at,is_optional) -VALUES -('22056106-bbde-4ae7-b5bd-e7d2f103ab7d',(SELECT id FROM re_services WHERE code='MS'),(SELECT id FROM service_item_param_keys where key='LockedPriceCents'), now(), now(), 'false'); +SELECT '22056106-bbde-4ae7-b5bd-e7d2f103ab7d',(SELECT id FROM re_services WHERE code='MS'),(SELECT id FROM service_item_param_keys where key='LockedPriceCents'), now(), now(), 'false' +WHERE NOT EXISTS + ( SELECT 1 + FROM service_params s + WHERE s.id = '22056106-bbde-4ae7-b5bd-e7d2f103ab7d' + ); INSERT INTO service_params (id,service_id,service_item_param_key_id,created_at,updated_at,is_optional) -VALUES -('86f8c20c-071e-4715-b0c1-608f540b3be3',(SELECT id FROM re_services WHERE code='CS'),(SELECT id FROM service_item_param_keys where key='LockedPriceCents'), now(), now(), 'false'); \ No newline at end of file +SELECT '86f8c20c-071e-4715-b0c1-608f540b3be3',(SELECT id FROM re_services WHERE code='CS'),(SELECT id FROM service_item_param_keys where key='LockedPriceCents'), now(), now(), 'false' +WHERE NOT EXISTS + ( SELECT 1 + FROM service_params s + WHERE s.id = '86f8c20c-071e-4715-b0c1-608f540b3be3' + ); \ No newline at end of file From 9ae3f966f5abd7bfc6bb16c0585623d305e735bb Mon Sep 17 00:00:00 2001 From: cameroncaci Date: Thu, 19 Sep 2024 19:24:23 +0000 Subject: [PATCH 58/68] catch up main to int test --- .../Office/CustomerOnboarding/CreateCustomerForm.test.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx index ef41bcb0532..2c08c2978f8 100644 --- a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx +++ b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx @@ -236,7 +236,8 @@ describe('CreateCustomerForm', () => { await user.type(getByLabelText('Best contact phone'), fakePayload.telephone); await user.type(getByLabelText('Personal email'), fakePayload.personal_email); - + await userEvent.type(getByTestId('edipiInput'), fakePayload.edipi); + await user.type(getByTestId('res-add-street1'), fakePayload.residential_address.streetAddress1); await user.type(getByTestId('res-add-city'), fakePayload.residential_address.city); await user.selectOptions(getByTestId('res-add-state'), [fakePayload.residential_address.state]); From 51c6f9f68fdfee5b0fa048a33998a4eb8741386b Mon Sep 17 00:00:00 2001 From: cameroncaci Date: Thu, 19 Sep 2024 19:25:03 +0000 Subject: [PATCH 59/68] server gen --- pkg/gen/ordersapi/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/gen/ordersapi/server.go b/pkg/gen/ordersapi/server.go index 33bfa754be5..c8204f5cfaf 100644 --- a/pkg/gen/ordersapi/server.go +++ b/pkg/gen/ordersapi/server.go @@ -246,7 +246,7 @@ func (s *Server) Serve() (err error) { PreferServerCipherSuites: true, // Only use curves which have assembly implementations // https://github.com/golang/go/tree/master/src/crypto/elliptic - CurvePreferences: []tls.CurveID{tls.CurveP256}, + CurvePreferences: []tls.CurveID{tls.CurveP256, tls.CurveP384, tls.CurveP521}, // Use modern tls mode https://wiki.mozilla.org/Security/Server_Side_TLS#Modern_compatibility NextProtos: []string{"h2", "http/1.1"}, // https://www.owasp.org/index.php/Transport_Layer_Protection_Cheat_Sheet#Rule_-_Only_Support_Strong_Protocols From 60344bc2cae73d235071995150d6e4bbf850330f Mon Sep 17 00:00:00 2001 From: cameroncaci Date: Thu, 19 Sep 2024 19:38:30 +0000 Subject: [PATCH 60/68] prettier --- src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx index 2c08c2978f8..841f7eb7c5e 100644 --- a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx +++ b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx @@ -237,7 +237,7 @@ describe('CreateCustomerForm', () => { await user.type(getByLabelText('Best contact phone'), fakePayload.telephone); await user.type(getByLabelText('Personal email'), fakePayload.personal_email); await userEvent.type(getByTestId('edipiInput'), fakePayload.edipi); - + await user.type(getByTestId('res-add-street1'), fakePayload.residential_address.streetAddress1); await user.type(getByTestId('res-add-city'), fakePayload.residential_address.city); await user.selectOptions(getByTestId('res-add-state'), [fakePayload.residential_address.state]); From 866a10949bb7ab97368a822e1d1c9f119e644354 Mon Sep 17 00:00:00 2001 From: deandreJones Date: Thu, 19 Sep 2024 15:37:36 -0500 Subject: [PATCH 61/68] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b80b7b38a2c..b861e8a17a3 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![GoDoc](https://godoc.org/github.com/transcom/mymove?status.svg)](https://godoc.org/github.com/transcom/mymove) -This repository contains the application source code for the Personal Property Prototype, a possible next generation version of the Defense Personal Property System (DPS). DPS is an online system managed by the U.S. [Department of Defense](https://www.defense.gov/) (DoD) [Transportation Command](http://www.ustranscom.mil/) (USTRANSCOM) and is used by service members and their families to manage household goods moves. +This repository contains the application source code for the MilMove application,the next generation version of the Defense Personal Property System (DPS). DPS is an online system managed by the U.S. [Department of Defense](https://www.defense.gov/) (DoD) [Transportation Command](http://www.ustranscom.mil/) (USTRANSCOM) and is used by service members and their families to manage household goods moves. This prototype was built by a [Defense Digital Service](https://www.dds.mil/) team in support of USTRANSCOM's mission. From 33c7b87eacea9d947d408fbe3400802c766a6aa0 Mon Sep 17 00:00:00 2001 From: deandreJones Date: Thu, 19 Sep 2024 16:01:46 -0500 Subject: [PATCH 62/68] not anymore --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index b861e8a17a3..16edc39f7d2 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,6 @@ This repository contains the application source code for the MilMove application,the next generation version of the Defense Personal Property System (DPS). DPS is an online system managed by the U.S. [Department of Defense](https://www.defense.gov/) (DoD) [Transportation Command](http://www.ustranscom.mil/) (USTRANSCOM) and is used by service members and their families to manage household goods moves. -This prototype was built by a [Defense Digital Service](https://www.dds.mil/) team in support of USTRANSCOM's mission. ## License Information From c046eb1b0461f00cbbc9c513f372465d74f0548e Mon Sep 17 00:00:00 2001 From: loganwc Date: Fri, 20 Sep 2024 14:05:28 +0000 Subject: [PATCH 63/68] moved code 4 lines down --- pkg/handlers/ghcapi/api.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/handlers/ghcapi/api.go b/pkg/handlers/ghcapi/api.go index 81db579c4e8..eb3bafeceb6 100644 --- a/pkg/handlers/ghcapi/api.go +++ b/pkg/handlers/ghcapi/api.go @@ -666,15 +666,15 @@ func NewGhcAPIHandler(handlerConfig handlers.HandlerConfig) *ghcops.MymoveAPI { order.NewOrderUpdater(moveRouter), } + ghcAPI.MoveMoveCancelerHandler = MoveCancelerHandler{ + handlerConfig, + move.NewMoveCanceler(), + } + paymentRequestBulkDownloadCreator := paymentrequest.NewPaymentRequestBulkDownloadCreator(pdfGenerator) ghcAPI.PaymentRequestsBulkDownloadHandler = PaymentRequestBulkDownloadHandler{ handlerConfig, paymentRequestBulkDownloadCreator, - } - - ghcAPI.MoveMoveCancelerHandler = MoveCancelerHandler{ - handlerConfig, - move.NewMoveCanceler(), } dateSelectionChecker := dateservice.NewDateSelectionChecker() From 7bd9ac646579b37a7153f243961b55d70b1ad7e4 Mon Sep 17 00:00:00 2001 From: AaronW Date: Fri, 20 Sep 2024 14:07:26 +0000 Subject: [PATCH 64/68] commits from PR 13591 --- src/components/Table/SearchResultsTable.jsx | 9 +- src/constants/queues.js | 9 - src/pages/Office/MoveQueue/MoveQueue.jsx | 2 +- src/pages/Office/MoveQueue/MoveQueue.test.jsx | 220 ++++++++++++------ ...icesCounselingEditShipmentDetails.test.jsx | 2 +- .../ServicesCounselingQueue.jsx | 6 +- 6 files changed, 157 insertions(+), 91 deletions(-) diff --git a/src/components/Table/SearchResultsTable.jsx b/src/components/Table/SearchResultsTable.jsx index 78a0daf5180..f36fc385faa 100644 --- a/src/components/Table/SearchResultsTable.jsx +++ b/src/components/Table/SearchResultsTable.jsx @@ -13,12 +13,7 @@ import DateSelectFilter from 'components/Table/Filters/DateSelectFilter'; import LoadingPlaceholder from 'shared/LoadingPlaceholder'; import SomethingWentWrong from 'shared/SomethingWentWrong'; import TextBoxFilter from 'components/Table/Filters/TextBoxFilter'; -import { - BRANCH_OPTIONS_WITH_MARINE_CORPS, - MOVE_STATUS_LABELS, - SEARCH_QUEUE_STATUS_FILTER_OPTIONS, - SortShape, -} from 'constants/queues'; +import { BRANCH_OPTIONS, MOVE_STATUS_LABELS, SEARCH_QUEUE_STATUS_FILTER_OPTIONS, SortShape } from 'constants/queues'; import { DATE_FORMAT_STRING } from 'shared/constants'; import { formatDateFromIso, serviceMemberAgencyLabel } from 'utils/formatters'; import MultiSelectCheckBoxFilter from 'components/Table/Filters/MultiSelectCheckBoxFilter'; @@ -107,7 +102,7 @@ const moveSearchColumns = (moveLockFlag, handleEditProfileClick) => [ isFilterable: true, Filter: (props) => ( // eslint-disable-next-line react/jsx-props-no-spreading - + ), }, ), diff --git a/src/constants/queues.js b/src/constants/queues.js index 87d23e1ed04..17a6e9d4066 100644 --- a/src/constants/queues.js +++ b/src/constants/queues.js @@ -52,15 +52,6 @@ export const BRANCH_OPTIONS = [ { value: 'AIR_FORCE', label: 'Air Force' }, { value: 'COAST_GUARD', label: 'Coast Guard' }, { value: 'SPACE_FORCE', label: 'Space Force' }, -]; - -export const BRANCH_OPTIONS_WITH_MARINE_CORPS = [ - { value: '', label: 'All' }, - { value: 'ARMY', label: 'Army' }, - { value: 'NAVY', label: 'Navy' }, - { value: 'AIR_FORCE', label: 'Air Force' }, - { value: 'COAST_GUARD', label: 'Coast Guard' }, - { value: 'SPACE_FORCE', label: 'Space Force' }, { value: 'MARINES', label: 'Marine Corps' }, ]; diff --git a/src/pages/Office/MoveQueue/MoveQueue.jsx b/src/pages/Office/MoveQueue/MoveQueue.jsx index d485648b3fe..acfceb62439 100644 --- a/src/pages/Office/MoveQueue/MoveQueue.jsx +++ b/src/pages/Office/MoveQueue/MoveQueue.jsx @@ -10,7 +10,7 @@ import { getMovesQueue } from 'services/ghcApi'; import { formatDateFromIso, serviceMemberAgencyLabel } from 'utils/formatters'; import MultiSelectCheckBoxFilter from 'components/Table/Filters/MultiSelectCheckBoxFilter'; import SelectFilter from 'components/Table/Filters/SelectFilter'; -import { BRANCH_OPTIONS, MOVE_STATUS_OPTIONS, GBLOC, MOVE_STATUS_LABELS } from 'constants/queues'; +import { MOVE_STATUS_OPTIONS, GBLOC, MOVE_STATUS_LABELS, BRANCH_OPTIONS } from 'constants/queues'; import TableQueue from 'components/Table/TableQueue'; import LoadingPlaceholder from 'shared/LoadingPlaceholder'; import SomethingWentWrong from 'shared/SomethingWentWrong'; diff --git a/src/pages/Office/MoveQueue/MoveQueue.test.jsx b/src/pages/Office/MoveQueue/MoveQueue.test.jsx index f62a6b75c05..b8017b59fdb 100644 --- a/src/pages/Office/MoveQueue/MoveQueue.test.jsx +++ b/src/pages/Office/MoveQueue/MoveQueue.test.jsx @@ -7,7 +7,7 @@ import { render, screen, waitFor } from '@testing-library/react'; import MoveQueue from './MoveQueue'; import { MockProviders } from 'testUtils'; -import { MOVE_STATUS_OPTIONS } from 'constants/queues'; +import { MOVE_STATUS_OPTIONS, BRANCH_OPTIONS } from 'constants/queues'; import { generalRoutes, tooRoutes } from 'constants/routes'; import { isBooleanFlagEnabled } from 'utils/featureFlags'; @@ -23,6 +23,71 @@ jest.mock('utils/featureFlags', () => ({ isBooleanFlagEnabled: jest.fn().mockImplementation(() => Promise.resolve()), })); +const moveData = [ + { + id: 'move1', + customer: { + agency: 'AIR_FORCE', + first_name: 'test first', + last_name: 'test last', + dodID: '555555555', + }, + locator: 'AB5P', + departmentIndicator: 'ARMY', + shipmentsCount: 2, + status: 'SUBMITTED', + originDutyLocation: { + name: 'Area 51', + }, + originGBLOC: 'EEEE', + requestedMoveDate: '2023-02-10', + appearedInTooAt: '2023-02-10T00:00:00.000Z', + lockExpiresAt: '2099-02-10T00:00:00.000Z', + lockedByOfficeUserID: '2744435d-7ba8-4cc5-bae5-f302c72c966e', + }, + { + id: 'move2', + customer: { + agency: 'COAST_GUARD', + first_name: 'test another first', + last_name: 'test another last', + dodID: '4444444444', + emplid: '4589652', + }, + locator: 'T12A', + departmentIndicator: 'COAST_GUARD', + shipmentsCount: 1, + status: 'APPROVED', + originDutyLocation: { + name: 'Los Alamos', + }, + originGBLOC: 'EEEE', + requestedMoveDate: '2023-02-12', + appearedInTooAt: '2023-02-12T00:00:00.000Z', + }, + { + id: 'move3', + customer: { + agency: 'Marine Corps', + first_name: 'will', + last_name: 'robinson', + dodID: '6666666666', + }, + locator: 'PREP', + departmentIndicator: 'MARINES', + shipmentsCount: 1, + status: 'SUBMITTED', + originDutyLocation: { + name: 'Area 52', + }, + originGBLOC: 'EEEE', + requestedMoveDate: '2023-03-12', + appearedInTooAt: '2023-03-12T00:00:00.000Z', + lockExpiresAt: '2099-03-12T00:00:00.000Z', + lockedByOfficeUserID: '2744435d-7ba8-4cc5-bae5-f302c72c966e', + }, +]; + jest.mock('hooks/queries', () => ({ useUserQueries: () => { return { @@ -38,50 +103,8 @@ jest.mock('hooks/queries', () => ({ isLoading: false, isError: false, queueResult: { - totalCount: 2, - data: [ - { - id: 'move1', - customer: { - agency: 'AIR_FORCE', - first_name: 'test first', - last_name: 'test last', - dodID: '555555555', - }, - locator: 'AB5P', - departmentIndicator: 'ARMY', - shipmentsCount: 2, - status: 'SUBMITTED', - originDutyLocation: { - name: 'Area 51', - }, - originGBLOC: 'EEEE', - requestedMoveDate: '2023-02-10', - appearedInTooAt: '2023-02-10T00:00:00.000Z', - lockExpiresAt: '2099-02-10T00:00:00.000Z', - lockedByOfficeUserID: '2744435d-7ba8-4cc5-bae5-f302c72c966e', - }, - { - id: 'move2', - customer: { - agency: 'COAST_GUARD', - first_name: 'test another first', - last_name: 'test another last', - dodID: '4444444444', - emplid: '4589652', - }, - locator: 'T12A', - departmentIndicator: 'COAST_GUARD', - shipmentsCount: 1, - status: 'APPROVED', - originDutyLocation: { - name: 'Los Alamos', - }, - originGBLOC: 'EEEE', - requestedMoveDate: '2023-02-12', - appearedInTooAt: '2023-02-12T00:00:00.000Z', - }, - ], + totalCount: 3, + data: moveData, }, }; }, @@ -103,7 +126,7 @@ describe('MoveQueue', () => { }); it('should render the h1', () => { - expect(GetMountedComponent(tooRoutes.MOVE_QUEUE).find('h1').text()).toBe('All moves (2)'); + expect(GetMountedComponent(tooRoutes.MOVE_QUEUE).find('h1').text()).toBe('All moves (3)'); }); it('should render the table', () => { @@ -111,32 +134,89 @@ describe('MoveQueue', () => { }); it('should format the column data', () => { + let currentIndex = 0; + let currentMove; const moves = GetMountedComponent(tooRoutes.MOVE_QUEUE).find('tbody tr'); - const firstMove = moves.at(0); - expect(firstMove.find({ 'data-testid': 'lastName-0' }).text()).toBe('test last, test first'); - expect(firstMove.find({ 'data-testid': 'dodID-0' }).text()).toBe('555555555'); - expect(firstMove.find({ 'data-testid': 'status-0' }).text()).toBe('New move'); - expect(firstMove.find({ 'data-testid': 'locator-0' }).text()).toBe('AB5P'); - expect(firstMove.find({ 'data-testid': 'branch-0' }).text()).toBe('Air Force'); - expect(firstMove.find({ 'data-testid': 'shipmentsCount-0' }).text()).toBe('2'); - expect(firstMove.find({ 'data-testid': 'originDutyLocation-0' }).text()).toBe('Area 51'); - expect(firstMove.find({ 'data-testid': 'originGBLOC-0' }).text()).toBe('EEEE'); - expect(firstMove.find({ 'data-testid': 'requestedMoveDate-0' }).text()).toBe('10 Feb 2023'); - expect(firstMove.find({ 'data-testid': 'appearedInTooAt-0' }).text()).toBe('10 Feb 2023'); + currentMove = moves.at(currentIndex); + expect(currentMove.find({ 'data-testid': `lastName-${currentIndex}` }).text()).toBe( + `${moveData[currentIndex].customer.last_name}, ${moveData[currentIndex].customer.first_name}`, + ); + expect(currentMove.find({ 'data-testid': `dodID-${currentIndex}` }).text()).toBe( + moveData[currentIndex].customer.dodID, + ); + expect(currentMove.find({ 'data-testid': `status-${currentIndex}` }).text()).toBe('New move'); + expect(currentMove.find({ 'data-testid': `locator-${currentIndex}` }).text()).toBe(moveData[currentIndex].locator); + expect(currentMove.find({ 'data-testid': `branch-${currentIndex}` }).text()).toBe( + BRANCH_OPTIONS.find((value) => value.value === moveData[currentIndex].customer.agency).label, + ); + expect(currentMove.find({ 'data-testid': `shipmentsCount-${currentIndex}` }).text()).toBe( + moveData[currentIndex].shipmentsCount.toString(), + ); + expect(currentMove.find({ 'data-testid': `originDutyLocation-${currentIndex}` }).text()).toBe( + moveData[currentIndex].originDutyLocation.name, + ); + expect(currentMove.find({ 'data-testid': `originGBLOC-${currentIndex}` }).text()).toBe( + moveData[currentIndex].originGBLOC, + ); + expect(currentMove.find({ 'data-testid': `requestedMoveDate-${currentIndex}` }).text()).toBe('10 Feb 2023'); + expect(currentMove.find({ 'data-testid': `appearedInTooAt-${currentIndex}` }).text()).toBe('10 Feb 2023'); + + currentIndex += 1; + currentMove = moves.at(currentIndex); + expect(currentMove.find({ 'data-testid': `lastName-${currentIndex}` }).text()).toBe( + 'test another last, test another first', + ); + expect(currentMove.find({ 'data-testid': `lastName-${currentIndex}` }).text()).toBe( + `${moveData[currentIndex].customer.last_name}, ${moveData[currentIndex].customer.first_name}`, + ); + expect(currentMove.find({ 'data-testid': `dodID-${currentIndex}` }).text()).toBe( + moveData[currentIndex].customer.dodID, + ); + expect(currentMove.find({ 'data-testid': `emplid-${currentIndex}` }).text()).toBe( + moveData[currentIndex].customer.emplid, + ); + expect(currentMove.find({ 'data-testid': `status-${currentIndex}` }).text()).toBe('Move approved'); + expect(currentMove.find({ 'data-testid': `locator-${currentIndex}` }).text()).toBe(moveData[currentIndex].locator); + expect(currentMove.find({ 'data-testid': `branch-${currentIndex}` }).text()).toBe( + BRANCH_OPTIONS.find((value) => value.value === moveData[currentIndex].customer.agency).label, + ); + expect(currentMove.find({ 'data-testid': `shipmentsCount-${currentIndex}` }).text()).toBe( + moveData[currentIndex].shipmentsCount.toString(), + ); + expect(currentMove.find({ 'data-testid': `originDutyLocation-${currentIndex}` }).text()).toBe( + moveData[currentIndex].originDutyLocation.name, + ); + expect(currentMove.find({ 'data-testid': `originGBLOC-${currentIndex}` }).text()).toBe( + moveData[currentIndex].originGBLOC, + ); + expect(currentMove.find({ 'data-testid': `requestedMoveDate-${currentIndex}` }).text()).toBe('12 Feb 2023'); + expect(currentMove.find({ 'data-testid': `appearedInTooAt-${currentIndex}` }).text()).toBe('12 Feb 2023'); - const secondMove = moves.at(1); - expect(secondMove.find({ 'data-testid': 'lastName-1' }).text()).toBe('test another last, test another first'); - expect(secondMove.find({ 'data-testid': 'dodID-1' }).text()).toBe('4444444444'); - expect(secondMove.find({ 'data-testid': 'emplid-1' }).text()).toBe('4589652'); - expect(secondMove.find({ 'data-testid': 'status-1' }).text()).toBe('Move approved'); - expect(secondMove.find({ 'data-testid': 'locator-1' }).text()).toBe('T12A'); - expect(secondMove.find({ 'data-testid': 'branch-1' }).text()).toBe('Coast Guard'); - expect(secondMove.find({ 'data-testid': 'shipmentsCount-1' }).text()).toBe('1'); - expect(secondMove.find({ 'data-testid': 'originDutyLocation-1' }).text()).toBe('Los Alamos'); - expect(secondMove.find({ 'data-testid': 'originGBLOC-1' }).text()).toBe('EEEE'); - expect(secondMove.find({ 'data-testid': 'requestedMoveDate-1' }).text()).toBe('12 Feb 2023'); - expect(secondMove.find({ 'data-testid': 'appearedInTooAt-1' }).text()).toBe('12 Feb 2023'); + currentIndex += 1; + currentMove = moves.at(currentIndex); + expect(currentMove.find({ 'data-testid': `lastName-${currentIndex}` }).text()).toBe( + `${moveData[currentIndex].customer.last_name}, ${moveData[currentIndex].customer.first_name}`, + ); + expect(currentMove.find({ 'data-testid': `dodID-${currentIndex}` }).text()).toBe( + moveData[currentIndex].customer.dodID, + ); + expect(currentMove.find({ 'data-testid': `status-${currentIndex}` }).text()).toBe('New move'); + expect(currentMove.find({ 'data-testid': `locator-${currentIndex}` }).text()).toBe(moveData[currentIndex].locator); + expect(currentMove.find({ 'data-testid': `branch-${currentIndex}` }).text()).toBe( + moveData[currentIndex].customer.agency.toString(), + ); + expect(currentMove.find({ 'data-testid': `shipmentsCount-${currentIndex}` }).text()).toBe( + moveData[currentIndex].shipmentsCount.toString(), + ); + expect(currentMove.find({ 'data-testid': `originDutyLocation-${currentIndex}` }).text()).toBe( + moveData[currentIndex].originDutyLocation.name, + ); + expect(currentMove.find({ 'data-testid': `originGBLOC-${currentIndex}` }).text()).toBe( + moveData[currentIndex].originGBLOC, + ); + expect(currentMove.find({ 'data-testid': `requestedMoveDate-${currentIndex}` }).text()).toBe('12 Mar 2023'); + expect(currentMove.find({ 'data-testid': `appearedInTooAt-${currentIndex}` }).text()).toBe('12 Mar 2023'); }); it('should render the pagination component', () => { @@ -249,7 +329,7 @@ describe('MoveQueue', () => { , ); await waitFor(() => { - const lockIcon = screen.queryByTestId('lock-icon'); + const lockIcon = screen.queryAllByTestId('lock-icon')[0]; expect(lockIcon).toBeInTheDocument(); }); }); diff --git a/src/pages/Office/ServicesCounselingEditShipmentDetails/ServicesCounselingEditShipmentDetails.test.jsx b/src/pages/Office/ServicesCounselingEditShipmentDetails/ServicesCounselingEditShipmentDetails.test.jsx index de1ac1b0ead..26b06a51d85 100644 --- a/src/pages/Office/ServicesCounselingEditShipmentDetails/ServicesCounselingEditShipmentDetails.test.jsx +++ b/src/pages/Office/ServicesCounselingEditShipmentDetails/ServicesCounselingEditShipmentDetails.test.jsx @@ -328,7 +328,7 @@ describe('ServicesCounselingEditShipmentDetails component', () => { expect( screen.getByText('Something went wrong, and your changes were not saved. Please try again.'), ).toBeVisible(); - }); + }, 10000); }); it('routes to the move details page when the cancel button is clicked', async () => { diff --git a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx index 398bc9fe860..dc2e4421932 100644 --- a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx +++ b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx @@ -10,7 +10,7 @@ import SelectFilter from 'components/Table/Filters/SelectFilter'; import DateSelectFilter from 'components/Table/Filters/DateSelectFilter'; import TableQueue from 'components/Table/TableQueue'; import { - BRANCH_OPTIONS_WITH_MARINE_CORPS, + BRANCH_OPTIONS, SERVICE_COUNSELING_MOVE_STATUS_LABELS, SERVICE_COUNSELING_PPM_TYPE_OPTIONS, SERVICE_COUNSELING_PPM_TYPE_LABELS, @@ -148,7 +148,7 @@ export const counselingColumns = (moveLockFlag, originLocationList, supervisor) isFilterable: true, Filter: (props) => ( // eslint-disable-next-line react/jsx-props-no-spreading - + ), }, ), @@ -252,7 +252,7 @@ export const closeoutColumns = (moveLockFlag, ppmCloseoutGBLOC, ppmCloseoutOrigi isFilterable: true, Filter: (props) => ( // eslint-disable-next-line react/jsx-props-no-spreading - + ), }, ), From c852584dd75a59ab2d5873fb6198be500255f72e Mon Sep 17 00:00:00 2001 From: cameroncaci Date: Fri, 20 Sep 2024 14:56:44 +0000 Subject: [PATCH 65/68] Revert "server gen" This reverts commit 51c6f9f68fdfee5b0fa048a33998a4eb8741386b. --- pkg/gen/ordersapi/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/gen/ordersapi/server.go b/pkg/gen/ordersapi/server.go index c8204f5cfaf..33bfa754be5 100644 --- a/pkg/gen/ordersapi/server.go +++ b/pkg/gen/ordersapi/server.go @@ -246,7 +246,7 @@ func (s *Server) Serve() (err error) { PreferServerCipherSuites: true, // Only use curves which have assembly implementations // https://github.com/golang/go/tree/master/src/crypto/elliptic - CurvePreferences: []tls.CurveID{tls.CurveP256, tls.CurveP384, tls.CurveP521}, + CurvePreferences: []tls.CurveID{tls.CurveP256}, // Use modern tls mode https://wiki.mozilla.org/Security/Server_Side_TLS#Modern_compatibility NextProtos: []string{"h2", "http/1.1"}, // https://www.owasp.org/index.php/Transport_Layer_Protection_Cheat_Sheet#Rule_-_Only_Support_Strong_Protocols From 89a3c18873a61970bb3277a0f0489aa96d5efbfe Mon Sep 17 00:00:00 2001 From: Paul Stonebraker Date: Fri, 20 Sep 2024 17:25:32 +0000 Subject: [PATCH 66/68] update error handling --- pkg/handlers/primeapi/mto_shipment_address.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/handlers/primeapi/mto_shipment_address.go b/pkg/handlers/primeapi/mto_shipment_address.go index 64e658d251c..0785e69f2d3 100644 --- a/pkg/handlers/primeapi/mto_shipment_address.go +++ b/pkg/handlers/primeapi/mto_shipment_address.go @@ -39,19 +39,19 @@ func (h UpdateMTOShipmentAddressHandler) Handle(params mtoshipmentops.UpdateMTOS } if dbShipment.ShipmentType == models.MTOShipmentTypeHHGIntoNTSDom && - (*dbShipment.DestinationAddressID == addressID) { + (dbShipment.DestinationAddressID != nil && *dbShipment.DestinationAddressID == addressID) { return mtoshipmentops.NewUpdateMTOShipmentAddressUnprocessableEntity().WithPayload(payloads.ValidationError( "Cannot update the destination address of an NTS shipment directly, please update the storage facility address instead", h.GetTraceIDFromRequest(params.HTTPRequest), nil)), err } if dbShipment.Status == models.MTOShipmentStatusApproved && - (*dbShipment.DestinationAddressID == addressID) { + (dbShipment.DestinationAddressID != nil && *dbShipment.DestinationAddressID == addressID) { return mtoshipmentops.NewUpdateMTOShipmentAddressUnprocessableEntity().WithPayload(payloads.ValidationError( "This shipment is approved, please use the updateShipmentDestinationAddress endpoint to update the destination address of an approved shipment", h.GetTraceIDFromRequest(params.HTTPRequest), nil)), err } if dbShipment.ShipmentType == models.MTOShipmentTypeHHGOutOfNTSDom && - (*dbShipment.PickupAddressID == addressID) { + (dbShipment.PickupAddressID != nil && *dbShipment.PickupAddressID == addressID) { return mtoshipmentops.NewUpdateMTOShipmentAddressUnprocessableEntity().WithPayload(payloads.ValidationError( "Cannot update the pickup address of an NTS-Release shipment directly, please update the storage facility address instead", h.GetTraceIDFromRequest(params.HTTPRequest), nil)), err } From 96327048910e61f123e90dabc9fc521ffc0b5a13 Mon Sep 17 00:00:00 2001 From: Paul Stonebraker Date: Mon, 23 Sep 2024 13:04:20 +0000 Subject: [PATCH 67/68] typo fix --- pkg/handlers/primeapi/mto_shipment_address.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/handlers/primeapi/mto_shipment_address.go b/pkg/handlers/primeapi/mto_shipment_address.go index 0785e69f2d3..b45999955a9 100644 --- a/pkg/handlers/primeapi/mto_shipment_address.go +++ b/pkg/handlers/primeapi/mto_shipment_address.go @@ -51,7 +51,7 @@ func (h UpdateMTOShipmentAddressHandler) Handle(params mtoshipmentops.UpdateMTOS } if dbShipment.ShipmentType == models.MTOShipmentTypeHHGOutOfNTSDom && - (dbShipment.PickupAddressID != nil && *dbShipment.PickupAddressID == addressID) { + (*dbShipment.PickupAddressID != uuid.Nil && *dbShipment.PickupAddressID == addressID) { return mtoshipmentops.NewUpdateMTOShipmentAddressUnprocessableEntity().WithPayload(payloads.ValidationError( "Cannot update the pickup address of an NTS-Release shipment directly, please update the storage facility address instead", h.GetTraceIDFromRequest(params.HTTPRequest), nil)), err } From 1e2278b615b60bc44cd45f6994cfaa2e5c4e8d47 Mon Sep 17 00:00:00 2001 From: deandreJones Date: Mon, 23 Sep 2024 11:41:58 -0500 Subject: [PATCH 68/68] spaces are important --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 16edc39f7d2..78e795bb938 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![GoDoc](https://godoc.org/github.com/transcom/mymove?status.svg)](https://godoc.org/github.com/transcom/mymove) -This repository contains the application source code for the MilMove application,the next generation version of the Defense Personal Property System (DPS). DPS is an online system managed by the U.S. [Department of Defense](https://www.defense.gov/) (DoD) [Transportation Command](http://www.ustranscom.mil/) (USTRANSCOM) and is used by service members and their families to manage household goods moves. +This repository contains the application source code for the MilMove application, the next generation version of the Defense Personal Property System (DPS). DPS is an online system managed by the U.S. [Department of Defense](https://www.defense.gov/) (DoD) [Transportation Command](http://www.ustranscom.mil/) (USTRANSCOM) and is used by service members and their families to manage household goods moves. ## License Information