From fa4e8301c6a567d057663171ec37a3ada7b196bd Mon Sep 17 00:00:00 2001 From: deandreJones Date: Mon, 16 Dec 2024 09:32:58 -0600 Subject: [PATCH 01/54] add test --- .../AddOrdersForm/AddOrdersForm.test.jsx | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/components/Office/AddOrdersForm/AddOrdersForm.test.jsx b/src/components/Office/AddOrdersForm/AddOrdersForm.test.jsx index f6c4f04e22f..611331d78a9 100644 --- a/src/components/Office/AddOrdersForm/AddOrdersForm.test.jsx +++ b/src/components/Office/AddOrdersForm/AddOrdersForm.test.jsx @@ -194,3 +194,23 @@ describe('AddOrdersForm - OCONUS and Accompanied Tour Test', () => { }); }); }); +describe('AddOrdersForm - Edge Cases and Additional Scenarios', () => { + it('disables orders type when safety move is selected', async () => { + render( + + + , + ); + + expect(screen.getByLabelText('Orders type')).toBeDisabled(); + }); + + it('disables orders type when bluebark move is selected', async () => { + render( + + + , + ); + expect(screen.getByLabelText('Orders type')).toBeDisabled(); + }); +}); From d308a38c80a52b9e83e80a583a47b5e24e8a82eb Mon Sep 17 00:00:00 2001 From: Alex Lusk Date: Fri, 20 Dec 2024 15:43:41 +0000 Subject: [PATCH 02/54] Added counseling office to move payload --- pkg/gen/ghcapi/embedded_spec.go | 18 +++++ pkg/gen/ghcmessages/move.go | 71 +++++++++++++++++++ .../internal/payloads/model_to_payload.go | 2 + pkg/services/move/move_fetcher.go | 2 +- swagger-def/ghc.yaml | 7 ++ swagger/ghc.yaml | 9 +++ 6 files changed, 108 insertions(+), 1 deletion(-) diff --git a/pkg/gen/ghcapi/embedded_spec.go b/pkg/gen/ghcapi/embedded_spec.go index cbe49f80e17..183609a41ca 100644 --- a/pkg/gen/ghcapi/embedded_spec.go +++ b/pkg/gen/ghcapi/embedded_spec.go @@ -9980,6 +9980,15 @@ func init() { "format": "uuid", "x-nullable": true }, + "counselingOffice": { + "$ref": "#/definitions/TransportationOffice" + }, + "counselingOfficeId": { + "description": "The transportation office that will handle services counseling for this move", + "type": "string", + "format": "uuid", + "x-nullable": true + }, "createdAt": { "type": "string", "format": "date-time" @@ -26580,6 +26589,15 @@ func init() { "format": "uuid", "x-nullable": true }, + "counselingOffice": { + "$ref": "#/definitions/TransportationOffice" + }, + "counselingOfficeId": { + "description": "The transportation office that will handle services counseling for this move", + "type": "string", + "format": "uuid", + "x-nullable": true + }, "createdAt": { "type": "string", "format": "date-time" diff --git a/pkg/gen/ghcmessages/move.go b/pkg/gen/ghcmessages/move.go index bb67e748b11..c647b3c7764 100644 --- a/pkg/gen/ghcmessages/move.go +++ b/pkg/gen/ghcmessages/move.go @@ -61,6 +61,13 @@ type Move struct { // Format: uuid ContractorID *strfmt.UUID `json:"contractorId,omitempty"` + // counseling office + CounselingOffice *TransportationOffice `json:"counselingOffice,omitempty"` + + // The transportation office that will handle services counseling for this move + // Format: uuid + CounselingOfficeID *strfmt.UUID `json:"counselingOfficeId,omitempty"` + // created at // Format: date-time CreatedAt strfmt.DateTime `json:"createdAt,omitempty"` @@ -193,6 +200,14 @@ func (m *Move) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validateCounselingOffice(formats); err != nil { + res = append(res, err) + } + + if err := m.validateCounselingOfficeID(formats); err != nil { + res = append(res, err) + } + if err := m.validateCreatedAt(formats); err != nil { res = append(res, err) } @@ -441,6 +456,37 @@ func (m *Move) validateContractorID(formats strfmt.Registry) error { return nil } +func (m *Move) validateCounselingOffice(formats strfmt.Registry) error { + if swag.IsZero(m.CounselingOffice) { // not required + return nil + } + + if m.CounselingOffice != nil { + if err := m.CounselingOffice.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("counselingOffice") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("counselingOffice") + } + return err + } + } + + return nil +} + +func (m *Move) validateCounselingOfficeID(formats strfmt.Registry) error { + if swag.IsZero(m.CounselingOfficeID) { // not required + return nil + } + + if err := validate.FormatOf("counselingOfficeId", "body", "uuid", m.CounselingOfficeID.String(), formats); err != nil { + return err + } + + return nil +} + func (m *Move) validateCreatedAt(formats strfmt.Registry) error { if swag.IsZero(m.CreatedAt) { // not required return nil @@ -661,6 +707,10 @@ func (m *Move) ContextValidate(ctx context.Context, formats strfmt.Registry) err res = append(res, err) } + if err := m.contextValidateCounselingOffice(ctx, formats); err != nil { + res = append(res, err) + } + if err := m.contextValidateFinancialReviewFlag(ctx, formats); err != nil { res = append(res, err) } @@ -817,6 +867,27 @@ func (m *Move) contextValidateContractor(ctx context.Context, formats strfmt.Reg return nil } +func (m *Move) contextValidateCounselingOffice(ctx context.Context, formats strfmt.Registry) error { + + if m.CounselingOffice != nil { + + if swag.IsZero(m.CounselingOffice) { // not required + return nil + } + + if err := m.CounselingOffice.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("counselingOffice") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("counselingOffice") + } + return err + } + } + + return nil +} + func (m *Move) contextValidateFinancialReviewFlag(ctx context.Context, formats strfmt.Registry) error { if err := validate.ReadOnly(ctx, "financialReviewFlag", "body", bool(m.FinancialReviewFlag)); err != nil { diff --git a/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go b/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go index 6d1dd799115..c15657f6966 100644 --- a/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go +++ b/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go @@ -120,6 +120,8 @@ func Move(move *models.Move, storer storage.FileStorer) (*ghcmessages.Move, erro SCAssignedUser: AssignedOfficeUser(move.SCAssignedUser), TOOAssignedUser: AssignedOfficeUser(move.TOOAssignedUser), TIOAssignedUser: AssignedOfficeUser(move.TIOAssignedUser), + CounselingOfficeID: handlers.FmtUUIDPtr(move.CounselingOfficeID), + CounselingOffice: TransportationOffice(move.CounselingOffice), } return payload, nil diff --git a/pkg/services/move/move_fetcher.go b/pkg/services/move/move_fetcher.go index 470f4ca78a4..a75cf4ece2a 100644 --- a/pkg/services/move/move_fetcher.go +++ b/pkg/services/move/move_fetcher.go @@ -25,7 +25,7 @@ func (f moveFetcher) FetchMove(appCtx appcontext.AppContext, locator string, sea move := &models.Move{} query := appCtx.DB(). EagerPreload("CloseoutOffice.Address", "Contractor", "ShipmentGBLOC", "LockedByOfficeUser", "LockedByOfficeUser.TransportationOffice", "AdditionalDocuments", - "AdditionalDocuments.UserUploads"). + "AdditionalDocuments.UserUploads", "CounselingOffice"). LeftJoin("move_to_gbloc", "move_to_gbloc.move_id = moves.id"). LeftJoin("office_users", "office_users.id = moves.locked_by"). Where("locator = $1", locator) diff --git a/swagger-def/ghc.yaml b/swagger-def/ghc.yaml index 5c60482d05e..2bf80c888f8 100644 --- a/swagger-def/ghc.yaml +++ b/swagger-def/ghc.yaml @@ -5147,6 +5147,13 @@ definitions: format: uuid description: The transportation office that will handle reviewing PPM Closeout documentation for Army and Air Force service members x-nullable: true + counselingOffice: + $ref: 'definitions/TransportationOffice.yaml' + counselingOfficeId: + type: string + format: uuid + description: The transportation office that will handle services counseling for this move + x-nullable: true approvalsRequestedAt: type: string format: date-time diff --git a/swagger/ghc.yaml b/swagger/ghc.yaml index 426b1a3b421..c4b0c22900d 100644 --- a/swagger/ghc.yaml +++ b/swagger/ghc.yaml @@ -5379,6 +5379,15 @@ definitions: The transportation office that will handle reviewing PPM Closeout documentation for Army and Air Force service members x-nullable: true + counselingOffice: + $ref: '#/definitions/TransportationOffice' + counselingOfficeId: + type: string + format: uuid + description: >- + The transportation office that will handle services counseling for + this move + x-nullable: true approvalsRequestedAt: type: string format: date-time From 3d0f4bcb4fbcbf27e448e42824b5e6c7f0a198b1 Mon Sep 17 00:00:00 2001 From: deandreJones Date: Wed, 11 Dec 2024 22:17:27 +0000 Subject: [PATCH 03/54] hide emplid --- 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 d51c45122e3..d8351e49f49 100644 --- a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx +++ b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx @@ -281,7 +281,7 @@ export const CreateCustomerForm = ({ userPrivileges, setFlashMessage, setCanAddO const handleBluebarkChange = (e) => { if (e.target.value === 'true') { setIsBluebarkMove(true); - setShowEmplid(true); + setShowEmplid(false); setValues({ ...values, affiliation: e.target.value, From 85fa79d0a40bff75507d564bf437625d207a9a91 Mon Sep 17 00:00:00 2001 From: deandreJones Date: Fri, 13 Dec 2024 11:21:34 -0600 Subject: [PATCH 04/54] updates --- .../CustomerOnboarding/CreateCustomerForm.jsx | 1 + .../CreateCustomerForm.test.jsx | 152 +++++++++++++++++- 2 files changed, 151 insertions(+), 2 deletions(-) diff --git a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx index d8351e49f49..ed6e6c024da 100644 --- a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx +++ b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx @@ -281,6 +281,7 @@ export const CreateCustomerForm = ({ userPrivileges, setFlashMessage, setCanAddO const handleBluebarkChange = (e) => { if (e.target.value === 'true') { setIsBluebarkMove(true); + setIsSafetyMove(false); setShowEmplid(false); setValues({ ...values, diff --git a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx index 99e398a005c..adc7c505cb3 100644 --- a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx +++ b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx @@ -211,6 +211,11 @@ const safetyPayload = { is_bluebark: 'false', }; +const bluebarkPayload = { + is_safety_move: 'false', + is_bluebark: 'true', +}; + const mockUserPrivileges = [ { createdAt: '0001-01-01T00:00:00.000Z', @@ -615,7 +620,7 @@ describe('CreateCustomerForm', () => { safetyPayload.residential_address.streetAddress1, ); - const locationBox = screen.getAllByRole('combobox'); + const locationBox = screen.getAllByLabelText('Location lookup'); await act(async () => { await userEvent.type(locationBox[1], 'BEVERLY HILLS'); @@ -655,5 +660,148 @@ describe('CreateCustomerForm', () => { }, }); }); - }, 20000); + }, 10000); + + it('submits the form and tests for unsupported state validation', 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 userEvent.type(getByTestId('edipiInput'), fakePayload.edipi); + + 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('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).toBeEnabled(); + }); + + await userEvent.selectOptions(getByTestId('backup-add-state'), 'HI'); + await userEvent.tab(); + + const msg = screen.getByText('Moves to this state are not supported at this time.'); + expect(msg).toBeVisible(); + + await userEvent.selectOptions(getByTestId('backup-add-state'), [fakePayload.residential_address.state]); + await userEvent.tab(); + expect(msg).not.toBeVisible(); + + await waitFor(() => { + expect(saveBtn).toBeEnabled(); + }); + + await userEvent.click(saveBtn); + + await waitFor(() => { + expect(createCustomerWithOktaOption).toHaveBeenCalled(); + expect(mockNavigate).toHaveBeenCalledWith(ordersPath, { + state: { + isSafetyMoveSelected: false, + isBluebarkMoveSelected: false, + }, + }); + }); + }, 10000); + + it('disables okta and non cac user inputs when bluebark move is selected', async () => { + createCustomerWithOktaOption.mockImplementation(() => Promise.resolve(fakeResponse)); + isBooleanFlagEnabled.mockImplementation(() => Promise.resolve(true)); + searchLocationByZipCityState.mockImplementation(mockSearchPickupLocation); + + 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-no'), bluebarkPayload.is_safety_move); + await userEvent.type(getByTestId('is-bluebark-yes'), bluebarkPayload.is_bluebark); + + await userEvent.selectOptions(getByLabelText('Branch of service'), ['ARMY']); + await user.type(getByTestId('edipiInput'), safetyPayload.edipi); + + 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('residential_address.streetAddress1'), + safetyPayload.residential_address.streetAddress1, + ); + + const locationBox = screen.getAllByRole('combobox'); + + await act(async () => { + await userEvent.type(locationBox[1], 'BEVERLY HILLS'); + const selectedResidentialLocation = await screen.findByText(/90210/); + await userEvent.click(selectedResidentialLocation); + }); + + await userEvent.type( + getByTestId('backup_mailing_address.streetAddress1'), + safetyPayload.backup_mailing_address.streetAddress1, + ); + + await act(async () => { + await userEvent.type(locationBox[1], 'DRYDEN'); + const selectedBackupLocation = await screen.findByText(/04225/); + await userEvent.click(selectedBackupLocation); + }); + + 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: false, + isBluebarkMoveSelected: true, + }, + }); + }); + }, 10000); }); From 03958adb0b083e0ef26d6348e93689fa412b5b83 Mon Sep 17 00:00:00 2001 From: deandreJones Date: Fri, 13 Dec 2024 11:31:06 -0600 Subject: [PATCH 05/54] fix test --- .../CustomerOnboarding/CreateCustomerForm.test.jsx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx index adc7c505cb3..27947aacee7 100644 --- a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx +++ b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx @@ -620,7 +620,7 @@ describe('CreateCustomerForm', () => { safetyPayload.residential_address.streetAddress1, ); - const locationBox = screen.getAllByLabelText('Location lookup'); + const locationBox = screen.getAllByRole('combobox'); await act(async () => { await userEvent.type(locationBox[1], 'BEVERLY HILLS'); @@ -735,15 +735,15 @@ describe('CreateCustomerForm', () => { searchLocationByZipCityState.mockImplementation(mockSearchPickupLocation); const { getByLabelText, getByTestId, getByRole } = render( - + , ); const user = userEvent.setup(); - const safetyMove = await screen.findByTestId('is-safety-move-no'); - expect(safetyMove).toBeChecked(); + const saveBtn = await screen.findByRole('button', { name: 'Save' }); + expect(saveBtn).toBeInTheDocument(); // check the safety move box await userEvent.type(getByTestId('is-safety-move-no'), bluebarkPayload.is_safety_move); @@ -777,7 +777,7 @@ describe('CreateCustomerForm', () => { ); await act(async () => { - await userEvent.type(locationBox[1], 'DRYDEN'); + await userEvent.type(locationBox[2], 'DRYDEN'); const selectedBackupLocation = await screen.findByText(/04225/); await userEvent.click(selectedBackupLocation); }); @@ -786,7 +786,6 @@ describe('CreateCustomerForm', () => { 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(() => { From f03251b2a3170a4ba7d191893f11661188073658 Mon Sep 17 00:00:00 2001 From: deandreJones Date: Fri, 13 Dec 2024 11:39:17 -0600 Subject: [PATCH 06/54] remove what was removed --- .../CreateCustomerForm.test.jsx | 67 ------------------- 1 file changed, 67 deletions(-) diff --git a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx index 27947aacee7..efa59c88f0b 100644 --- a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx +++ b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx @@ -662,73 +662,6 @@ describe('CreateCustomerForm', () => { }); }, 10000); - it('submits the form and tests for unsupported state validation', 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 userEvent.type(getByTestId('edipiInput'), fakePayload.edipi); - - 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('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).toBeEnabled(); - }); - - await userEvent.selectOptions(getByTestId('backup-add-state'), 'HI'); - await userEvent.tab(); - - const msg = screen.getByText('Moves to this state are not supported at this time.'); - expect(msg).toBeVisible(); - - await userEvent.selectOptions(getByTestId('backup-add-state'), [fakePayload.residential_address.state]); - await userEvent.tab(); - expect(msg).not.toBeVisible(); - - await waitFor(() => { - expect(saveBtn).toBeEnabled(); - }); - - await userEvent.click(saveBtn); - - await waitFor(() => { - expect(createCustomerWithOktaOption).toHaveBeenCalled(); - expect(mockNavigate).toHaveBeenCalledWith(ordersPath, { - state: { - isSafetyMoveSelected: false, - isBluebarkMoveSelected: false, - }, - }); - }); - }, 10000); - it('disables okta and non cac user inputs when bluebark move is selected', async () => { createCustomerWithOktaOption.mockImplementation(() => Promise.resolve(fakeResponse)); isBooleanFlagEnabled.mockImplementation(() => Promise.resolve(true)); From a7529c1476a412fa16367db7d5620549a09efe87 Mon Sep 17 00:00:00 2001 From: deandreJones Date: Mon, 16 Dec 2024 14:42:08 -0600 Subject: [PATCH 07/54] add timeout --- src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx index efa59c88f0b..3636608f467 100644 --- a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx +++ b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx @@ -31,6 +31,7 @@ const mockPickupLocation = [ const mockSearchPickupLocation = () => Promise.resolve(mockPickupLocation); +jest.setTimeout(60000); const mockNavigate = jest.fn(); jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), From e0847f17b0216bd336321dcaf3c949603dde0bd5 Mon Sep 17 00:00:00 2001 From: deandreJones Date: Mon, 16 Dec 2024 15:38:07 -0600 Subject: [PATCH 08/54] move to lower --- 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 3636608f467..876cb97962a 100644 --- a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx +++ b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx @@ -31,7 +31,6 @@ const mockPickupLocation = [ const mockSearchPickupLocation = () => Promise.resolve(mockPickupLocation); -jest.setTimeout(60000); const mockNavigate = jest.fn(); jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), @@ -59,6 +58,7 @@ jest.mock('store/general/actions', () => ({ beforeEach(() => { jest.clearAllMocks(); + jest.setTimeout(60000); }); const serviceCounselorState = { From d069b1cf66c8064a80697dc6710b0ca9b70e4c97 Mon Sep 17 00:00:00 2001 From: deandreJones Date: Mon, 16 Dec 2024 15:44:35 -0600 Subject: [PATCH 09/54] dont be a flake --- .../Office/CustomerOnboarding/CreateCustomerForm.test.jsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx index 876cb97962a..b49f6aea696 100644 --- a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx +++ b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx @@ -58,7 +58,6 @@ jest.mock('store/general/actions', () => ({ beforeEach(() => { jest.clearAllMocks(); - jest.setTimeout(60000); }); const serviceCounselorState = { @@ -661,7 +660,7 @@ describe('CreateCustomerForm', () => { }, }); }); - }, 10000); + }, 50000); it('disables okta and non cac user inputs when bluebark move is selected', async () => { createCustomerWithOktaOption.mockImplementation(() => Promise.resolve(fakeResponse)); @@ -736,5 +735,5 @@ describe('CreateCustomerForm', () => { }, }); }); - }, 10000); + }, 50000); }); From beaf6f43e684cc32dce247b7ac25b6ca12538bda Mon Sep 17 00:00:00 2001 From: deandreJones Date: Mon, 16 Dec 2024 16:10:09 -0600 Subject: [PATCH 10/54] add safety type test --- pkg/models/order_test.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/pkg/models/order_test.go b/pkg/models/order_test.go index 4171045ee74..6ef09fe7598 100644 --- a/pkg/models/order_test.go +++ b/pkg/models/order_test.go @@ -500,7 +500,7 @@ func (suite *ModelSuite) TestOrderCanSendEmailWithOrdersType() { suite.True(canSendEmail) }) - suite.Run("Safety and BB orders cannot send email", func() { + suite.Run(" BB orders cannot send email", func() { order := factory.BuildOrder(suite.DB(), []factory.Customization{ { Model: m.Order{ @@ -512,4 +512,17 @@ func (suite *ModelSuite) TestOrderCanSendEmailWithOrdersType() { canSendEmail := order.CanSendEmailWithOrdersType() suite.False(canSendEmail) }) + + suite.Run("Safety orders cannot send email", func() { + order := factory.BuildOrder(suite.DB(), []factory.Customization{ + { + Model: m.Order{ + OrdersType: internalmessages.OrdersTypeSAFETY, + }, + }, + }, nil) + + canSendEmail := order.CanSendEmailWithOrdersType() + suite.False(canSendEmail) + }) } From 28c8181691791b42968e8be0ea7c9d5a25d1e24d Mon Sep 17 00:00:00 2001 From: deandreJones Date: Tue, 17 Dec 2024 09:13:09 -0600 Subject: [PATCH 11/54] special moves section --- src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx index ed6e6c024da..fbe210ce4e1 100644 --- a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx +++ b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx @@ -305,7 +305,7 @@ export const CreateCustomerForm = ({ userPrivileges, setFlashMessage, setCanAddO

Create Customer Profile

-

Customer Affiliation

+

Special Moves

{isSafetyPrivileged && (
Is this a Safety move? @@ -358,6 +358,9 @@ export const CreateCustomerForm = ({ userPrivileges, setFlashMessage, setCanAddO />
+
+ +

Customer Affiliation

Date: Tue, 17 Dec 2024 09:19:39 -0600 Subject: [PATCH 12/54] lowercase --- 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 fbe210ce4e1..6b4bd9830cb 100644 --- a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx +++ b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx @@ -334,7 +334,7 @@ export const CreateCustomerForm = ({ userPrivileges, setFlashMessage, setCanAddO )}
- Is this a Bluebark Move? + Is this a Bluebark move?
Date: Sat, 21 Dec 2024 02:21:59 +0000 Subject: [PATCH 13/54] fix capturing the order type in adding orders detail info --- .../Office/OrdersDetailForm/OrdersDetailForm.jsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/Office/OrdersDetailForm/OrdersDetailForm.jsx b/src/components/Office/OrdersDetailForm/OrdersDetailForm.jsx index b24ff3ae670..1c6897583b2 100644 --- a/src/components/Office/OrdersDetailForm/OrdersDetailForm.jsx +++ b/src/components/Office/OrdersDetailForm/OrdersDetailForm.jsx @@ -82,12 +82,16 @@ const OrdersDetailForm = ({ { setFormOrdersType(e.target.value); setFieldValue('ordersType', e.target.value); }} - isDisabled={formIsDisabled || formOrdersType === 'SAFETY'} + isDisabled={formIsDisabled || formOrdersType === 'SAFETY' || formOrdersType === 'BLUEBARK'} /> {showOrdersTypeDetail && ( Date: Thu, 26 Dec 2024 15:57:53 +0000 Subject: [PATCH 14/54] use contant values --- .../Office/OrdersDetailForm/OrdersDetailForm.jsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/Office/OrdersDetailForm/OrdersDetailForm.jsx b/src/components/Office/OrdersDetailForm/OrdersDetailForm.jsx index 1c6897583b2..046c4ae9169 100644 --- a/src/components/Office/OrdersDetailForm/OrdersDetailForm.jsx +++ b/src/components/Office/OrdersDetailForm/OrdersDetailForm.jsx @@ -8,6 +8,7 @@ import { CheckboxField, DropdownInput, DatePickerInput, DutyLocationInput } from import TextField from 'components/form/fields/TextField/TextField'; import MaskedTextField from 'components/form/fields/MaskedTextField/MaskedTextField'; import { DropdownArrayOf } from 'types/form'; +import { SPECIAL_ORDERS_TYPES } from 'constants/orders'; const OrdersDetailForm = ({ deptIndicatorOptions, @@ -83,7 +84,7 @@ const OrdersDetailForm = ({ name="ordersType" label="Orders type" options={ - formOrdersType === 'SAFETY' || formOrdersType === 'BLUEBARK' + formOrdersType === SPECIAL_ORDERS_TYPES.SAFETY || formOrdersType === SPECIAL_ORDERS_TYPES.BLUEBARK ? dropdownInputOptions({ SAFETY: 'Safety', BLUEBARK: 'Bluebark' }) : ordersTypeOptions } @@ -91,7 +92,11 @@ const OrdersDetailForm = ({ setFormOrdersType(e.target.value); setFieldValue('ordersType', e.target.value); }} - isDisabled={formIsDisabled || formOrdersType === 'SAFETY' || formOrdersType === 'BLUEBARK'} + isDisabled={ + formIsDisabled || + formOrdersType === SPECIAL_ORDERS_TYPES.SAFETY || + formOrdersType === SPECIAL_ORDERS_TYPES.BLUEBARK + } /> {showOrdersTypeDetail && ( Date: Thu, 26 Dec 2024 16:29:51 +0000 Subject: [PATCH 15/54] fix test --- .../OrdersDetailForm/OrdersDetailForm.test.jsx | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/components/Office/OrdersDetailForm/OrdersDetailForm.test.jsx b/src/components/Office/OrdersDetailForm/OrdersDetailForm.test.jsx index 37ec69d7645..37861c8b881 100644 --- a/src/components/Office/OrdersDetailForm/OrdersDetailForm.test.jsx +++ b/src/components/Office/OrdersDetailForm/OrdersDetailForm.test.jsx @@ -196,7 +196,23 @@ describe('OrdersDetailForm', () => { showNTSTac: false, showNTSSac: false, showOrdersAcknowledgement: false, - ordersType: 'SAFETY', + ordersType: 'Safety', + }); + + // correct labels are visible + expect(await screen.findByLabelText('Orders type')).toBeDisabled(); + }); + it('has orders type dropdown disabled if bluebark move', async () => { + renderOrdersDetailForm({ + showDepartmentIndicator: false, + showOrdersNumber: false, + showOrdersTypeDetail: false, + showHHGTac: false, + showHHGSac: false, + showNTSTac: false, + showNTSSac: false, + showOrdersAcknowledgement: false, + ordersType: 'BLUEBARK', }); // correct labels are visible From df5f8fc073e13933cc095127e4b20ba3df0efa5a Mon Sep 17 00:00:00 2001 From: deandreJones Date: Thu, 2 Jan 2025 13:08:22 -0600 Subject: [PATCH 16/54] caps --- .../Office/OrdersDetailForm/OrdersDetailForm.jsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/components/Office/OrdersDetailForm/OrdersDetailForm.jsx b/src/components/Office/OrdersDetailForm/OrdersDetailForm.jsx index 046c4ae9169..e83bf2afcfb 100644 --- a/src/components/Office/OrdersDetailForm/OrdersDetailForm.jsx +++ b/src/components/Office/OrdersDetailForm/OrdersDetailForm.jsx @@ -84,7 +84,7 @@ const OrdersDetailForm = ({ name="ordersType" label="Orders type" options={ - formOrdersType === SPECIAL_ORDERS_TYPES.SAFETY || formOrdersType === SPECIAL_ORDERS_TYPES.BLUEBARK + formOrdersType === 'SAFETY' || formOrdersType === SPECIAL_ORDERS_TYPES.BLUEBARK ? dropdownInputOptions({ SAFETY: 'Safety', BLUEBARK: 'Bluebark' }) : ordersTypeOptions } @@ -92,11 +92,7 @@ const OrdersDetailForm = ({ setFormOrdersType(e.target.value); setFieldValue('ordersType', e.target.value); }} - isDisabled={ - formIsDisabled || - formOrdersType === SPECIAL_ORDERS_TYPES.SAFETY || - formOrdersType === SPECIAL_ORDERS_TYPES.BLUEBARK - } + isDisabled={formIsDisabled || formOrdersType === 'SAFETY' || formOrdersType === SPECIAL_ORDERS_TYPES.BLUEBARK} /> {showOrdersTypeDetail && ( Date: Thu, 2 Jan 2025 13:38:52 -0600 Subject: [PATCH 17/54] put back --- src/constants/orders.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/constants/orders.js b/src/constants/orders.js index 93e20b804c8..eb74a318b38 100644 --- a/src/constants/orders.js +++ b/src/constants/orders.js @@ -24,6 +24,7 @@ export const ORDERS_TYPE_OPTIONS = { RETIREMENT: 'Retirement', SEPARATION: 'Separation', WOUNDED_WARRIOR: 'Wounded Warrior', + BLUEBARK: 'BLUEBARK', TEMPORARY_DUTY: 'Temporary Duty (TDY)', EARLY_RETURN_OF_DEPENDENTS: 'Early Return of Dependents', STUDENT_TRAVEL: 'Student Travel', From cea9ade6f31d8f145ee301d34724754341850ea0 Mon Sep 17 00:00:00 2001 From: deandreJones Date: Thu, 2 Jan 2025 13:55:30 -0600 Subject: [PATCH 18/54] put test order type back to caps --- .../Office/OrdersDetailForm/OrdersDetailForm.test.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Office/OrdersDetailForm/OrdersDetailForm.test.jsx b/src/components/Office/OrdersDetailForm/OrdersDetailForm.test.jsx index 37861c8b881..59c860e300f 100644 --- a/src/components/Office/OrdersDetailForm/OrdersDetailForm.test.jsx +++ b/src/components/Office/OrdersDetailForm/OrdersDetailForm.test.jsx @@ -196,7 +196,7 @@ describe('OrdersDetailForm', () => { showNTSTac: false, showNTSSac: false, showOrdersAcknowledgement: false, - ordersType: 'Safety', + ordersType: 'SAFETY', }); // correct labels are visible From b57afab09bbb175d3c269d280d7214075cfa5519 Mon Sep 17 00:00:00 2001 From: deandreJones Date: Thu, 2 Jan 2025 14:43:53 -0600 Subject: [PATCH 19/54] caps on the const --- .../Office/OrdersDetailForm/OrdersDetailForm.jsx | 8 ++++++-- src/constants/orders.js | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/components/Office/OrdersDetailForm/OrdersDetailForm.jsx b/src/components/Office/OrdersDetailForm/OrdersDetailForm.jsx index e83bf2afcfb..046c4ae9169 100644 --- a/src/components/Office/OrdersDetailForm/OrdersDetailForm.jsx +++ b/src/components/Office/OrdersDetailForm/OrdersDetailForm.jsx @@ -84,7 +84,7 @@ const OrdersDetailForm = ({ name="ordersType" label="Orders type" options={ - formOrdersType === 'SAFETY' || formOrdersType === SPECIAL_ORDERS_TYPES.BLUEBARK + formOrdersType === SPECIAL_ORDERS_TYPES.SAFETY || formOrdersType === SPECIAL_ORDERS_TYPES.BLUEBARK ? dropdownInputOptions({ SAFETY: 'Safety', BLUEBARK: 'Bluebark' }) : ordersTypeOptions } @@ -92,7 +92,11 @@ const OrdersDetailForm = ({ setFormOrdersType(e.target.value); setFieldValue('ordersType', e.target.value); }} - isDisabled={formIsDisabled || formOrdersType === 'SAFETY' || formOrdersType === SPECIAL_ORDERS_TYPES.BLUEBARK} + isDisabled={ + formIsDisabled || + formOrdersType === SPECIAL_ORDERS_TYPES.SAFETY || + formOrdersType === SPECIAL_ORDERS_TYPES.BLUEBARK + } /> {showOrdersTypeDetail && ( { From cc69631197162e8ac58f11ca83122a5ffb27007e Mon Sep 17 00:00:00 2001 From: deandreJones Date: Thu, 2 Jan 2025 14:55:00 -0600 Subject: [PATCH 20/54] no cap on the move details header --- .../Office/OrdersDetailForm/OrdersDetailForm.jsx | 9 +++++---- src/constants/orders.js | 6 ++++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/components/Office/OrdersDetailForm/OrdersDetailForm.jsx b/src/components/Office/OrdersDetailForm/OrdersDetailForm.jsx index 046c4ae9169..df282179f60 100644 --- a/src/components/Office/OrdersDetailForm/OrdersDetailForm.jsx +++ b/src/components/Office/OrdersDetailForm/OrdersDetailForm.jsx @@ -8,7 +8,7 @@ import { CheckboxField, DropdownInput, DatePickerInput, DutyLocationInput } from import TextField from 'components/form/fields/TextField/TextField'; import MaskedTextField from 'components/form/fields/MaskedTextField/MaskedTextField'; import { DropdownArrayOf } from 'types/form'; -import { SPECIAL_ORDERS_TYPES } from 'constants/orders'; +import { SPECIAL_ORDERS_TYPES_NON_LABEL } from 'constants/orders'; const OrdersDetailForm = ({ deptIndicatorOptions, @@ -84,7 +84,8 @@ const OrdersDetailForm = ({ name="ordersType" label="Orders type" options={ - formOrdersType === SPECIAL_ORDERS_TYPES.SAFETY || formOrdersType === SPECIAL_ORDERS_TYPES.BLUEBARK + formOrdersType === SPECIAL_ORDERS_TYPES_NON_LABEL.SAFETY || + formOrdersType === SPECIAL_ORDERS_TYPES_NON_LABEL.BLUEBARK ? dropdownInputOptions({ SAFETY: 'Safety', BLUEBARK: 'Bluebark' }) : ordersTypeOptions } @@ -94,8 +95,8 @@ const OrdersDetailForm = ({ }} isDisabled={ formIsDisabled || - formOrdersType === SPECIAL_ORDERS_TYPES.SAFETY || - formOrdersType === SPECIAL_ORDERS_TYPES.BLUEBARK + formOrdersType === SPECIAL_ORDERS_TYPES_NON_LABEL.SAFETY || + formOrdersType === SPECIAL_ORDERS_TYPES_NON_LABEL.BLUEBARK } /> {showOrdersTypeDetail && ( diff --git a/src/constants/orders.js b/src/constants/orders.js index 56781f96b86..4f7b9dccd13 100644 --- a/src/constants/orders.js +++ b/src/constants/orders.js @@ -9,6 +9,12 @@ export const ORDERS_TYPE = { }; export const SPECIAL_ORDERS_TYPES = { + WOUNDED_WARRIOR: 'Wounded Warrior', + BLUEBARK: 'Bluebark', + SAFETY: 'Safety', +}; + +export const SPECIAL_ORDERS_TYPES_NON_LABEL = { WOUNDED_WARRIOR: 'Wounded Warrior', BLUEBARK: 'BLUEBARK', SAFETY: 'SAFETY', From f23c0d112831c781760e67291fc9a935a54baf9c Mon Sep 17 00:00:00 2001 From: deandreJones Date: Thu, 2 Jan 2025 15:03:38 -0600 Subject: [PATCH 21/54] trash basket --- .../Office/OrdersDetailForm/OrdersDetailForm.jsx | 9 ++++----- src/constants/orders.js | 9 ++------- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/src/components/Office/OrdersDetailForm/OrdersDetailForm.jsx b/src/components/Office/OrdersDetailForm/OrdersDetailForm.jsx index df282179f60..26028b4ea69 100644 --- a/src/components/Office/OrdersDetailForm/OrdersDetailForm.jsx +++ b/src/components/Office/OrdersDetailForm/OrdersDetailForm.jsx @@ -8,7 +8,7 @@ import { CheckboxField, DropdownInput, DatePickerInput, DutyLocationInput } from import TextField from 'components/form/fields/TextField/TextField'; import MaskedTextField from 'components/form/fields/MaskedTextField/MaskedTextField'; import { DropdownArrayOf } from 'types/form'; -import { SPECIAL_ORDERS_TYPES_NON_LABEL } from 'constants/orders'; +import { SPECIAL_ORDERS_TYPES } from 'constants/orders'; const OrdersDetailForm = ({ deptIndicatorOptions, @@ -84,8 +84,7 @@ const OrdersDetailForm = ({ name="ordersType" label="Orders type" options={ - formOrdersType === SPECIAL_ORDERS_TYPES_NON_LABEL.SAFETY || - formOrdersType === SPECIAL_ORDERS_TYPES_NON_LABEL.BLUEBARK + formOrdersType === SPECIAL_ORDERS_TYPES.SAFETY_NON_LABEL || formOrdersType === SPECIAL_ORDERS_TYPES.BLUEBARK ? dropdownInputOptions({ SAFETY: 'Safety', BLUEBARK: 'Bluebark' }) : ordersTypeOptions } @@ -95,8 +94,8 @@ const OrdersDetailForm = ({ }} isDisabled={ formIsDisabled || - formOrdersType === SPECIAL_ORDERS_TYPES_NON_LABEL.SAFETY || - formOrdersType === SPECIAL_ORDERS_TYPES_NON_LABEL.BLUEBARK + formOrdersType === SPECIAL_ORDERS_TYPES.SAFETY_NON_LABEL || + formOrdersType === SPECIAL_ORDERS_TYPES.BLUEBARK } /> {showOrdersTypeDetail && ( diff --git a/src/constants/orders.js b/src/constants/orders.js index 4f7b9dccd13..7eeb4ab9e73 100644 --- a/src/constants/orders.js +++ b/src/constants/orders.js @@ -9,15 +9,10 @@ export const ORDERS_TYPE = { }; export const SPECIAL_ORDERS_TYPES = { - WOUNDED_WARRIOR: 'Wounded Warrior', - BLUEBARK: 'Bluebark', - SAFETY: 'Safety', -}; - -export const SPECIAL_ORDERS_TYPES_NON_LABEL = { WOUNDED_WARRIOR: 'Wounded Warrior', BLUEBARK: 'BLUEBARK', - SAFETY: 'SAFETY', + SAFETY: 'Safety', + SAFETY_NON_LABEL: 'SAFETY', }; export const CHECK_SPECIAL_ORDERS_TYPES = (ordersType) => { From d3a7800e3a6a44cd1f457065dde13fe533c55f24 Mon Sep 17 00:00:00 2001 From: AaronW Date: Mon, 6 Jan 2025 17:56:28 +0000 Subject: [PATCH 22/54] brought in changes done on PR 14127 --- migrations/app/migrations_manifest.txt | 2 +- .../payloads/model_to_payload_test.go | 88 +++++++++++++ pkg/handlers/internalapi/orders.go | 6 + pkg/handlers/internalapi/orders_test.go | 124 ++++++++++++++++++ pkg/models/errors.go | 3 + pkg/models/order.go | 43 ++++++ pkg/testdatagen/scenario/shared.go | 54 ++++++++ pkg/testdatagen/testharness/dispatch.go | 3 + pkg/testdatagen/testharness/make_move.go | 16 +++ .../servicesCounselingFlows.spec.js | 29 ++++ playwright/tests/utils/testharness.js | 11 ++ 11 files changed, 378 insertions(+), 1 deletion(-) diff --git a/migrations/app/migrations_manifest.txt b/migrations/app/migrations_manifest.txt index 18c13cdaa73..a5330171a14 100644 --- a/migrations/app/migrations_manifest.txt +++ b/migrations/app/migrations_manifest.txt @@ -1052,9 +1052,9 @@ 20241203024453_add_ppm_max_incentive_column.up.sql 20241204155919_update_ordering_proc.up.sql 20241204210208_retroactive_update_of_ppm_max_and_estimated_incentives_prd.up.sql -20241218201833_add_PPPO_BASE_ELIZABETH.up.sql 20241217163231_update_duty_locations_bad_zips.up.sql 20241217180136_add_AK_zips_to_zip3_distances.up.sql +20241218201833_add_PPPO_BASE_ELIZABETH.up.sql 20241220171035_add_additional_AK_zips_to_zip3_distances.up.sql 20241227153723_remove_empty_string_emplid_values.up.sql 20241230190638_remove_AK_zips_from_zip3.up.sql diff --git a/pkg/handlers/ghcapi/internal/payloads/model_to_payload_test.go b/pkg/handlers/ghcapi/internal/payloads/model_to_payload_test.go index ec3072d2ca0..40fb6e67ebf 100644 --- a/pkg/handlers/ghcapi/internal/payloads/model_to_payload_test.go +++ b/pkg/handlers/ghcapi/internal/payloads/model_to_payload_test.go @@ -9,6 +9,7 @@ import ( "github.com/transcom/mymove/pkg/factory" "github.com/transcom/mymove/pkg/gen/ghcmessages" + "github.com/transcom/mymove/pkg/gen/internalmessages" "github.com/transcom/mymove/pkg/handlers" "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/models/roles" @@ -315,6 +316,93 @@ func (suite *PayloadsSuite) TestShipmentAddressUpdate() { }) } +func (suite *PayloadsSuite) TestMoveWithGBLOC() { + defaultOrdersNumber := "ORDER3" + defaultTACNumber := "F8E1" + defaultDepartmentIndicator := "AIR_AND_SPACE_FORCE" + defaultGrade := "E_1" + defaultHasDependents := false + defaultSpouseHasProGear := false + defaultOrdersType := internalmessages.OrdersTypePERMANENTCHANGEOFSTATION + defaultOrdersTypeDetail := internalmessages.OrdersTypeDetail("HHG_PERMITTED") + defaultStatus := models.OrderStatusDRAFT + testYear := 2018 + defaultIssueDate := time.Date(testYear, time.March, 15, 0, 0, 0, 0, time.UTC) + defaultReportByDate := time.Date(testYear, time.August, 1, 0, 0, 0, 0, time.UTC) + defaultGBLOC := "KKFA" + + originDutyLocation := models.DutyLocation{ + Name: "Custom Origin", + } + originDutyLocationTOName := "origin duty location transportation office" + firstName := "customFirst" + lastName := "customLast" + serviceMember := models.ServiceMember{ + FirstName: &firstName, + LastName: &lastName, + } + uploadedOrders := models.Document{ + ID: uuid.Must(uuid.NewV4()), + } + dependents := 7 + entitlement := models.Entitlement{ + TotalDependents: &dependents, + } + amendedOrders := models.Document{ + ID: uuid.Must(uuid.NewV4()), + } + // Create order + order := factory.BuildOrder(suite.DB(), []factory.Customization{ + { + Model: originDutyLocation, + Type: &factory.DutyLocations.OriginDutyLocation, + }, + { + Model: models.TransportationOffice{ + Name: originDutyLocationTOName, + }, + Type: &factory.TransportationOffices.OriginDutyLocation, + }, + { + Model: serviceMember, + }, + { + Model: uploadedOrders, + Type: &factory.Documents.UploadedOrders, + }, + { + Model: entitlement, + }, + { + Model: amendedOrders, + Type: &factory.Documents.UploadedAmendedOrders, + }, + }, nil) + + suite.Equal(defaultOrdersNumber, *order.OrdersNumber) + suite.Equal(defaultTACNumber, *order.TAC) + suite.Equal(defaultDepartmentIndicator, *order.DepartmentIndicator) + suite.Equal(defaultGrade, string(*order.Grade)) + suite.Equal(defaultHasDependents, order.HasDependents) + suite.Equal(defaultSpouseHasProGear, order.SpouseHasProGear) + suite.Equal(defaultOrdersType, order.OrdersType) + suite.Equal(defaultOrdersTypeDetail, *order.OrdersTypeDetail) + suite.Equal(defaultStatus, order.Status) + suite.Equal(defaultIssueDate, order.IssueDate) + suite.Equal(defaultReportByDate, order.ReportByDate) + suite.Equal(defaultGBLOC, *order.OriginDutyLocationGBLOC) + + suite.Equal(originDutyLocation.Name, order.OriginDutyLocation.Name) + suite.Equal(originDutyLocationTOName, order.OriginDutyLocation.TransportationOffice.Name) + suite.Equal(*serviceMember.FirstName, *order.ServiceMember.FirstName) + suite.Equal(*serviceMember.LastName, *order.ServiceMember.LastName) + suite.Equal(uploadedOrders.ID, order.UploadedOrdersID) + suite.Equal(uploadedOrders.ID, order.UploadedOrders.ID) + suite.Equal(*entitlement.TotalDependents, *order.Entitlement.TotalDependents) + suite.Equal(amendedOrders.ID, *order.UploadedAmendedOrdersID) + suite.Equal(amendedOrders.ID, order.UploadedAmendedOrders.ID) +} + func (suite *PayloadsSuite) TestWeightTicketUpload() { uploadID, _ := uuid.NewV4() testURL := "https://testurl.com" diff --git a/pkg/handlers/internalapi/orders.go b/pkg/handlers/internalapi/orders.go index c9b5125c827..3936dcb39e2 100644 --- a/pkg/handlers/internalapi/orders.go +++ b/pkg/handlers/internalapi/orders.go @@ -382,6 +382,12 @@ func (h UpdateOrdersHandler) Handle(params ordersop.UpdateOrdersParams) middlewa order.OriginDutyLocation = &originDutyLocation order.OriginDutyLocationID = &originDutyLocationID + originGBLOC, originGBLOCerr := models.FetchGBLOCForPostalCode(appCtx.DB(), originDutyLocation.Address.PostalCode) + if originGBLOCerr != nil { + return handlers.ResponseForError(appCtx.Logger(), originGBLOCerr), originGBLOCerr + } + order.OriginDutyLocationGBLOC = &originGBLOC.GBLOC + if payload.MoveID != "" { moveID, err := uuid.FromString(payload.MoveID.String()) diff --git a/pkg/handlers/internalapi/orders_test.go b/pkg/handlers/internalapi/orders_test.go index b2ad7897f8a..59df8daf475 100644 --- a/pkg/handlers/internalapi/orders_test.go +++ b/pkg/handlers/internalapi/orders_test.go @@ -723,6 +723,130 @@ func (suite *HandlerSuite) TestUpdateOrdersHandler() { }) } +func (suite *HandlerSuite) TestUpdateOrdersHandlerOriginPostalCodeAndGBLOC() { + factory.BuildPostalCodeToGBLOC(suite.DB(), []factory.Customization{ + { + Model: models.PostalCodeToGBLOC{ + PostalCode: "90210", + GBLOC: "KKFA", + }, + }, + }, nil) + factory.BuildPostalCodeToGBLOC(suite.DB(), []factory.Customization{ + { + Model: models.PostalCodeToGBLOC{ + PostalCode: "35023", + GBLOC: "CNNQ", + }, + }, + }, nil) + + firstAddress := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + PostalCode: "90210", + }, + }, + }, nil) + updatedAddress := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + PostalCode: "35023", + }, + }, + }, nil) + dutyLocation := factory.BuildDutyLocation(suite.DB(), []factory.Customization{ + { + Model: models.DutyLocation{ + AddressID: firstAddress.ID, + }, + }, + }, nil) + updatedDutyLocation := factory.BuildDutyLocation(suite.DB(), []factory.Customization{ + { + Model: models.DutyLocation{ + AddressID: updatedAddress.ID, + }, + }, + }, nil) + newDutyLocation := factory.BuildDutyLocation(suite.DB(), nil, nil) + + order := factory.BuildOrder(suite.DB(), []factory.Customization{ + { + Model: models.Order{ + OriginDutyLocationID: &dutyLocation.ID, + NewDutyLocationID: newDutyLocation.ID, + }, + }, + }, nil) + + fetchedOrder, err := models.FetchOrder(suite.DB(), order.ID) + suite.NoError(err) + + var fetchedPostalCode, fetchedGBLOC string + fetchedPostalCode, err = fetchedOrder.GetOriginPostalCode(suite.DB()) + suite.NoError(err) + fetchedGBLOC, err = fetchedOrder.GetOriginGBLOC(suite.DB()) + suite.NoError(err) + + suite.Equal("90210", fetchedPostalCode) + suite.Equal("KKFA", fetchedGBLOC) + + newOrdersType := internalmessages.OrdersTypePERMANENTCHANGEOFSTATION + issueDate := time.Date(2018, time.March, 10, 0, 0, 0, 0, time.UTC) + reportByDate := time.Date(2018, time.August, 1, 0, 0, 0, 0, time.UTC) + deptIndicator := internalmessages.DeptIndicatorAIRANDSPACEFORCE + + payload := &internalmessages.CreateUpdateOrders{ + OrdersType: &order.OrdersType, + NewDutyLocationID: handlers.FmtUUID(order.NewDutyLocationID), + OriginDutyLocationID: *handlers.FmtUUID(updatedDutyLocation.ID), + IssueDate: handlers.FmtDate(issueDate), + ReportByDate: handlers.FmtDate(reportByDate), + DepartmentIndicator: &deptIndicator, + HasDependents: handlers.FmtBool(false), + SpouseHasProGear: handlers.FmtBool(false), + ServiceMemberID: handlers.FmtUUID(order.ServiceMemberID), + Grade: models.ServiceMemberGradeE4.Pointer(), + } + + path := fmt.Sprintf("/orders/%v", order.ID.String()) + req := httptest.NewRequest("PUT", path, nil) + req = suite.AuthenticateRequest(req, order.ServiceMember) + + params := ordersop.UpdateOrdersParams{ + HTTPRequest: req, + OrdersID: *handlers.FmtUUID(order.ID), + UpdateOrders: payload, + } + + fakeS3 := storageTest.NewFakeS3Storage(true) + handlerConfig := suite.HandlerConfig() + handlerConfig.SetFileStorer(fakeS3) + + handler := UpdateOrdersHandler{handlerConfig} + + response := handler.Handle(params) + + suite.IsType(&ordersop.UpdateOrdersOK{}, response) + + okResponse := response.(*ordersop.UpdateOrdersOK) + + suite.NoError(okResponse.Payload.Validate(strfmt.Default)) + suite.Equal(string(newOrdersType), string(*okResponse.Payload.OrdersType)) + + fetchedOrder, err = models.FetchOrder(suite.DB(), order.ID) + suite.NoError(err) + + fetchedPostalCode, err = fetchedOrder.GetOriginPostalCode(suite.DB()) + suite.NoError(err) + fetchedGBLOC = *fetchedOrder.OriginDutyLocationGBLOC + suite.NoError(err) + + suite.Equal("35023", fetchedPostalCode) + suite.Equal("CNNQ", fetchedGBLOC) +} + func (suite *HandlerSuite) TestEntitlementHelperFunc() { orderGrade := internalmessages.OrderPayGrade("O-3") int64Dependents := int64(2) diff --git a/pkg/models/errors.go b/pkg/models/errors.go index 130432a4008..498cf03e771 100644 --- a/pkg/models/errors.go +++ b/pkg/models/errors.go @@ -51,3 +51,6 @@ const UniqueConstraintViolationOfficeUserOtherUniqueIDErrorString = "pq: duplica // ErrInvalidMoveID is used if a argument is provided in cases where a move ID is provided, but may be malformed, empty, or nonexistent var ErrInvalidMoveID = errors.New("INVALID_MOVE_ID") + +// ErrInvalidOrderID is used if a argument is provided in cases where a order ID is provided, but may be malformed, empty, or nonexistent +var ErrInvalidOrderID = errors.New("INVALID_ORDER_ID") diff --git a/pkg/models/order.go b/pkg/models/order.go index add1e705938..f4628894a84 100644 --- a/pkg/models/order.go +++ b/pkg/models/order.go @@ -308,6 +308,49 @@ func (o *Order) CreateNewMove(db *pop.Connection, moveOptions MoveOptions) (*Mov return createNewMove(db, *o, moveOptions) } +/* + * GetOriginPostalCode returns the GBLOC for the postal code of the the origin duty location of the order. + */ +func (o Order) GetOriginPostalCode(db *pop.Connection) (string, error) { + // Since this requires looking up the order in the DB, the order must have an ID. This means, the order has to have been created first. + if uuid.UUID.IsNil(o.ID) { + return "", errors.WithMessage(ErrInvalidOrderID, "You must create the order in the DB before getting the origin GBLOC.") + } + + err := db.Load(&o, "OriginDutyLocation.Address") + if err != nil { + if err.Error() == RecordNotFoundErrorString { + return "", errors.WithMessage(err, "No Origin Duty Location was found for the order ID "+o.ID.String()) + } + return "", err + } + + return o.OriginDutyLocation.Address.PostalCode, nil +} + +/* + * GetOriginGBLOC returns the GBLOC for the postal code of the the origin duty location of the order. + */ +func (o Order) GetOriginGBLOC(db *pop.Connection) (string, error) { + // Since this requires looking up the order in the DB, the order must have an ID. This means, the order has to have been created first. + if uuid.UUID.IsNil(o.ID) { + return "", errors.WithMessage(ErrInvalidOrderID, "You must created the order in the DB before getting the destination GBLOC.") + } + + originPostalCode, err := o.GetOriginPostalCode(db) + if err != nil { + return "", err + } + + var originGBLOC PostalCodeToGBLOC + originGBLOC, err = FetchGBLOCForPostalCode(db, originPostalCode) + if err != nil { + return "", err + } + + return originGBLOC.GBLOC, nil +} + // IsComplete checks if orders have all fields necessary to approve a move func (o *Order) IsComplete() bool { diff --git a/pkg/testdatagen/scenario/shared.go b/pkg/testdatagen/scenario/shared.go index 8d04108fd6b..4450a6964e9 100644 --- a/pkg/testdatagen/scenario/shared.go +++ b/pkg/testdatagen/scenario/shared.go @@ -10507,6 +10507,60 @@ func CreateNeedsServicesCounseling(appCtx appcontext.AppContext, ordersType inte return move } +/* +Create Needs Service Counseling in a non-default GBLOC - pass in orders with all required information, shipment type, destination type, locator +*/ +func CreateNeedsServicesCounselingInOtherGBLOC(appCtx appcontext.AppContext, ordersType internalmessages.OrdersType, shipmentType models.MTOShipmentType, destinationType *models.DestinationType, locator string) models.Move { + db := appCtx.DB() + originDutyLocationAddress := factory.BuildAddress(db, []factory.Customization{ + { + Model: models.Address{ + PostalCode: "35023", + }, + }, + }, nil) + originDutyLocation := factory.BuildDutyLocation(db, []factory.Customization{ + { + Model: models.DutyLocation{ + Name: "Test Location", + AddressID: originDutyLocationAddress.ID, + }, + }, + }, nil) + order := factory.BuildOrder(db, []factory.Customization{ + { + Model: originDutyLocation, + }, + }, nil) + move := factory.BuildMove(db, []factory.Customization{ + { + Model: order, + LinkOnly: true, + }, + { + Model: models.Move{ + Locator: locator, + Status: models.MoveStatusNeedsServiceCounseling, + }, + }, + }, nil) + + factory.BuildMTOShipment(db, []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + { + Model: models.MTOShipment{ + ShipmentType: shipmentType, + Status: models.MTOShipmentStatusSubmitted, + }, + }, + }, nil) + + return move +} + func CreateNeedsServicesCounselingWithAmendedOrders(appCtx appcontext.AppContext, userUploader *uploader.UserUploader, ordersType internalmessages.OrdersType, shipmentType models.MTOShipmentType, destinationType *models.DestinationType, locator string) models.Move { db := appCtx.DB() submittedAt := time.Now() diff --git a/pkg/testdatagen/testharness/dispatch.go b/pkg/testdatagen/testharness/dispatch.go index e9cf4c27d5f..dd4b421cc2c 100644 --- a/pkg/testdatagen/testharness/dispatch.go +++ b/pkg/testdatagen/testharness/dispatch.go @@ -50,6 +50,9 @@ var actionDispatcher = map[string]actionFunc{ "HHGMoveNeedsSC": func(appCtx appcontext.AppContext) testHarnessResponse { return MakeHHGMoveNeedsSC(appCtx) }, + "HHGMoveNeedsSCOtherGBLOC": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeHHGMoveNeedsSCOtherGBLOC(appCtx) + }, "HHGMoveAsUSMCNeedsSC": func(appCtx appcontext.AppContext) testHarnessResponse { return MakeHHGMoveNeedsServicesCounselingUSMC(appCtx) }, diff --git a/pkg/testdatagen/testharness/make_move.go b/pkg/testdatagen/testharness/make_move.go index fd337bb029f..50acd913415 100644 --- a/pkg/testdatagen/testharness/make_move.go +++ b/pkg/testdatagen/testharness/make_move.go @@ -4013,6 +4013,22 @@ func MakeHHGMoveNeedsSC(appCtx appcontext.AppContext) models.Move { return *newmove } +// MakeHHGMoveNeedsSCOtherGBLOC creates an fully ready move needing SC approval in a non-default GBLOC +func MakeHHGMoveNeedsSCOtherGBLOC(appCtx appcontext.AppContext) models.Move { + pcos := internalmessages.OrdersTypePERMANENTCHANGEOFSTATION + hhg := models.MTOShipmentTypeHHG + locator := models.GenerateLocator() + move := scenario.CreateNeedsServicesCounselingInOtherGBLOC(appCtx, pcos, hhg, nil, locator) + + // re-fetch the move so that we ensure we have exactly what is in + // the db + newmove, err := models.FetchMove(appCtx.DB(), &auth.Session{}, move.ID) + if err != nil { + log.Panic(fmt.Errorf("failed to fetch move: %w", err)) + } + return *newmove +} + // MakeBoatHaulAwayMoveNeedsSC creates an fully ready move with a boat haul-away shipment needing SC approval func MakeBoatHaulAwayMoveNeedsSC(appCtx appcontext.AppContext) models.Move { userUploader := newUserUploader(appCtx) diff --git a/playwright/tests/office/servicescounseling/servicesCounselingFlows.spec.js b/playwright/tests/office/servicescounseling/servicesCounselingFlows.spec.js index 6ab07e9026c..0285d60a72c 100644 --- a/playwright/tests/office/servicescounseling/servicesCounselingFlows.spec.js +++ b/playwright/tests/office/servicescounseling/servicesCounselingFlows.spec.js @@ -14,6 +14,33 @@ const LocationLookup = 'BEVERLY HILLS, CA 90210 (LOS ANGELES)'; test.describe('Services counselor user', () => { test.describe('with basic HHG move', () => { + test.describe('GBLOC tests', () => { + test.describe('Origin Duty Location', () => { + let moveLocatorKKFA = ''; + let moveLocatorCNNQ = ''; + test.beforeEach(async ({ scPage }) => { + const moveKKFA = await scPage.testHarness.buildHHGMoveNeedsSC(); + moveLocatorKKFA = moveKKFA.locator; + const moveCNNQ = await scPage.testHarness.buildHHGMoveNeedsSC(); + moveLocatorCNNQ = moveCNNQ.locator; + }); + + test('when origin duty location GBLOC matches services counselor GBLOC', async ({ page }) => { + const locatorFilter = await page.getByTestId('locator').getByTestId('TextBoxFilter'); + await locatorFilter.fill(moveLocatorKKFA); + await locatorFilter.blur(); + await expect(page.getByTestId('locator-0')).toBeVisible(); + }); + + test('when origin duty location GBLOC does not match services counselor GBLOC', async ({ page }) => { + const locatorFilter = await page.getByTestId('locator').getByTestId('TextBoxFilter'); + await locatorFilter.fill(moveLocatorCNNQ); + await locatorFilter.blur(); + await expect(page.getByTestId('locator-0')).not.toBeVisible(); + }); + }); + }); + test.beforeEach(async ({ scPage }) => { const move = await scPage.testHarness.buildHHGMoveNeedsSC(); await scPage.navigateToMove(move.locator); @@ -387,6 +414,8 @@ test.describe('Services counselor user', () => { // Edit the shipment so that the tag disappears await page.locator('[data-testid="ShipmentContainer"] .usa-button').last().click(); await page.locator('select[name="destinationType"]').selectOption({ label: 'Home of selection (HOS)' }); + await page.getByLabel('Requested pickup date').fill('16 Mar 2022'); + await page.locator('[data-testid="submitForm"]').click(); await scPage.waitForLoading(); diff --git a/playwright/tests/utils/testharness.js b/playwright/tests/utils/testharness.js index a01dac3461e..74feee4ffef 100644 --- a/playwright/tests/utils/testharness.js +++ b/playwright/tests/utils/testharness.js @@ -28,6 +28,9 @@ export class TestHarness { * @property {string} id * @property {string} locator * @property {Object} Orders + * @property {Object} OriginDutyLocation + * @property {Object} OriginDutyLocation.Address + * @property {string} OriginDutyLocation.Address.PostalCode * @property {Object} Orders.NewDutyLocation * @property {string} Orders.NewDutyLocation.name * @property {Object} Orders.ServiceMember @@ -392,6 +395,14 @@ export class TestHarness { return this.buildDefault('HHGMoveNeedsSC'); } + /** + * Use testharness to build hhg move needing SC approval in a non-default GBLOC + * @returns {Promise} + */ + async buildHHGMoveNeedsSCInOtherGBLOC() { + return this.buildDefault('HHGMoveNeedsSCOtherGBLOC'); + } + /** * Use testharness to build hhg move as USMC needing SC approval * @returns {Promise} From 1c27b188ccb6841b8db0d931e306aa471450ee16 Mon Sep 17 00:00:00 2001 From: AaronW Date: Tue, 7 Jan 2025 17:13:30 +0000 Subject: [PATCH 23/54] corrected for bad automatic conflict resolution --- .../servicesCounselingFlows.spec.js | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/playwright/tests/office/servicescounseling/servicesCounselingFlows.spec.js b/playwright/tests/office/servicescounseling/servicesCounselingFlows.spec.js index 0285d60a72c..73adcf940f2 100644 --- a/playwright/tests/office/servicescounseling/servicesCounselingFlows.spec.js +++ b/playwright/tests/office/servicescounseling/servicesCounselingFlows.spec.js @@ -13,34 +13,34 @@ const supportingDocsEnabled = process.env.FEATURE_FLAG_MANAGE_SUPPORTING_DOCS; const LocationLookup = 'BEVERLY HILLS, CA 90210 (LOS ANGELES)'; test.describe('Services counselor user', () => { - test.describe('with basic HHG move', () => { - test.describe('GBLOC tests', () => { - test.describe('Origin Duty Location', () => { - let moveLocatorKKFA = ''; - let moveLocatorCNNQ = ''; - test.beforeEach(async ({ scPage }) => { - const moveKKFA = await scPage.testHarness.buildHHGMoveNeedsSC(); - moveLocatorKKFA = moveKKFA.locator; - const moveCNNQ = await scPage.testHarness.buildHHGMoveNeedsSC(); - moveLocatorCNNQ = moveCNNQ.locator; - }); - - test('when origin duty location GBLOC matches services counselor GBLOC', async ({ page }) => { - const locatorFilter = await page.getByTestId('locator').getByTestId('TextBoxFilter'); - await locatorFilter.fill(moveLocatorKKFA); - await locatorFilter.blur(); - await expect(page.getByTestId('locator-0')).toBeVisible(); - }); - - test('when origin duty location GBLOC does not match services counselor GBLOC', async ({ page }) => { - const locatorFilter = await page.getByTestId('locator').getByTestId('TextBoxFilter'); - await locatorFilter.fill(moveLocatorCNNQ); - await locatorFilter.blur(); - await expect(page.getByTestId('locator-0')).not.toBeVisible(); - }); + test.describe('GBLOC tests', () => { + test.describe('Origin Duty Location', () => { + let moveLocatorKKFA = ''; + let moveLocatorCNNQ = ''; + test.beforeEach(async ({ scPage }) => { + const moveKKFA = await scPage.testHarness.buildHHGMoveNeedsSC(); + moveLocatorKKFA = moveKKFA.locator; + const moveCNNQ = await scPage.testHarness.buildHHGMoveNeedsSC(); + moveLocatorCNNQ = moveCNNQ.locator; + }); + + test('when origin duty location GBLOC matches services counselor GBLOC', async ({ page }) => { + const locatorFilter = await page.getByTestId('locator').getByTestId('TextBoxFilter'); + await locatorFilter.fill(moveLocatorKKFA); + await locatorFilter.blur(); + await expect(page.getByTestId('locator-0')).toBeVisible(); + }); + + test('when origin duty location GBLOC does not match services counselor GBLOC', async ({ page }) => { + const locatorFilter = await page.getByTestId('locator').getByTestId('TextBoxFilter'); + await locatorFilter.fill(moveLocatorCNNQ); + await locatorFilter.blur(); + await expect(page.getByTestId('locator-0')).not.toBeVisible(); }); }); + }); + test.describe('with basic HHG move', () => { test.beforeEach(async ({ scPage }) => { const move = await scPage.testHarness.buildHHGMoveNeedsSC(); await scPage.navigateToMove(move.locator); From 9ffdd19d6e5a948712ce376816f9f89e60a189c1 Mon Sep 17 00:00:00 2001 From: AaronW Date: Tue, 7 Jan 2025 21:42:11 +0000 Subject: [PATCH 24/54] added the rest of the changes in the errors file --- pkg/models/errors.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pkg/models/errors.go b/pkg/models/errors.go index 498cf03e771..2a1cdeeff0e 100644 --- a/pkg/models/errors.go +++ b/pkg/models/errors.go @@ -54,3 +54,12 @@ var ErrInvalidMoveID = errors.New("INVALID_MOVE_ID") // ErrInvalidOrderID is used if a argument is provided in cases where a order ID is provided, but may be malformed, empty, or nonexistent var ErrInvalidOrderID = errors.New("INVALID_ORDER_ID") + +// ErrSqlRecordNotFound is used if an error is returned from the database indicating that no record was found +var ErrSqlRecordNotFound = errors.New("RECORD_NOT_FOUND") + +// ErrMissingDestinationAddress is used if destination address is missing from the shipment +var ErrMissingDestinationAddress = errors.New("DESTINATION_ADDRESS_MISSING") + +// ErrUnsupportedShipmentType is used if the shipment type is not supported by a method +var ErrUnsupportedShipmentType = errors.New("UNSUPPORTED_SHIPMENT_TYPE") From 6a767151b3bb4b09802b7b3c19fc4286319e537e Mon Sep 17 00:00:00 2001 From: Alex Lusk Date: Tue, 7 Jan 2025 23:12:21 +0000 Subject: [PATCH 25/54] Add counseling office to orders list component --- .../Office/DefinitionLists/OrdersList.jsx | 10 +++++-- .../DefinitionLists/OrdersList.stories.jsx | 18 +++++++++++ .../DefinitionLists/OrdersList.test.jsx | 9 +++++- src/pages/Office/MoveDetails/MoveDetails.jsx | 2 +- .../ServicesCounselingMoveDetails.jsx | 2 +- src/setupProxy.js | 30 +++++++++---------- 6 files changed, 51 insertions(+), 20 deletions(-) diff --git a/src/components/Office/DefinitionLists/OrdersList.jsx b/src/components/Office/DefinitionLists/OrdersList.jsx index 46ec027d40e..f915abc480c 100644 --- a/src/components/Office/DefinitionLists/OrdersList.jsx +++ b/src/components/Office/DefinitionLists/OrdersList.jsx @@ -15,7 +15,7 @@ import { ordersTypeDetailReadable, } from 'utils/formatters'; -const OrdersList = ({ ordersInfo, showMissingWarnings }) => { +const OrdersList = ({ ordersInfo, moveInfo, showMissingWarnings }) => { const { ordersType } = ordersInfo; const isRetiree = ordersType === 'RETIREMENT'; const isSeparatee = ordersType === 'SEPARATION'; @@ -57,6 +57,12 @@ const OrdersList = ({ ordersInfo, showMissingWarnings }) => {
Current duty location
{ordersInfo.currentDutyLocation?.name}
+
+
Counseling office
+
+ {moveInfo.counselingOffice?.name ? moveInfo.counselingOffice?.name : '—'} +
+
{ {isRetiree || isSeparatee ? 'HOR, HOS, or PLEAD' : 'New duty location'}
- {ordersInfo.newDutyLocation?.name ? ordersInfo.newDutyLocation?.name : '-'} + {ordersInfo.newDutyLocation?.name ? ordersInfo.newDutyLocation?.name : '—'}
( NTStac: text('ordersInfo.NTStac', '9999'), payGrade: text('ordersInfo.payGrade', 'E_5'), }} + moveInfo={{ + name: 'PPPO Los Angeles SFB - USAF', + }} />
); @@ -81,6 +84,9 @@ export const AsServiceCounselor = () => ( NTStac: '', payGrade: text('ordersInfo.payGrade', 'E_5'), }} + moveInfo={{ + name: 'PPPO Los Angeles SFB - USAF', + }} /> ); @@ -105,6 +111,9 @@ export const AsServiceCounselorProcessingRetirement = () => ( NTStac: '', payGrade: text('ordersInfo.payGrade', 'E_5'), }} + moveInfo={{ + name: 'PPPO Los Angeles SFB - USAF', + }} /> ); @@ -129,6 +138,9 @@ export const AsServiceCounselorProcessingSeparation = () => ( NTStac: '', payGrade: text('ordersInfo.payGrade', 'E_5'), }} + moveInfo={{ + name: 'PPPO Los Angeles SFB - USAF', + }} /> ); @@ -166,6 +178,9 @@ export const AsTOO = () => ( NTStac: '', payGrade: text('ordersInfo.payGrade', 'E_5'), }} + moveInfo={{ + name: 'PPPO Los Angeles SFB - USAF', + }} /> ); @@ -212,6 +227,9 @@ export const AsTOOProcessingSeparation = () => ( NTStac: '', payGrade: text('ordersInfo.payGrade', 'E_5'), }} + moveInfo={{ + name: 'PPPO Los Angeles SFB - USAF', + }} /> ); diff --git a/src/components/Office/DefinitionLists/OrdersList.test.jsx b/src/components/Office/DefinitionLists/OrdersList.test.jsx index 586c0d1bfab..107463b7a1c 100644 --- a/src/components/Office/DefinitionLists/OrdersList.test.jsx +++ b/src/components/Office/DefinitionLists/OrdersList.test.jsx @@ -32,9 +32,16 @@ const ordersInfo = { payGrade: 'E_7', }; +const moveInfo = { + counselingOffice: { + name: 'PPPO Los Angeles SFB - USAF', + }, +}; + // what ordersInfo from above should be rendered as const expectedRenderedOrdersInfo = { currentDutyLocation: 'JBSA Lackland', + counselingOffice: 'PPPO Los Angeles SFB - USAF', newDutyLocation: 'JB Lewis-McChord', issuedDate: '08 Mar 2020', reportByDate: '01 Apr 2020', @@ -65,7 +72,7 @@ const ordersInfoMissing = { describe('OrdersList', () => { it('renders formatted orders info', () => { - render(); + render(); Object.keys(expectedRenderedOrdersInfo).forEach((key) => { expect(screen.getByText(expectedRenderedOrdersInfo[key])).toBeInTheDocument(); }); diff --git a/src/pages/Office/MoveDetails/MoveDetails.jsx b/src/pages/Office/MoveDetails/MoveDetails.jsx index d47156e3dc0..3ee4c4d3dcf 100644 --- a/src/pages/Office/MoveDetails/MoveDetails.jsx +++ b/src/pages/Office/MoveDetails/MoveDetails.jsx @@ -638,7 +638,7 @@ const MoveDetails = ({ } shipmentsInfoNonPpm={shipmentsInfoNonPPM} > - +
diff --git a/src/pages/Office/ServicesCounselingMoveDetails/ServicesCounselingMoveDetails.jsx b/src/pages/Office/ServicesCounselingMoveDetails/ServicesCounselingMoveDetails.jsx index 82e50380a2f..2c6bc7cef99 100644 --- a/src/pages/Office/ServicesCounselingMoveDetails/ServicesCounselingMoveDetails.jsx +++ b/src/pages/Office/ServicesCounselingMoveDetails/ServicesCounselingMoveDetails.jsx @@ -878,7 +878,7 @@ const ServicesCounselingMoveDetails = ({ } ppmShipmentInfoNeedsApproval={ppmShipmentsInfoNeedsApproval} > - +
diff --git a/src/setupProxy.js b/src/setupProxy.js index 3a6b0f36484..7d92e2f9aea 100644 --- a/src/setupProxy.js +++ b/src/setupProxy.js @@ -1,19 +1,19 @@ const { createProxyMiddleware } = require('http-proxy-middleware'); module.exports = (app) => { - app.use(createProxyMiddleware('/api', { target: 'http://milmovelocal:8080/' })); - app.use(createProxyMiddleware('/internal', { target: 'http://milmovelocal:8080/' })); - app.use(createProxyMiddleware('/admin', { target: 'http://milmovelocal:8080/' })); - app.use(createProxyMiddleware('/ghc', { target: 'http://milmovelocal:8080/' })); - app.use(createProxyMiddleware('/prime', { target: 'http://milmovelocal:8080/' })); - app.use(createProxyMiddleware('/pptas', { target: 'http://milmovelocal:8080/' })); - app.use(createProxyMiddleware('/support', { target: 'http://milmovelocal:8080/' })); - app.use(createProxyMiddleware('/testharness', { target: 'http://milmovelocal:8080/' })); - app.use(createProxyMiddleware('/storage', { target: 'http://milmovelocal:8080/' })); - app.use(createProxyMiddleware('/devlocal-auth', { target: 'http://milmovelocal:8080/' })); - app.use(createProxyMiddleware('/auth/**', { target: 'http://milmovelocal:8080/' })); - app.use(createProxyMiddleware('/logout', { target: 'http://milmovelocal:8080/' })); - app.use(createProxyMiddleware('/downloads', { target: 'http://milmovelocal:8080/' })); - app.use(createProxyMiddleware('/debug/**', { target: 'http://milmovelocal:8080/' })); - app.use(createProxyMiddleware('/client/**', { target: 'http://milmovelocal:8080/' })); + app.use('/api', createProxyMiddleware({ target: 'http://milmovelocal:8080/api' })); + app.use('/internal', createProxyMiddleware({ target: 'http://milmovelocal:8080/internal' })); + app.use('/admin', createProxyMiddleware({ target: 'http://milmovelocal:8080/admin' })); + app.use('/ghc', createProxyMiddleware({ target: 'http://milmovelocal:8080/ghc' })); + app.use('/prime', createProxyMiddleware({ target: 'http://milmovelocal:8080/prime' })); + app.use('/pptas', createProxyMiddleware({ target: 'http://milmovelocal:8080/pptas' })); + app.use('/support', createProxyMiddleware({ target: 'http://milmovelocal:8080/support' })); + app.use('/testharness', createProxyMiddleware({ target: 'http://milmovelocal:8080/testharness' })); + app.use('/storage', createProxyMiddleware({ target: 'http://milmovelocal:8080/storage' })); + app.use('/devlocal-auth', createProxyMiddleware({ target: 'http://milmovelocal:8080/devlocal-auth' })); + app.use('/auth', createProxyMiddleware({ target: 'http://milmovelocal:8080/auth' })); + app.use('/logout', createProxyMiddleware({ target: 'http://milmovelocal:8080/logout' })); + app.use('/downloads', createProxyMiddleware({ target: 'http://milmovelocal:8080/downloads' })); + app.use('/debug', createProxyMiddleware({ target: 'http://milmovelocal:8080/debug' })); + app.use('/client', createProxyMiddleware({ target: 'http://milmovelocal:8080/client' })); }; From a9755ee1ed5e8d2f4e8926cd3efd148fa1089a86 Mon Sep 17 00:00:00 2001 From: AaronW Date: Tue, 7 Jan 2025 23:47:59 +0000 Subject: [PATCH 26/54] brought in all changes from B-21581 --- pkg/factory/order_factory.go | 8 +- pkg/gen/ghcapi/embedded_spec.go | 32 +- pkg/gen/ghcmessages/list_prime_move.go | 8 + pkg/gen/ghcmessages/search_move.go | 34 +- pkg/gen/primeapi/embedded_spec.go | 36 +++ pkg/gen/primemessages/list_move.go | 8 + pkg/gen/primemessages/move_task_order.go | 54 ++++ pkg/gen/primev2api/embedded_spec.go | 20 ++ pkg/gen/primev2messages/move_task_order.go | 54 ++++ pkg/gen/primev3api/embedded_spec.go | 20 ++ pkg/gen/primev3messages/move_task_order.go | 54 ++++ .../internal/payloads/model_to_payload.go | 47 +-- pkg/handlers/ghcapi/move_test.go | 303 +++++++++++++++++- pkg/handlers/ghcapi/orders.go | 4 +- pkg/handlers/primeapi/move_task_order.go | 6 +- .../primeapi/payloads/model_to_payload.go | 57 +++- .../payloads/model_to_payload_test.go | 51 ++- pkg/handlers/primeapiv2/move_task_order.go | 2 +- .../primeapiv2/payloads/model_to_payload.go | 17 +- .../payloads/model_to_payload_test.go | 51 ++- pkg/handlers/primeapiv3/move_task_order.go | 2 +- .../primeapiv3/payloads/model_to_payload.go | 21 +- .../payloads/model_to_payload_test.go | 59 +++- pkg/models/move.go | 44 +++ pkg/models/mto_shipments.go | 57 ++++ pkg/models/order.go | 198 ++++++++++++ pkg/models/service_member.go | 4 +- src/components/Table/SearchResultsTable.jsx | 2 +- .../definitions/prime/MoveTaskOrder.yaml | 8 + .../definitions/prime/v2/MoveTaskOrder.yaml | 8 + .../definitions/prime/v3/MoveTaskOrder.yaml | 8 + swagger-def/ghc.yaml | 8 +- swagger-def/prime.yaml | 6 + swagger/ghc.yaml | 8 +- swagger/prime.yaml | 14 + swagger/prime_v2.yaml | 8 + swagger/prime_v3.yaml | 8 + 37 files changed, 1239 insertions(+), 90 deletions(-) diff --git a/pkg/factory/order_factory.go b/pkg/factory/order_factory.go index d6f2165e095..9bc3c61fb1d 100644 --- a/pkg/factory/order_factory.go +++ b/pkg/factory/order_factory.go @@ -203,8 +203,8 @@ func buildOrderWithBuildType(db *pop.Connection, customs []Customization, traits defaultSpouseHasProGear := false defaultOrdersType := internalmessages.OrdersTypePERMANENTCHANGEOFSTATION defaultOrdersTypeDetail := internalmessages.OrdersTypeDetail("HHG_PERMITTED") - defaultDestinationDutyLocationGbloc := "AGFM" - destinationDutyLocationGbloc := &defaultDestinationDutyLocationGbloc + defaultDestinationGbloc := "AGFM" + destinationGbloc := &defaultDestinationGbloc testYear := 2018 defaultIssueDate := time.Date(testYear, time.March, 15, 0, 0, 0, 0, time.UTC) defaultReportByDate := time.Date(testYear, time.August, 1, 0, 0, 0, 0, time.UTC) @@ -248,7 +248,7 @@ func buildOrderWithBuildType(db *pop.Connection, customs []Customization, traits log.Panicf("Error loading duty location by id %s: %s\n", newDutyLocation.ID.String(), err) } destinationPostalCodeToGBLOC := FetchOrBuildPostalCodeToGBLOC(db, newDutyLocation.Address.PostalCode, "AGFM") - destinationDutyLocationGbloc = &destinationPostalCodeToGBLOC.GBLOC + destinationGbloc = &destinationPostalCodeToGBLOC.GBLOC } } @@ -257,7 +257,7 @@ func buildOrderWithBuildType(db *pop.Connection, customs []Customization, traits ServiceMemberID: serviceMember.ID, NewDutyLocation: newDutyLocation, NewDutyLocationID: newDutyLocation.ID, - DestinationGBLOC: destinationDutyLocationGbloc, + DestinationGBLOC: destinationGbloc, UploadedOrders: uploadedOrders, UploadedOrdersID: uploadedOrders.ID, IssueDate: defaultIssueDate, diff --git a/pkg/gen/ghcapi/embedded_spec.go b/pkg/gen/ghcapi/embedded_spec.go index 361f666c222..bedc53f5951 100644 --- a/pkg/gen/ghcapi/embedded_spec.go +++ b/pkg/gen/ghcapi/embedded_spec.go @@ -8944,6 +8944,14 @@ func init() { "format": "date-time", "readOnly": true }, + "destinationGBLOC": { + "type": "string", + "example": "AGFM" + }, + "destinationPostalCode": { + "type": "string", + "example": "90210" + }, "eTag": { "type": "string", "readOnly": true @@ -13148,16 +13156,16 @@ func init() { "branch": { "type": "string" }, - "destinationDutyLocationPostalCode": { + "destinationGBLOC": { + "$ref": "#/definitions/GBLOC" + }, + "destinationPostalCode": { "type": "string", "format": "zip", "title": "ZIP", "pattern": "^(\\d{5})$", "example": "90210" }, - "destinationGBLOC": { - "$ref": "#/definitions/GBLOC" - }, "edipi": { "type": "string", "x-nullable": true, @@ -25688,6 +25696,14 @@ func init() { "format": "date-time", "readOnly": true }, + "destinationGBLOC": { + "type": "string", + "example": "AGFM" + }, + "destinationPostalCode": { + "type": "string", + "example": "90210" + }, "eTag": { "type": "string", "readOnly": true @@ -30018,16 +30034,16 @@ func init() { "branch": { "type": "string" }, - "destinationDutyLocationPostalCode": { + "destinationGBLOC": { + "$ref": "#/definitions/GBLOC" + }, + "destinationPostalCode": { "type": "string", "format": "zip", "title": "ZIP", "pattern": "^(\\d{5})$", "example": "90210" }, - "destinationGBLOC": { - "$ref": "#/definitions/GBLOC" - }, "edipi": { "type": "string", "x-nullable": true, diff --git a/pkg/gen/ghcmessages/list_prime_move.go b/pkg/gen/ghcmessages/list_prime_move.go index 22a8f47b561..690f8097816 100644 --- a/pkg/gen/ghcmessages/list_prime_move.go +++ b/pkg/gen/ghcmessages/list_prime_move.go @@ -35,6 +35,14 @@ type ListPrimeMove struct { // Format: date-time CreatedAt strfmt.DateTime `json:"createdAt,omitempty"` + // destination g b l o c + // Example: AGFM + DestinationGBLOC string `json:"destinationGBLOC,omitempty"` + + // destination postal code + // Example: 90210 + DestinationPostalCode string `json:"destinationPostalCode,omitempty"` + // e tag // Read Only: true ETag string `json:"eTag,omitempty"` diff --git a/pkg/gen/ghcmessages/search_move.go b/pkg/gen/ghcmessages/search_move.go index c6690995151..f0f91015deb 100644 --- a/pkg/gen/ghcmessages/search_move.go +++ b/pkg/gen/ghcmessages/search_move.go @@ -22,13 +22,13 @@ type SearchMove struct { // branch Branch string `json:"branch,omitempty"` + // destination g b l o c + DestinationGBLOC GBLOC `json:"destinationGBLOC,omitempty"` + // ZIP // Example: 90210 // Pattern: ^(\d{5})$ - DestinationDutyLocationPostalCode string `json:"destinationDutyLocationPostalCode,omitempty"` - - // destination g b l o c - DestinationGBLOC GBLOC `json:"destinationGBLOC,omitempty"` + DestinationPostalCode string `json:"destinationPostalCode,omitempty"` // edipi // Example: 1234567890 @@ -94,11 +94,11 @@ type SearchMove struct { func (m *SearchMove) Validate(formats strfmt.Registry) error { var res []error - if err := m.validateDestinationDutyLocationPostalCode(formats); err != nil { + if err := m.validateDestinationGBLOC(formats); err != nil { res = append(res, err) } - if err := m.validateDestinationGBLOC(formats); err != nil { + if err := m.validateDestinationPostalCode(formats); err != nil { res = append(res, err) } @@ -140,29 +140,29 @@ func (m *SearchMove) Validate(formats strfmt.Registry) error { return nil } -func (m *SearchMove) validateDestinationDutyLocationPostalCode(formats strfmt.Registry) error { - if swag.IsZero(m.DestinationDutyLocationPostalCode) { // not required +func (m *SearchMove) validateDestinationGBLOC(formats strfmt.Registry) error { + if swag.IsZero(m.DestinationGBLOC) { // not required return nil } - if err := validate.Pattern("destinationDutyLocationPostalCode", "body", m.DestinationDutyLocationPostalCode, `^(\d{5})$`); err != nil { + if err := m.DestinationGBLOC.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("destinationGBLOC") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("destinationGBLOC") + } return err } return nil } -func (m *SearchMove) validateDestinationGBLOC(formats strfmt.Registry) error { - if swag.IsZero(m.DestinationGBLOC) { // not required +func (m *SearchMove) validateDestinationPostalCode(formats strfmt.Registry) error { + if swag.IsZero(m.DestinationPostalCode) { // not required return nil } - if err := m.DestinationGBLOC.Validate(formats); err != nil { - if ve, ok := err.(*errors.Validation); ok { - return ve.ValidateName("destinationGBLOC") - } else if ce, ok := err.(*errors.CompositeError); ok { - return ce.ValidateName("destinationGBLOC") - } + if err := validate.Pattern("destinationPostalCode", "body", m.DestinationPostalCode, `^(\d{5})$`); err != nil { return err } diff --git a/pkg/gen/primeapi/embedded_spec.go b/pkg/gen/primeapi/embedded_spec.go index 52039d783cc..4f00b706379 100644 --- a/pkg/gen/primeapi/embedded_spec.go +++ b/pkg/gen/primeapi/embedded_spec.go @@ -1885,6 +1885,14 @@ func init() { "format": "date-time", "readOnly": true }, + "destinationGBLOC": { + "type": "string", + "example": "AGFM" + }, + "destinationPostalCode": { + "type": "string", + "example": "90210" + }, "eTag": { "type": "string", "readOnly": true @@ -2847,6 +2855,16 @@ func init() { "format": "date-time", "readOnly": true }, + "destinationGBLOC": { + "type": "string", + "readOnly": true, + "example": "KKFA" + }, + "destinationPostalCode": { + "type": "string", + "readOnly": true, + "example": "90210" + }, "eTag": { "type": "string", "readOnly": true @@ -6779,6 +6797,14 @@ func init() { "format": "date-time", "readOnly": true }, + "destinationGBLOC": { + "type": "string", + "example": "AGFM" + }, + "destinationPostalCode": { + "type": "string", + "example": "90210" + }, "eTag": { "type": "string", "readOnly": true @@ -7741,6 +7767,16 @@ func init() { "format": "date-time", "readOnly": true }, + "destinationGBLOC": { + "type": "string", + "readOnly": true, + "example": "KKFA" + }, + "destinationPostalCode": { + "type": "string", + "readOnly": true, + "example": "90210" + }, "eTag": { "type": "string", "readOnly": true diff --git a/pkg/gen/primemessages/list_move.go b/pkg/gen/primemessages/list_move.go index 4e9af4f1e52..866dd6e8810 100644 --- a/pkg/gen/primemessages/list_move.go +++ b/pkg/gen/primemessages/list_move.go @@ -38,6 +38,14 @@ type ListMove struct { // Format: date-time CreatedAt strfmt.DateTime `json:"createdAt,omitempty"` + // destination g b l o c + // Example: AGFM + DestinationGBLOC string `json:"destinationGBLOC,omitempty"` + + // destination postal code + // Example: 90210 + DestinationPostalCode string `json:"destinationPostalCode,omitempty"` + // e tag // Read Only: true ETag string `json:"eTag,omitempty"` diff --git a/pkg/gen/primemessages/move_task_order.go b/pkg/gen/primemessages/move_task_order.go index 3d5f5854487..befdf8fe3d0 100644 --- a/pkg/gen/primemessages/move_task_order.go +++ b/pkg/gen/primemessages/move_task_order.go @@ -39,6 +39,16 @@ type MoveTaskOrder struct { // Format: date-time CreatedAt strfmt.DateTime `json:"createdAt,omitempty"` + // destination g b l o c + // Example: KKFA + // Read Only: true + DestinationGBLOC string `json:"destinationGBLOC,omitempty"` + + // destination postal code + // Example: 90210 + // Read Only: true + DestinationPostalCode string `json:"destinationPostalCode,omitempty"` + // e tag // Read Only: true ETag string `json:"eTag,omitempty"` @@ -127,6 +137,10 @@ func (m *MoveTaskOrder) UnmarshalJSON(raw []byte) error { CreatedAt strfmt.DateTime `json:"createdAt,omitempty"` + DestinationGBLOC string `json:"destinationGBLOC,omitempty"` + + DestinationPostalCode string `json:"destinationPostalCode,omitempty"` + ETag string `json:"eTag,omitempty"` ExcessWeightAcknowledgedAt *strfmt.DateTime `json:"excessWeightAcknowledgedAt"` @@ -183,6 +197,12 @@ func (m *MoveTaskOrder) UnmarshalJSON(raw []byte) error { // createdAt result.CreatedAt = data.CreatedAt + // destinationGBLOC + result.DestinationGBLOC = data.DestinationGBLOC + + // destinationPostalCode + result.DestinationPostalCode = data.DestinationPostalCode + // eTag result.ETag = data.ETag @@ -247,6 +267,10 @@ func (m MoveTaskOrder) MarshalJSON() ([]byte, error) { CreatedAt strfmt.DateTime `json:"createdAt,omitempty"` + DestinationGBLOC string `json:"destinationGBLOC,omitempty"` + + DestinationPostalCode string `json:"destinationPostalCode,omitempty"` + ETag string `json:"eTag,omitempty"` ExcessWeightAcknowledgedAt *strfmt.DateTime `json:"excessWeightAcknowledgedAt"` @@ -284,6 +308,10 @@ func (m MoveTaskOrder) MarshalJSON() ([]byte, error) { CreatedAt: m.CreatedAt, + DestinationGBLOC: m.DestinationGBLOC, + + DestinationPostalCode: m.DestinationPostalCode, + ETag: m.ETag, ExcessWeightAcknowledgedAt: m.ExcessWeightAcknowledgedAt, @@ -655,6 +683,14 @@ func (m *MoveTaskOrder) ContextValidate(ctx context.Context, formats strfmt.Regi res = append(res, err) } + if err := m.contextValidateDestinationGBLOC(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateDestinationPostalCode(ctx, formats); err != nil { + res = append(res, err) + } + if err := m.contextValidateETag(ctx, formats); err != nil { res = append(res, err) } @@ -732,6 +768,24 @@ func (m *MoveTaskOrder) contextValidateCreatedAt(ctx context.Context, formats st return nil } +func (m *MoveTaskOrder) contextValidateDestinationGBLOC(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "destinationGBLOC", "body", string(m.DestinationGBLOC)); err != nil { + return err + } + + return nil +} + +func (m *MoveTaskOrder) contextValidateDestinationPostalCode(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "destinationPostalCode", "body", string(m.DestinationPostalCode)); err != nil { + return err + } + + return nil +} + func (m *MoveTaskOrder) contextValidateETag(ctx context.Context, formats strfmt.Registry) error { if err := validate.ReadOnly(ctx, "eTag", "body", string(m.ETag)); err != nil { diff --git a/pkg/gen/primev2api/embedded_spec.go b/pkg/gen/primev2api/embedded_spec.go index f0468e10884..c6c054f45ec 100644 --- a/pkg/gen/primev2api/embedded_spec.go +++ b/pkg/gen/primev2api/embedded_spec.go @@ -1957,6 +1957,16 @@ func init() { "format": "date-time", "readOnly": true }, + "destinationGBLOC": { + "type": "string", + "readOnly": true, + "example": "KKFA" + }, + "destinationPostalCode": { + "type": "string", + "readOnly": true, + "example": "90210" + }, "eTag": { "type": "string", "readOnly": true @@ -5564,6 +5574,16 @@ func init() { "format": "date-time", "readOnly": true }, + "destinationGBLOC": { + "type": "string", + "readOnly": true, + "example": "KKFA" + }, + "destinationPostalCode": { + "type": "string", + "readOnly": true, + "example": "90210" + }, "eTag": { "type": "string", "readOnly": true diff --git a/pkg/gen/primev2messages/move_task_order.go b/pkg/gen/primev2messages/move_task_order.go index af1390e30c7..c5e6cf21146 100644 --- a/pkg/gen/primev2messages/move_task_order.go +++ b/pkg/gen/primev2messages/move_task_order.go @@ -43,6 +43,16 @@ type MoveTaskOrder struct { // Format: date-time CreatedAt strfmt.DateTime `json:"createdAt,omitempty"` + // destination g b l o c + // Example: KKFA + // Read Only: true + DestinationGBLOC string `json:"destinationGBLOC,omitempty"` + + // destination postal code + // Example: 90210 + // Read Only: true + DestinationPostalCode string `json:"destinationPostalCode,omitempty"` + // e tag // Read Only: true ETag string `json:"eTag,omitempty"` @@ -133,6 +143,10 @@ func (m *MoveTaskOrder) UnmarshalJSON(raw []byte) error { CreatedAt strfmt.DateTime `json:"createdAt,omitempty"` + DestinationGBLOC string `json:"destinationGBLOC,omitempty"` + + DestinationPostalCode string `json:"destinationPostalCode,omitempty"` + ETag string `json:"eTag,omitempty"` ExcessWeightAcknowledgedAt *strfmt.DateTime `json:"excessWeightAcknowledgedAt"` @@ -192,6 +206,12 @@ func (m *MoveTaskOrder) UnmarshalJSON(raw []byte) error { // createdAt result.CreatedAt = data.CreatedAt + // destinationGBLOC + result.DestinationGBLOC = data.DestinationGBLOC + + // destinationPostalCode + result.DestinationPostalCode = data.DestinationPostalCode + // eTag result.ETag = data.ETag @@ -258,6 +278,10 @@ func (m MoveTaskOrder) MarshalJSON() ([]byte, error) { CreatedAt strfmt.DateTime `json:"createdAt,omitempty"` + DestinationGBLOC string `json:"destinationGBLOC,omitempty"` + + DestinationPostalCode string `json:"destinationPostalCode,omitempty"` + ETag string `json:"eTag,omitempty"` ExcessWeightAcknowledgedAt *strfmt.DateTime `json:"excessWeightAcknowledgedAt"` @@ -297,6 +321,10 @@ func (m MoveTaskOrder) MarshalJSON() ([]byte, error) { CreatedAt: m.CreatedAt, + DestinationGBLOC: m.DestinationGBLOC, + + DestinationPostalCode: m.DestinationPostalCode, + ETag: m.ETag, ExcessWeightAcknowledgedAt: m.ExcessWeightAcknowledgedAt, @@ -672,6 +700,14 @@ func (m *MoveTaskOrder) ContextValidate(ctx context.Context, formats strfmt.Regi res = append(res, err) } + if err := m.contextValidateDestinationGBLOC(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateDestinationPostalCode(ctx, formats); err != nil { + res = append(res, err) + } + if err := m.contextValidateETag(ctx, formats); err != nil { res = append(res, err) } @@ -758,6 +794,24 @@ func (m *MoveTaskOrder) contextValidateCreatedAt(ctx context.Context, formats st return nil } +func (m *MoveTaskOrder) contextValidateDestinationGBLOC(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "destinationGBLOC", "body", string(m.DestinationGBLOC)); err != nil { + return err + } + + return nil +} + +func (m *MoveTaskOrder) contextValidateDestinationPostalCode(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "destinationPostalCode", "body", string(m.DestinationPostalCode)); err != nil { + return err + } + + return nil +} + func (m *MoveTaskOrder) contextValidateETag(ctx context.Context, formats strfmt.Registry) error { if err := validate.ReadOnly(ctx, "eTag", "body", string(m.ETag)); err != nil { diff --git a/pkg/gen/primev3api/embedded_spec.go b/pkg/gen/primev3api/embedded_spec.go index 22f6c879b42..424ab464dbf 100644 --- a/pkg/gen/primev3api/embedded_spec.go +++ b/pkg/gen/primev3api/embedded_spec.go @@ -2191,6 +2191,16 @@ func init() { "format": "date-time", "readOnly": true }, + "destinationGBLOC": { + "type": "string", + "readOnly": true, + "example": "KKFA" + }, + "destinationPostalCode": { + "type": "string", + "readOnly": true, + "example": "90210" + }, "eTag": { "type": "string", "readOnly": true @@ -6483,6 +6493,16 @@ func init() { "format": "date-time", "readOnly": true }, + "destinationGBLOC": { + "type": "string", + "readOnly": true, + "example": "KKFA" + }, + "destinationPostalCode": { + "type": "string", + "readOnly": true, + "example": "90210" + }, "eTag": { "type": "string", "readOnly": true diff --git a/pkg/gen/primev3messages/move_task_order.go b/pkg/gen/primev3messages/move_task_order.go index 4d295abc872..02b991a0a16 100644 --- a/pkg/gen/primev3messages/move_task_order.go +++ b/pkg/gen/primev3messages/move_task_order.go @@ -43,6 +43,16 @@ type MoveTaskOrder struct { // Format: date-time CreatedAt strfmt.DateTime `json:"createdAt,omitempty"` + // destination g b l o c + // Example: KKFA + // Read Only: true + DestinationGBLOC string `json:"destinationGBLOC,omitempty"` + + // destination postal code + // Example: 90210 + // Read Only: true + DestinationPostalCode string `json:"destinationPostalCode,omitempty"` + // e tag // Read Only: true ETag string `json:"eTag,omitempty"` @@ -133,6 +143,10 @@ func (m *MoveTaskOrder) UnmarshalJSON(raw []byte) error { CreatedAt strfmt.DateTime `json:"createdAt,omitempty"` + DestinationGBLOC string `json:"destinationGBLOC,omitempty"` + + DestinationPostalCode string `json:"destinationPostalCode,omitempty"` + ETag string `json:"eTag,omitempty"` ExcessWeightAcknowledgedAt *strfmt.DateTime `json:"excessWeightAcknowledgedAt"` @@ -192,6 +206,12 @@ func (m *MoveTaskOrder) UnmarshalJSON(raw []byte) error { // createdAt result.CreatedAt = data.CreatedAt + // destinationGBLOC + result.DestinationGBLOC = data.DestinationGBLOC + + // destinationPostalCode + result.DestinationPostalCode = data.DestinationPostalCode + // eTag result.ETag = data.ETag @@ -258,6 +278,10 @@ func (m MoveTaskOrder) MarshalJSON() ([]byte, error) { CreatedAt strfmt.DateTime `json:"createdAt,omitempty"` + DestinationGBLOC string `json:"destinationGBLOC,omitempty"` + + DestinationPostalCode string `json:"destinationPostalCode,omitempty"` + ETag string `json:"eTag,omitempty"` ExcessWeightAcknowledgedAt *strfmt.DateTime `json:"excessWeightAcknowledgedAt"` @@ -297,6 +321,10 @@ func (m MoveTaskOrder) MarshalJSON() ([]byte, error) { CreatedAt: m.CreatedAt, + DestinationGBLOC: m.DestinationGBLOC, + + DestinationPostalCode: m.DestinationPostalCode, + ETag: m.ETag, ExcessWeightAcknowledgedAt: m.ExcessWeightAcknowledgedAt, @@ -672,6 +700,14 @@ func (m *MoveTaskOrder) ContextValidate(ctx context.Context, formats strfmt.Regi res = append(res, err) } + if err := m.contextValidateDestinationGBLOC(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateDestinationPostalCode(ctx, formats); err != nil { + res = append(res, err) + } + if err := m.contextValidateETag(ctx, formats); err != nil { res = append(res, err) } @@ -758,6 +794,24 @@ func (m *MoveTaskOrder) contextValidateCreatedAt(ctx context.Context, formats st return nil } +func (m *MoveTaskOrder) contextValidateDestinationGBLOC(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "destinationGBLOC", "body", string(m.DestinationGBLOC)); err != nil { + return err + } + + return nil +} + +func (m *MoveTaskOrder) contextValidateDestinationPostalCode(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "destinationPostalCode", "body", string(m.DestinationPostalCode)); err != nil { + return err + } + + return nil +} + func (m *MoveTaskOrder) contextValidateETag(ctx context.Context, formats strfmt.Registry) error { if err := validate.ReadOnly(ctx, "eTag", "body", string(m.ETag)); err != nil { diff --git a/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go b/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go index caa4aef11d5..8869d79511a 100644 --- a/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go +++ b/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go @@ -2260,20 +2260,21 @@ func QueueMoves(moves []models.Move, officeUsers []models.OfficeUser, requestedP deptIndicator = ghcmessages.DeptIndicator(*move.Orders.DepartmentIndicator) } - var gbloc string + var originGbloc string if move.Status == models.MoveStatusNeedsServiceCounseling { - gbloc = swag.StringValue(move.Orders.OriginDutyLocationGBLOC) + originGbloc = swag.StringValue(move.Orders.OriginDutyLocationGBLOC) } else if len(move.ShipmentGBLOC) > 0 && move.ShipmentGBLOC[0].GBLOC != nil { // There is a Pop bug that prevents us from using a has_one association for // Move.ShipmentGBLOC, so we have to treat move.ShipmentGBLOC as an array, even // though there can never be more than one GBLOC for a move. - gbloc = swag.StringValue(move.ShipmentGBLOC[0].GBLOC) + originGbloc = swag.StringValue(move.ShipmentGBLOC[0].GBLOC) } else { // If the move's first shipment doesn't have a pickup address (like with an NTS-Release), // we need to fall back to the origin duty location GBLOC. If that's not available for // some reason, then we should get the empty string (no GBLOC). - gbloc = swag.StringValue(move.Orders.OriginDutyLocationGBLOC) + originGbloc = swag.StringValue(move.Orders.OriginDutyLocationGBLOC) } + var closeoutLocation string if move.CloseoutOffice != nil { closeoutLocation = move.CloseoutOffice.Name @@ -2344,7 +2345,7 @@ func QueueMoves(moves []models.Move, officeUsers []models.OfficeUser, requestedP ShipmentsCount: int64(len(validMTOShipments)), OriginDutyLocation: DutyLocation(move.Orders.OriginDutyLocation), DestinationDutyLocation: DutyLocation(&move.Orders.NewDutyLocation), // #nosec G601 new in 1.22.2 - OriginGBLOC: ghcmessages.GBLOC(gbloc), + OriginGBLOC: ghcmessages.GBLOC(originGbloc), PpmType: move.PPMType, CloseoutInitiated: handlers.FmtDateTimePtr(&closeoutInitiated), CloseoutLocation: &closeoutLocation, @@ -2579,24 +2580,24 @@ func SearchMoves(appCtx appcontext.AppContext, moves models.Moves) *ghcmessages. } searchMoves[i] = &ghcmessages.SearchMove{ - FirstName: customer.FirstName, - LastName: customer.LastName, - Edipi: customer.Edipi, - Emplid: customer.Emplid, - Branch: customer.Affiliation.String(), - Status: ghcmessages.MoveStatus(move.Status), - ID: *handlers.FmtUUID(move.ID), - Locator: move.Locator, - ShipmentsCount: int64(numShipments), - OriginDutyLocationPostalCode: move.Orders.OriginDutyLocation.Address.PostalCode, - DestinationDutyLocationPostalCode: move.Orders.NewDutyLocation.Address.PostalCode, - OrderType: string(move.Orders.OrdersType), - RequestedPickupDate: pickupDate, - RequestedDeliveryDate: deliveryDate, - OriginGBLOC: ghcmessages.GBLOC(originGBLOC), - DestinationGBLOC: destinationGBLOC, - LockedByOfficeUserID: handlers.FmtUUIDPtr(move.LockedByOfficeUserID), - LockExpiresAt: handlers.FmtDateTimePtr(move.LockExpiresAt), + FirstName: customer.FirstName, + LastName: customer.LastName, + Edipi: customer.Edipi, + Emplid: customer.Emplid, + Branch: customer.Affiliation.String(), + Status: ghcmessages.MoveStatus(move.Status), + ID: *handlers.FmtUUID(move.ID), + Locator: move.Locator, + ShipmentsCount: int64(numShipments), + OriginDutyLocationPostalCode: move.Orders.OriginDutyLocation.Address.PostalCode, + DestinationPostalCode: move.Orders.NewDutyLocation.Address.PostalCode, + OrderType: string(move.Orders.OrdersType), + RequestedPickupDate: pickupDate, + RequestedDeliveryDate: deliveryDate, + OriginGBLOC: ghcmessages.GBLOC(originGBLOC), + DestinationGBLOC: destinationGBLOC, + LockedByOfficeUserID: handlers.FmtUUIDPtr(move.LockedByOfficeUserID), + LockExpiresAt: handlers.FmtDateTimePtr(move.LockExpiresAt), } } return &searchMoves diff --git a/pkg/handlers/ghcapi/move_test.go b/pkg/handlers/ghcapi/move_test.go index f345c7cfb85..3b9fda47401 100644 --- a/pkg/handlers/ghcapi/move_test.go +++ b/pkg/handlers/ghcapi/move_test.go @@ -290,6 +290,174 @@ func (suite *HandlerSuite) TestSearchMovesHandler() { } + /* setupGblocTestData is a helper function to set up test data for the search moves handler specifically for testing GBLOCs. + * returns a non-PPM move and a PPM move with different destination postal codes and GBLOCs. */ + setupGblocTestData := func() (*models.Move, *models.Move, *models.Move) { + // ZIPs takes a GBLOC and returns a ZIP + ZIPs := map[string]string{ + "AGFM": "62225", + "KKFA": "90210", + "BGNC": "47712", + "CLPK": "33009", + } + + for k, v := range ZIPs { + factory.FetchOrBuildPostalCodeToGBLOC(suite.DB(), v, k) + } + + serviceMember := factory.BuildServiceMember(suite.DB(), nil, nil) + + defaultPickupAddress := factory.BuildAddress(suite.DB(), nil, nil) + addressAGFM := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "123 Main St", + City: "Beverly Hills", + State: "CA", + PostalCode: ZIPs["AGFM"], + }, + }, + }, nil) + addressKKFA := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "123 Main St", + City: "Beverly Hills", + State: "CA", + PostalCode: ZIPs["KKFA"], + }, + }, + }, nil) + addressBGNC := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "123 Main St", + City: "Beverly Hills", + State: "CA", + PostalCode: ZIPs["BGNC"], + }, + }, + }, nil) + addressCLPK := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "123 Main St", + City: "Beverly Hills", + State: "CA", + PostalCode: ZIPs["CLPK"], + }, + }, + }, nil) + + destDutyLocationAGFM := factory.BuildDutyLocation(suite.DB(), []factory.Customization{ + { + Model: models.DutyLocation{ + Name: "Test AGFM", + AddressID: addressAGFM.ID, + }, + }, + }, nil) + destDutyLocationCLPK := factory.BuildDutyLocation(suite.DB(), []factory.Customization{ + { + Model: models.DutyLocation{ + Name: "Test CLPK", + AddressID: addressCLPK.ID, + }, + }, + }, nil) + + order := factory.BuildOrder(suite.DB(), []factory.Customization{ + { + Model: models.Order{ + ServiceMemberID: serviceMember.ID, + NewDutyLocationID: destDutyLocationAGFM.ID, + DestinationGBLOC: handlers.FmtString("AGFM"), + HasDependents: false, + SpouseHasProGear: false, + OrdersType: "PERMANENT_CHANGE_OF_STATION", + OrdersTypeDetail: nil, + }, + }, + }, nil) + + orderWithShipment := factory.BuildOrder(suite.DB(), []factory.Customization{ + { + Model: models.Order{ + ServiceMemberID: serviceMember.ID, + NewDutyLocationID: destDutyLocationAGFM.ID, + DestinationGBLOC: handlers.FmtString("AGFM"), + HasDependents: false, + SpouseHasProGear: false, + OrdersType: "PERMANENT_CHANGE_OF_STATION", + OrdersTypeDetail: nil, + }, + }, + }, nil) + + orderWithShipmentPPM := factory.BuildOrder(suite.DB(), []factory.Customization{ + { + Model: models.Order{ + ServiceMemberID: serviceMember.ID, + NewDutyLocationID: destDutyLocationCLPK.ID, + DestinationGBLOC: handlers.FmtString("CLPK"), + HasDependents: false, + SpouseHasProGear: false, + OrdersType: "PERMANENT_CHANGE_OF_STATION", + OrdersTypeDetail: nil, + }, + }, + }, nil) + + moveWithoutShipment := factory.BuildMove(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + OrdersID: order.ID, + }, + }, + }, nil) + + moveWithShipment := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + DestinationAddressID: &addressKKFA.ID, + PickupAddressID: &defaultPickupAddress.ID, + Status: models.MTOShipmentStatusSubmitted, + ShipmentType: models.MTOShipmentTypeHHG, + }, + }, + { + Model: models.Move{ + OrdersID: orderWithShipment.ID, + }, + }, + }, nil) + + moveWithShipmentPPM := factory.BuildMoveWithPPMShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusSUBMITTED, + OrdersID: orderWithShipmentPPM.ID, + }, + }, + { + Model: models.MTOShipment{ + Status: models.MTOShipmentStatusSubmitted, + DestinationAddressID: &addressBGNC.ID, + PickupAddressID: &defaultPickupAddress.ID, + }, + }, + { + Model: models.PPMShipment{ + Status: models.PPMShipmentStatusSubmitted, + DestinationAddressID: &addressBGNC.ID, + PickupAddressID: &defaultPickupAddress.ID, + }, + }, + }, nil) + + return &moveWithoutShipment, &moveWithShipment, &moveWithShipmentPPM + } + suite.Run("Successful move search by locator", func() { req := setupTestData() move := factory.BuildMove(suite.DB(), nil, nil) @@ -331,7 +499,7 @@ func (suite *HandlerSuite) TestSearchMovesHandler() { payloadMove := *(*payload).SearchMoves[0] suite.Equal(move.ID.String(), payloadMove.ID.String()) suite.Equal(*move.Orders.ServiceMember.Edipi, *payloadMove.Edipi) - suite.Equal(move.Orders.NewDutyLocation.Address.PostalCode, payloadMove.DestinationDutyLocationPostalCode) + suite.Equal(move.Orders.NewDutyLocation.Address.PostalCode, payloadMove.DestinationPostalCode) suite.Equal(move.Orders.OriginDutyLocation.Address.PostalCode, payloadMove.OriginDutyLocationPostalCode) suite.Equal(ghcmessages.MoveStatusDRAFT, payloadMove.Status) suite.Equal("ARMY", payloadMove.Branch) @@ -383,6 +551,139 @@ func (suite *HandlerSuite) TestSearchMovesHandler() { suite.Equal(move.ID.String(), (*payload).SearchMoves[0].ID.String()) }) + + suite.Run("Destination Postal Code and GBLOC is correct for different shipment types", func() { + req := setupTestData() + move, moveWithShipment, moveWithShipmentPPM := setupGblocTestData() + + moves := models.Moves{*move} + movesWithShipment := models.Moves{*moveWithShipment} + movesWithShipmentPPM := models.Moves{*moveWithShipmentPPM} + + // Mocks + mockSearcher := mocks.MoveSearcher{} + mockUnlocker := movelocker.NewMoveUnlocker() + handler := SearchMovesHandler{ + HandlerConfig: suite.HandlerConfig(), + MoveSearcher: &mockSearcher, + MoveUnlocker: mockUnlocker, + } + + // Set Mock Search settings for move without Shipment + mockSearcher.On("SearchMoves", + mock.AnythingOfType("*appcontext.appContext"), + mock.MatchedBy(func(params *services.SearchMovesParams) bool { + return *params.Locator == move.Locator + }), + ).Return(moves, 1, nil) + + // Move search params without Shipment + params := moveops.SearchMovesParams{ + HTTPRequest: req, + Body: moveops.SearchMovesBody{ + Locator: &move.Locator, + Edipi: nil, + }, + } + + // Validate incoming payload non-PPM + suite.NoError(params.Body.Validate(strfmt.Default)) + + // set and validate response and payload + response := handler.Handle(params) + suite.IsType(&moveops.SearchMovesOK{}, response) + payload := response.(*moveops.SearchMovesOK).Payload + + // Validate outgoing payload without shipment + suite.NoError(payload.Validate(strfmt.Default)) + + var moveDestinationPostalCode string + var moveDestinationGBLOC string + var err error + + // Get destination postal code and GBLOC based on business logic + moveDestinationPostalCode, err = move.GetDestinationPostalCode(suite.DB()) + suite.NoError(err) + moveDestinationGBLOC, err = move.GetDestinationGBLOC(suite.DB()) + suite.NoError(err) + + suite.Equal(moveDestinationPostalCode, "62225") + suite.Equal(ghcmessages.GBLOC(moveDestinationGBLOC), ghcmessages.GBLOC("AGFM")) + + // Set Mock Search settings for move with MTO Shipment + mockSearcher.On("SearchMoves", + mock.AnythingOfType("*appcontext.appContext"), + mock.MatchedBy(func(params *services.SearchMovesParams) bool { + return *params.Locator == moveWithShipment.Locator + }), + ).Return(movesWithShipment, 1, nil) + + // Move search params with MTO Shipment + params = moveops.SearchMovesParams{ + HTTPRequest: req, + Body: moveops.SearchMovesBody{ + Locator: &moveWithShipment.Locator, + Edipi: nil, + }, + } + + // Validate incoming payload with shipment + suite.NoError(params.Body.Validate(strfmt.Default)) + + // reset and validate response and payload + response = handler.Handle(params) + suite.IsType(&moveops.SearchMovesOK{}, response) + payload = response.(*moveops.SearchMovesOK).Payload + + // Validate outgoing payload with shipment + suite.NoError(payload.Validate(strfmt.Default)) + + // Get destination postal code and GBLOC based on business logic + moveDestinationPostalCode, err = moveWithShipment.GetDestinationPostalCode(suite.DB()) + suite.NoError(err) + moveDestinationGBLOC, err = moveWithShipment.GetDestinationGBLOC(suite.DB()) + suite.NoError(err) + + suite.Equal(moveDestinationPostalCode, "90210") + suite.Equal(ghcmessages.GBLOC(moveDestinationGBLOC), ghcmessages.GBLOC("KKFA")) + + // Set Mock Search settings for move with PPM Shipment + mockSearcher.On("SearchMoves", + mock.AnythingOfType("*appcontext.appContext"), + mock.MatchedBy(func(params *services.SearchMovesParams) bool { + return *params.Locator == moveWithShipmentPPM.Locator + }), + ).Return(movesWithShipmentPPM, 1, nil) + + // Move search params with PPM Shipment + params = moveops.SearchMovesParams{ + HTTPRequest: req, + Body: moveops.SearchMovesBody{ + Locator: &moveWithShipmentPPM.Locator, + Edipi: nil, + }, + } + + // Validate incoming payload with PPM shipment + suite.NoError(params.Body.Validate(strfmt.Default)) + + // reset and validate response and payload + response = handler.Handle(params) + suite.IsType(&moveops.SearchMovesOK{}, response) + payload = response.(*moveops.SearchMovesOK).Payload + + // Validate outgoing payload non-PPM + suite.NoError(payload.Validate(strfmt.Default)) + + // Get destination postal code and GBLOC based on business logic + moveDestinationPostalCode, err = moveWithShipmentPPM.GetDestinationPostalCode(suite.DB()) + suite.NoError(err) + moveDestinationGBLOC, err = moveWithShipmentPPM.GetDestinationGBLOC(suite.DB()) + suite.NoError(err) + + suite.Equal(moveDestinationPostalCode, payload.SearchMoves[0].DestinationPostalCode) + suite.Equal(ghcmessages.GBLOC(moveDestinationGBLOC), payload.SearchMoves[0].DestinationGBLOC) + }) } func (suite *HandlerSuite) TestSetFinancialReviewFlagHandler() { diff --git a/pkg/handlers/ghcapi/orders.go b/pkg/handlers/ghcapi/orders.go index ff1c47a9569..cd9ff484a59 100644 --- a/pkg/handlers/ghcapi/orders.go +++ b/pkg/handlers/ghcapi/orders.go @@ -218,7 +218,7 @@ func (h CreateOrderHandler) Handle(params orderop.CreateOrderParams) middleware. return orderop.NewCreateOrderUnprocessableEntity(), err } - newDutyLocationGBLOC, err := models.FetchGBLOCForPostalCode(appCtx.DB(), newDutyLocation.Address.PostalCode) + destinationGBLOC, err := models.FetchGBLOCForPostalCode(appCtx.DB(), newDutyLocation.Address.PostalCode) if err != nil { err = apperror.NewBadDataError("New duty location GBLOC cannot be verified") appCtx.Logger().Error(err.Error()) @@ -313,7 +313,7 @@ func (h CreateOrderHandler) Handle(params orderop.CreateOrderParams) middleware. &entitlement, &originDutyLocationGBLOC.GBLOC, packingAndShippingInstructions, - &newDutyLocationGBLOC.GBLOC, + &destinationGBLOC.GBLOC, ) if err != nil || verrs.HasAny() { return handlers.ResponseForVErrors(appCtx.Logger(), verrs, err), err diff --git a/pkg/handlers/primeapi/move_task_order.go b/pkg/handlers/primeapi/move_task_order.go index 6c6795cb56c..d82f2b72fb8 100644 --- a/pkg/handlers/primeapi/move_task_order.go +++ b/pkg/handlers/primeapi/move_task_order.go @@ -45,7 +45,7 @@ func (h ListMovesHandler) Handle(params movetaskorderops.ListMovesParams) middle return movetaskorderops.NewListMovesInternalServerError().WithPayload(payloads.InternalServerError(nil, h.GetTraceIDFromRequest(params.HTTPRequest))), err } - payload := payloads.ListMoves(&mtos, amendmentCountInfo) + payload := payloads.ListMoves(&mtos, appCtx, amendmentCountInfo) return movetaskorderops.NewListMovesOK().WithPayload(payload), nil }) @@ -141,7 +141,7 @@ func (h GetMoveTaskOrderHandler) Handle(params movetaskorderops.GetMoveTaskOrder } /** End of Feature Flag **/ - moveTaskOrderPayload := payloads.MoveTaskOrder(mto) + moveTaskOrderPayload := payloads.MoveTaskOrder(appCtx, mto) return movetaskorderops.NewGetMoveTaskOrderOK().WithPayload(moveTaskOrderPayload), nil }) @@ -250,7 +250,7 @@ func (h UpdateMTOPostCounselingInformationHandler) Handle(params movetaskorderop } } - mtoPayload := payloads.MoveTaskOrder(mto) + mtoPayload := payloads.MoveTaskOrder(appCtx, mto) /* Don't send prime related emails on BLUEBARK moves */ if mto.Orders.CanSendEmailWithOrdersType() { diff --git a/pkg/handlers/primeapi/payloads/model_to_payload.go b/pkg/handlers/primeapi/payloads/model_to_payload.go index d5c76723fa4..10a1e30be8c 100644 --- a/pkg/handlers/primeapi/payloads/model_to_payload.go +++ b/pkg/handlers/primeapi/payloads/model_to_payload.go @@ -20,7 +20,8 @@ import ( ) // MoveTaskOrder payload -func MoveTaskOrder(moveTaskOrder *models.Move) *primemessages.MoveTaskOrder { +func MoveTaskOrder(appCtx appcontext.AppContext, moveTaskOrder *models.Move) *primemessages.MoveTaskOrder { + db := appCtx.DB() if moveTaskOrder == nil { return nil } @@ -28,6 +29,17 @@ func MoveTaskOrder(moveTaskOrder *models.Move) *primemessages.MoveTaskOrder { mtoServiceItems := MTOServiceItems(&moveTaskOrder.MTOServiceItems) mtoShipments := MTOShipmentsWithoutServiceItems(&moveTaskOrder.MTOShipments) + var destGbloc, destZip string + var err error + destGbloc, err = moveTaskOrder.GetDestinationGBLOC(db) + if err != nil { + destGbloc = "" + } + destZip, err = moveTaskOrder.GetDestinationPostalCode(db) + if err != nil { + destZip = "" + } + payload := &primemessages.MoveTaskOrder{ ID: strfmt.UUID(moveTaskOrder.ID.String()), MoveCode: moveTaskOrder.Locator, @@ -40,6 +52,8 @@ func MoveTaskOrder(moveTaskOrder *models.Move) *primemessages.MoveTaskOrder { ExcessWeightUploadID: handlers.FmtUUIDPtr(moveTaskOrder.ExcessWeightUploadID), OrderID: strfmt.UUID(moveTaskOrder.OrdersID.String()), Order: Order(&moveTaskOrder.Orders), + DestinationGBLOC: destGbloc, + DestinationPostalCode: destZip, ReferenceID: *moveTaskOrder.ReferenceID, PaymentRequests: *paymentRequests, MtoShipments: *mtoShipments, @@ -63,21 +77,36 @@ func MoveTaskOrder(moveTaskOrder *models.Move) *primemessages.MoveTaskOrder { } // ListMove payload -func ListMove(move *models.Move, moveOrderAmendmentsCount *services.MoveOrderAmendmentAvailableSinceCount) *primemessages.ListMove { +func ListMove(move *models.Move, appCtx appcontext.AppContext, moveOrderAmendmentsCount *services.MoveOrderAmendmentAvailableSinceCount) *primemessages.ListMove { if move == nil { return nil } + db := appCtx.DB() + + var destGbloc, destZip string + var err error + destGbloc, err = move.GetDestinationGBLOC(db) + if err != nil { + destGbloc = "" + } + destZip, err = move.GetDestinationPostalCode(db) + if err != nil { + destZip = "" + } + payload := &primemessages.ListMove{ - ID: strfmt.UUID(move.ID.String()), - MoveCode: move.Locator, - CreatedAt: strfmt.DateTime(move.CreatedAt), - AvailableToPrimeAt: handlers.FmtDateTimePtr(move.AvailableToPrimeAt), - ApprovedAt: handlers.FmtDateTimePtr(move.ApprovedAt), - OrderID: strfmt.UUID(move.OrdersID.String()), - ReferenceID: *move.ReferenceID, - UpdatedAt: strfmt.DateTime(move.UpdatedAt), - ETag: etag.GenerateEtag(move.UpdatedAt), + ID: strfmt.UUID(move.ID.String()), + MoveCode: move.Locator, + CreatedAt: strfmt.DateTime(move.CreatedAt), + AvailableToPrimeAt: handlers.FmtDateTimePtr(move.AvailableToPrimeAt), + ApprovedAt: handlers.FmtDateTimePtr(move.ApprovedAt), + DestinationGBLOC: destGbloc, + DestinationPostalCode: destZip, + OrderID: strfmt.UUID(move.OrdersID.String()), + ReferenceID: *move.ReferenceID, + UpdatedAt: strfmt.DateTime(move.UpdatedAt), + ETag: etag.GenerateEtag(move.UpdatedAt), Amendments: &primemessages.Amendments{ Total: handlers.FmtInt64(0), AvailableSince: handlers.FmtInt64(0), @@ -97,7 +126,7 @@ func ListMove(move *models.Move, moveOrderAmendmentsCount *services.MoveOrderAme } // ListMoves payload -func ListMoves(moves *models.Moves, moveOrderAmendmentAvailableSinceCounts services.MoveOrderAmendmentAvailableSinceCounts) []*primemessages.ListMove { +func ListMoves(moves *models.Moves, appCtx appcontext.AppContext, moveOrderAmendmentAvailableSinceCounts services.MoveOrderAmendmentAvailableSinceCounts) []*primemessages.ListMove { payload := make(primemessages.ListMoves, len(*moves)) moveOrderAmendmentsFilterCountMap := make(map[uuid.UUID]services.MoveOrderAmendmentAvailableSinceCount, len(*moves)) @@ -108,9 +137,9 @@ func ListMoves(moves *models.Moves, moveOrderAmendmentAvailableSinceCounts servi for i, m := range *moves { copyOfM := m // Make copy to avoid implicit memory aliasing of items from a range statement. if value, ok := moveOrderAmendmentsFilterCountMap[m.ID]; ok { - payload[i] = ListMove(©OfM, &value) + payload[i] = ListMove(©OfM, appCtx, &value) } else { - payload[i] = ListMove(©OfM, nil) + payload[i] = ListMove(©OfM, appCtx, nil) } } diff --git a/pkg/handlers/primeapi/payloads/model_to_payload_test.go b/pkg/handlers/primeapi/payloads/model_to_payload_test.go index 73342af2430..f1f038d5f6a 100644 --- a/pkg/handlers/primeapi/payloads/model_to_payload_test.go +++ b/pkg/handlers/primeapi/payloads/model_to_payload_test.go @@ -55,7 +55,7 @@ func (suite *PayloadsSuite) TestMoveTaskOrder() { } suite.Run("Success - Returns a basic move payload with no payment requests, service items or shipments", func() { - returnedModel := MoveTaskOrder(&basicMove) + returnedModel := MoveTaskOrder(suite.AppContextForTest(), &basicMove) suite.IsType(&primemessages.MoveTaskOrder{}, returnedModel) suite.Equal(strfmt.UUID(basicMove.ID.String()), returnedModel.ID) @@ -745,6 +745,55 @@ func (suite *PayloadsSuite) TestMTOServiceItemDDSHUT() { suite.True(ok) } +func (suite *PayloadsSuite) TestDestinationPostalCodeAndGBLOC() { + moveID := uuid.Must(uuid.NewV4()) + moveLocator := "TESTTEST" + primeTime := time.Now() + ordersID := uuid.Must(uuid.NewV4()) + refID := "123456" + contractNum := "HTC-123-456" + address := models.Address{PostalCode: "35023"} + shipment := models.MTOShipment{ + ID: uuid.Must(uuid.NewV4()), + DestinationAddress: &address, + } + shipments := models.MTOShipments{shipment} + contractor := models.Contractor{ + ContractNumber: contractNum, + } + + basicMove := models.Move{ + ID: moveID, + Locator: moveLocator, + CreatedAt: primeTime, + ReferenceID: &refID, + AvailableToPrimeAt: &primeTime, + ApprovedAt: &primeTime, + OrdersID: ordersID, + Contractor: &contractor, + PaymentRequests: models.PaymentRequests{}, + SubmittedAt: &primeTime, + UpdatedAt: primeTime, + Status: models.MoveStatusAPPROVED, + SignedCertifications: models.SignedCertifications{}, + MTOServiceItems: models.MTOServiceItems{}, + MTOShipments: shipments, + } + + suite.Run("Returns values needed to get the destination postal code and GBLOC", func() { + returnedModel := MoveTaskOrder(suite.AppContextForTest(), &basicMove) + + suite.IsType(&primemessages.MoveTaskOrder{}, returnedModel) + suite.Equal(strfmt.UUID(basicMove.ID.String()), returnedModel.ID) + suite.Equal(basicMove.Locator, returnedModel.MoveCode) + suite.Equal(strfmt.DateTime(basicMove.CreatedAt), returnedModel.CreatedAt) + suite.Equal(handlers.FmtDateTimePtr(basicMove.AvailableToPrimeAt), returnedModel.AvailableToPrimeAt) + suite.Equal(strfmt.UUID(basicMove.OrdersID.String()), returnedModel.OrderID) + suite.Equal(strfmt.DateTime(basicMove.UpdatedAt), returnedModel.UpdatedAt) + suite.NotEmpty(returnedModel.ETag) + }) +} + func (suite *PayloadsSuite) TestStorageFacilityPayload() { phone := "555" email := "email" diff --git a/pkg/handlers/primeapiv2/move_task_order.go b/pkg/handlers/primeapiv2/move_task_order.go index 0ac24d23651..f3ecd6db066 100644 --- a/pkg/handlers/primeapiv2/move_task_order.go +++ b/pkg/handlers/primeapiv2/move_task_order.go @@ -104,7 +104,7 @@ func (h GetMoveTaskOrderHandler) Handle(params movetaskorderops.GetMoveTaskOrder } /** End of Feature Flag **/ - moveTaskOrderPayload := payloads.MoveTaskOrder(mto) + moveTaskOrderPayload := payloads.MoveTaskOrder(appCtx, mto) return movetaskorderops.NewGetMoveTaskOrderOK().WithPayload(moveTaskOrderPayload), nil }) diff --git a/pkg/handlers/primeapiv2/payloads/model_to_payload.go b/pkg/handlers/primeapiv2/payloads/model_to_payload.go index 56ad6caabcb..c3e83a07cf3 100644 --- a/pkg/handlers/primeapiv2/payloads/model_to_payload.go +++ b/pkg/handlers/primeapiv2/payloads/model_to_payload.go @@ -9,6 +9,7 @@ import ( "github.com/gobuffalo/validate/v3" "github.com/gofrs/uuid" + "github.com/transcom/mymove/pkg/appcontext" "github.com/transcom/mymove/pkg/etag" "github.com/transcom/mymove/pkg/gen/primev2messages" "github.com/transcom/mymove/pkg/handlers" @@ -16,7 +17,8 @@ import ( ) // MoveTaskOrder payload -func MoveTaskOrder(moveTaskOrder *models.Move) *primev2messages.MoveTaskOrder { +func MoveTaskOrder(appCtx appcontext.AppContext, moveTaskOrder *models.Move) *primev2messages.MoveTaskOrder { + db := appCtx.DB() if moveTaskOrder == nil { return nil } @@ -24,6 +26,17 @@ func MoveTaskOrder(moveTaskOrder *models.Move) *primev2messages.MoveTaskOrder { mtoServiceItems := MTOServiceItems(&moveTaskOrder.MTOServiceItems) mtoShipments := MTOShipmentsWithoutServiceItems(&moveTaskOrder.MTOShipments) + var destGbloc, destZip string + var err error + destGbloc, err = moveTaskOrder.GetDestinationGBLOC(db) + if err != nil { + destGbloc = "" + } + destZip, err = moveTaskOrder.GetDestinationPostalCode(db) + if err != nil { + destZip = "" + } + payload := &primev2messages.MoveTaskOrder{ ID: strfmt.UUID(moveTaskOrder.ID.String()), MoveCode: moveTaskOrder.Locator, @@ -36,6 +49,8 @@ func MoveTaskOrder(moveTaskOrder *models.Move) *primev2messages.MoveTaskOrder { ExcessWeightUploadID: handlers.FmtUUIDPtr(moveTaskOrder.ExcessWeightUploadID), OrderID: strfmt.UUID(moveTaskOrder.OrdersID.String()), Order: Order(&moveTaskOrder.Orders), + DestinationGBLOC: destGbloc, + DestinationPostalCode: destZip, ReferenceID: *moveTaskOrder.ReferenceID, PaymentRequests: *paymentRequests, MtoShipments: *mtoShipments, diff --git a/pkg/handlers/primeapiv2/payloads/model_to_payload_test.go b/pkg/handlers/primeapiv2/payloads/model_to_payload_test.go index 13e3bd35e3c..ec125590da5 100644 --- a/pkg/handlers/primeapiv2/payloads/model_to_payload_test.go +++ b/pkg/handlers/primeapiv2/payloads/model_to_payload_test.go @@ -64,7 +64,7 @@ func (suite *PayloadsSuite) TestMoveTaskOrder() { } suite.Run("Success - Returns a basic move payload with no payment requests, service items or shipments", func() { - returnedModel := MoveTaskOrder(&basicMove) + returnedModel := MoveTaskOrder(suite.AppContextForTest(), &basicMove) suite.IsType(&primev2messages.MoveTaskOrder{}, returnedModel) suite.Equal(strfmt.UUID(basicMove.ID.String()), returnedModel.ID) @@ -89,6 +89,55 @@ func (suite *PayloadsSuite) TestMoveTaskOrder() { }) } +func (suite *PayloadsSuite) TestDestinationPostalCodeAndGBLOC() { + moveID := uuid.Must(uuid.NewV4()) + moveLocator := "TESTTEST" + primeTime := time.Now() + ordersID := uuid.Must(uuid.NewV4()) + refID := "123456" + contractNum := "HTC-123-456" + address := models.Address{PostalCode: "35023"} + shipment := models.MTOShipment{ + ID: uuid.Must(uuid.NewV4()), + DestinationAddress: &address, + } + shipments := models.MTOShipments{shipment} + contractor := models.Contractor{ + ContractNumber: contractNum, + } + + basicMove := models.Move{ + ID: moveID, + Locator: moveLocator, + CreatedAt: primeTime, + ReferenceID: &refID, + AvailableToPrimeAt: &primeTime, + ApprovedAt: &primeTime, + OrdersID: ordersID, + Contractor: &contractor, + PaymentRequests: models.PaymentRequests{}, + SubmittedAt: &primeTime, + UpdatedAt: primeTime, + Status: models.MoveStatusAPPROVED, + SignedCertifications: models.SignedCertifications{}, + MTOServiceItems: models.MTOServiceItems{}, + MTOShipments: shipments, + } + + suite.Run("Returns values needed to get the destination postal code and GBLOC", func() { + returnedModel := MoveTaskOrder(suite.AppContextForTest(), &basicMove) + + suite.IsType(&primev2messages.MoveTaskOrder{}, returnedModel) + suite.Equal(strfmt.UUID(basicMove.ID.String()), returnedModel.ID) + suite.Equal(basicMove.Locator, returnedModel.MoveCode) + suite.Equal(strfmt.DateTime(basicMove.CreatedAt), returnedModel.CreatedAt) + suite.Equal(handlers.FmtDateTimePtr(basicMove.AvailableToPrimeAt), returnedModel.AvailableToPrimeAt) + suite.Equal(strfmt.UUID(basicMove.OrdersID.String()), returnedModel.OrderID) + suite.Equal(strfmt.DateTime(basicMove.UpdatedAt), returnedModel.UpdatedAt) + suite.NotEmpty(returnedModel.ETag) + }) +} + func (suite *PayloadsSuite) TestReweigh() { id, _ := uuid.NewV4() shipmentID, _ := uuid.NewV4() diff --git a/pkg/handlers/primeapiv3/move_task_order.go b/pkg/handlers/primeapiv3/move_task_order.go index e7b5f01149c..c725fe7210b 100644 --- a/pkg/handlers/primeapiv3/move_task_order.go +++ b/pkg/handlers/primeapiv3/move_task_order.go @@ -113,7 +113,7 @@ func (h GetMoveTaskOrderHandler) Handle(params movetaskorderops.GetMoveTaskOrder payloads.InternalServerError(handlers.FmtString(err.Error()), h.GetTraceIDFromRequest(params.HTTPRequest))), err } - moveTaskOrderPayload := payloads.MoveTaskOrderWithShipmentOconusRateArea(mto, shipmentPostalCodeRateArea) + moveTaskOrderPayload := payloads.MoveTaskOrderWithShipmentOconusRateArea(appCtx, mto, shipmentPostalCodeRateArea) return movetaskorderops.NewGetMoveTaskOrderOK().WithPayload(moveTaskOrderPayload), nil }) diff --git a/pkg/handlers/primeapiv3/payloads/model_to_payload.go b/pkg/handlers/primeapiv3/payloads/model_to_payload.go index 4676e9b3d47..841192ec0e6 100644 --- a/pkg/handlers/primeapiv3/payloads/model_to_payload.go +++ b/pkg/handlers/primeapiv3/payloads/model_to_payload.go @@ -9,6 +9,7 @@ import ( "github.com/gobuffalo/validate/v3" "github.com/gofrs/uuid" + "github.com/transcom/mymove/pkg/appcontext" "github.com/transcom/mymove/pkg/etag" "github.com/transcom/mymove/pkg/gen/primev3messages" "github.com/transcom/mymove/pkg/handlers" @@ -17,7 +18,8 @@ import ( ) // MoveTaskOrder payload -func MoveTaskOrder(moveTaskOrder *models.Move) *primev3messages.MoveTaskOrder { +func MoveTaskOrder(appCtx appcontext.AppContext, moveTaskOrder *models.Move) *primev3messages.MoveTaskOrder { + db := appCtx.DB() if moveTaskOrder == nil { return nil } @@ -27,6 +29,17 @@ func MoveTaskOrder(moveTaskOrder *models.Move) *primev3messages.MoveTaskOrder { setPortsOnShipments(&moveTaskOrder.MTOServiceItems, mtoShipments) + var destGbloc, destZip string + var err error + destGbloc, err = moveTaskOrder.GetDestinationGBLOC(db) + if err != nil { + destGbloc = "" + } + destZip, err = moveTaskOrder.GetDestinationPostalCode(db) + if err != nil { + destZip = "" + } + payload := &primev3messages.MoveTaskOrder{ ID: strfmt.UUID(moveTaskOrder.ID.String()), MoveCode: moveTaskOrder.Locator, @@ -38,6 +51,8 @@ func MoveTaskOrder(moveTaskOrder *models.Move) *primev3messages.MoveTaskOrder { ExcessWeightUploadID: handlers.FmtUUIDPtr(moveTaskOrder.ExcessWeightUploadID), OrderID: strfmt.UUID(moveTaskOrder.OrdersID.String()), Order: Order(&moveTaskOrder.Orders), + DestinationGBLOC: destGbloc, + DestinationPostalCode: destZip, ReferenceID: *moveTaskOrder.ReferenceID, PaymentRequests: *paymentRequests, MtoShipments: *mtoShipments, @@ -61,9 +76,9 @@ func MoveTaskOrder(moveTaskOrder *models.Move) *primev3messages.MoveTaskOrder { return payload } -func MoveTaskOrderWithShipmentOconusRateArea(moveTaskOrder *models.Move, shipmentRateArea *[]services.ShipmentPostalCodeRateArea) *primev3messages.MoveTaskOrder { +func MoveTaskOrderWithShipmentOconusRateArea(appCtx appcontext.AppContext, moveTaskOrder *models.Move, shipmentRateArea *[]services.ShipmentPostalCodeRateArea) *primev3messages.MoveTaskOrder { // create default payload - var payload = MoveTaskOrder(moveTaskOrder) + var payload = MoveTaskOrder(appCtx, moveTaskOrder) // decorate payload with oconus rateArea information if payload != nil && shipmentRateArea != nil { diff --git a/pkg/handlers/primeapiv3/payloads/model_to_payload_test.go b/pkg/handlers/primeapiv3/payloads/model_to_payload_test.go index e6de847b908..cdc4dc13b7c 100644 --- a/pkg/handlers/primeapiv3/payloads/model_to_payload_test.go +++ b/pkg/handlers/primeapiv3/payloads/model_to_payload_test.go @@ -82,7 +82,7 @@ func (suite *PayloadsSuite) TestMoveTaskOrder() { } suite.Run("Success - Returns a basic move payload with no payment requests, service items or shipments", func() { - returnedModel := MoveTaskOrder(&basicMove) + returnedModel := MoveTaskOrder(suite.AppContextForTest(), &basicMove) suite.IsType(&primev3messages.MoveTaskOrder{}, returnedModel) suite.Equal(strfmt.UUID(basicMove.ID.String()), returnedModel.ID) @@ -249,7 +249,7 @@ func (suite *PayloadsSuite) TestMoveTaskOrder() { }) // no ShipmentPostalCodeRateArea passed in - returnedModel := MoveTaskOrderWithShipmentOconusRateArea(newMove, nil) + returnedModel := MoveTaskOrderWithShipmentOconusRateArea(suite.AppContextForTest(), newMove, nil) suite.IsType(&primev3messages.MoveTaskOrder{}, returnedModel) suite.Equal(strfmt.UUID(newMove.ID.String()), returnedModel.ID) @@ -312,7 +312,7 @@ func (suite *PayloadsSuite) TestMoveTaskOrder() { }, } - returnedModel = MoveTaskOrderWithShipmentOconusRateArea(newMove, &shipmentPostalCodeRateArea) + returnedModel = MoveTaskOrderWithShipmentOconusRateArea(suite.AppContextForTest(), newMove, &shipmentPostalCodeRateArea) var shipmentPostalCodeRateAreaLookupMap = make(map[string]services.ShipmentPostalCodeRateArea) for _, i := range shipmentPostalCodeRateArea { @@ -1214,6 +1214,55 @@ func (suite *PayloadsSuite) TestBoatShipment() { suite.NotNil(result) } +func (suite *PayloadsSuite) TestDestinationPostalCodeAndGBLOC() { + moveID := uuid.Must(uuid.NewV4()) + moveLocator := "TESTTEST" + primeTime := time.Now() + ordersID := uuid.Must(uuid.NewV4()) + refID := "123456" + contractNum := "HTC-123-456" + address := models.Address{PostalCode: "35023"} + shipment := models.MTOShipment{ + ID: uuid.Must(uuid.NewV4()), + DestinationAddress: &address, + } + shipments := models.MTOShipments{shipment} + contractor := models.Contractor{ + ContractNumber: contractNum, + } + + basicMove := models.Move{ + ID: moveID, + Locator: moveLocator, + CreatedAt: primeTime, + ReferenceID: &refID, + AvailableToPrimeAt: &primeTime, + ApprovedAt: &primeTime, + OrdersID: ordersID, + Contractor: &contractor, + PaymentRequests: models.PaymentRequests{}, + SubmittedAt: &primeTime, + UpdatedAt: primeTime, + Status: models.MoveStatusAPPROVED, + SignedCertifications: models.SignedCertifications{}, + MTOServiceItems: models.MTOServiceItems{}, + MTOShipments: shipments, + } + + suite.Run("Returns values needed to get the destination postal code and GBLOC", func() { + returnedModel := MoveTaskOrder(suite.AppContextForTest(), &basicMove) + + suite.IsType(&primev3messages.MoveTaskOrder{}, returnedModel) + suite.Equal(strfmt.UUID(basicMove.ID.String()), returnedModel.ID) + suite.Equal(basicMove.Locator, returnedModel.MoveCode) + suite.Equal(strfmt.DateTime(basicMove.CreatedAt), returnedModel.CreatedAt) + suite.Equal(handlers.FmtDateTimePtr(basicMove.AvailableToPrimeAt), returnedModel.AvailableToPrimeAt) + suite.Equal(strfmt.UUID(basicMove.OrdersID.String()), returnedModel.OrderID) + suite.Equal(strfmt.DateTime(basicMove.UpdatedAt), returnedModel.UpdatedAt) + suite.NotEmpty(returnedModel.ETag) + }) +} + func (suite *PayloadsSuite) TestMarketCode() { suite.Run("returns nil when marketCode is nil", func() { var marketCode *models.MarketCode = nil @@ -1275,7 +1324,7 @@ func (suite *PayloadsSuite) TestMTOServiceItemPOEFSC() { }, } - mtoPayload := MoveTaskOrder(&move) + mtoPayload := MoveTaskOrder(suite.AppContextForTest(), &move) suite.NotNil(mtoPayload) suite.Equal(mtoPayload.MtoShipments[0].PortOfEmbarkation.PortType, portLocation.Port.PortType.String()) suite.Equal(mtoPayload.MtoShipments[0].PortOfEmbarkation.PortCode, portLocation.Port.PortCode) @@ -1326,7 +1375,7 @@ func (suite *PayloadsSuite) TestMTOServiceItemPODFSC() { }, } - mtoPayload := MoveTaskOrder(&move) + mtoPayload := MoveTaskOrder(suite.AppContextForTest(), &move) suite.NotNil(mtoPayload) suite.Equal(mtoPayload.MtoShipments[0].PortOfDebarkation.PortType, portLocation.Port.PortType.String()) suite.Equal(mtoPayload.MtoShipments[0].PortOfDebarkation.PortCode, portLocation.Port.PortCode) diff --git a/pkg/models/move.go b/pkg/models/move.go index 23c1dd7c479..8201a7f0dc6 100644 --- a/pkg/models/move.go +++ b/pkg/models/move.go @@ -196,6 +196,50 @@ func FetchMove(db *pop.Connection, session *auth.Session, id uuid.UUID) (*Move, return &move, nil } +// GetDestinationPostalCode returns the postal code for the move. This ensures that business logic is centralized. +func (m Move) GetDestinationPostalCode(db *pop.Connection) (string, error) { + // Since this requires looking up the move in the DB, the move must have an ID. This means, the move has to have been created first. + if uuid.UUID.IsNil(m.ID) { + return "", errors.WithMessage(ErrInvalidOrderID, "You must created the move in the DB before getting the destination Postal Code.") + } + + err := db.Load(&m, "Orders") + if err != nil { + if err.Error() == RecordNotFoundErrorString { + return "", errors.WithMessage(err, "No Orders found in the DB associated with moveID "+m.ID.String()) + } + return "", err + } + + var gblocsMap map[uuid.UUID]string + gblocsMap, err = m.Orders.GetDestinationPostalCodeForAssociatedMoves(db) + if err != nil { + return "", err + } + return gblocsMap[m.ID], nil +} + +// GetDestinationGBLOC returns the GBLOC for the move. This ensures that business logic is centralized. +func (m Move) GetDestinationGBLOC(db *pop.Connection) (string, error) { + // Since this requires looking up the move in the DB, the move must have an ID. This means, the move has to have been created first. + if uuid.UUID.IsNil(m.ID) { + return "", errors.WithMessage(ErrInvalidOrderID, "You must created the move in the DB before getting the destination GBLOC.") + } + + postalCode, err := m.GetDestinationPostalCode(db) + if err != nil { + return "", err + } + + var gblocResult PostalCodeToGBLOC + gblocResult, err = FetchGBLOCForPostalCode(db, postalCode) + if err != nil { + return "", err + } + + return gblocResult.GBLOC, err +} + // CreateSignedCertification creates a new SignedCertification associated with this move func (m Move) CreateSignedCertification(db *pop.Connection, submittingUserID uuid.UUID, diff --git a/pkg/models/mto_shipments.go b/pkg/models/mto_shipments.go index da5e021a698..6311ce24d6e 100644 --- a/pkg/models/mto_shipments.go +++ b/pkg/models/mto_shipments.go @@ -8,6 +8,7 @@ import ( "github.com/gobuffalo/validate/v3" "github.com/gobuffalo/validate/v3/validators" "github.com/gofrs/uuid" + "github.com/pkg/errors" "github.com/transcom/mymove/pkg/unit" ) @@ -275,6 +276,33 @@ func GetCustomerFromShipment(db *pop.Connection, shipmentID uuid.UUID) (*Service return &serviceMember, nil } +func (m *MTOShipment) UpdateOrdersDestinationGBLOC(db *pop.Connection) error { + // Since this requires looking up the order in the DB, the order must have an ID. This means, the order has to have been created first. + if uuid.UUID.IsNil(m.ID) { + return fmt.Errorf("error updating orders destination GBLOC for shipment due to no shipment ID provided") + } + + var err error + var order Order + + err = db.Load(&m, "MoveTaskOrder.OrdersID") + if err != nil { + return fmt.Errorf("error loading orders for shipment ID: %s with error %w", m.ID, err) + } + + order, err = FetchOrder(db, m.MoveTaskOrder.OrdersID) + if err != nil { + return fmt.Errorf("error fetching order for shipment ID: %s with error %w", m.ID, err) + } + + err = order.UpdateDestinationGBLOC(db) + if err != nil { + return fmt.Errorf("error fetching GBLOC for postal code with error %w", err) + } + + return nil +} + // Helper function to check that an MTO Shipment contains a PPM Shipment func (m MTOShipment) ContainsAPPMShipment() bool { return m.PPMShipment != nil @@ -341,6 +369,35 @@ func DetermineShipmentMarketCode(shipment *MTOShipment) *MTOShipment { return shipment } +func (s MTOShipment) GetDestinationAddress(db *pop.Connection) (*Address, error) { + if uuid.UUID.IsNil(s.ID) { + return nil, errors.New("MTOShipment ID is required to fetch destination address.") + } + + err := db.Load(&s, "DestinationAddress", "PPMShipment.DestinationAddress") + if err != nil { + if err.Error() == RecordNotFoundErrorString { + return nil, errors.WithMessage(ErrSqlRecordNotFound, string(s.ShipmentType)+" ShipmentID: "+s.ID.String()) + } + return nil, err + } + + if s.ShipmentType == MTOShipmentTypePPM { + if s.PPMShipment.DestinationAddress != nil { + return s.PPMShipment.DestinationAddress, nil + } else if s.DestinationAddress != nil { + return s.DestinationAddress, nil + } + return nil, errors.WithMessage(ErrMissingDestinationAddress, string(s.ShipmentType)) + } + + if s.DestinationAddress != nil { + return s.DestinationAddress, nil + } + + return nil, errors.WithMessage(ErrMissingDestinationAddress, string(s.ShipmentType)) +} + // this function takes in two addresses and determines the market code string func DetermineMarketCode(address1 *Address, address2 *Address) (MarketCode, error) { if address1 == nil || address2 == nil { diff --git a/pkg/models/order.go b/pkg/models/order.go index f4628894a84..56c71e2265a 100644 --- a/pkg/models/order.go +++ b/pkg/models/order.go @@ -1,6 +1,7 @@ package models import ( + "sort" "time" "github.com/gobuffalo/pop/v6" @@ -371,6 +372,203 @@ func (o *Order) IsComplete() bool { return true } +// FetchAllShipmentsExcludingRejected returns all the shipments associated with an order excluding rejected shipments +func (o Order) FetchAllShipmentsExcludingRejected(db *pop.Connection) (map[uuid.UUID]MTOShipments, error) { + // Since this requires looking up the order in the DB, the order must have an ID. This means, the order has to have been created first. + if uuid.UUID.IsNil(o.ID) { + return nil, errors.WithMessage(ErrInvalidOrderID, "You must created the order in the DB before fetching associated shipments.") + } + + var err error + + err = db.Load(&o, "Moves") + if err != nil { + if err.Error() == RecordNotFoundErrorString { + return nil, errors.WithMessage(err, "No Moves were found for the order ID "+o.ID.String()) + } + return nil, errors.WithMessage(err, "Could not load moves for order ID "+o.ID.String()) + } + + // shipmentsMap is a map of key, value pairs where the key is the move id and the value is a list of associated MTOShipments + shipmentsMap := make(map[uuid.UUID]MTOShipments) + + for _, m := range o.Moves { + var shipments MTOShipments + err = db.Load(&m, "MTOShipments") + if err != nil { + return nil, errors.WithMessage(err, "Could not load shipments for move "+m.ID.String()) + } + + for _, s := range m.MTOShipments { + err = db.Load(&s, "Status", "DeletedAt", "CreatedAt", "DestinationAddress") + if err != nil { + return nil, errors.WithMessage(err, "Could not load shipment with ID of "+s.ID.String()+" for move ID "+m.ID.String()) + } + + if s.Status != MTOShipmentStatusRejected && s.Status != MTOShipmentStatusCanceled && s.DeletedAt == nil { + shipments = append(shipments, s) + } + } + + sort.Slice(shipments, func(i, j int) bool { + return shipments[i].CreatedAt.Before(shipments[j].CreatedAt) + }) + + shipmentsMap[m.ID] = shipments + } + + return shipmentsMap, nil +} + +/* + * GetDestinationGBLOC returns a map of destination GBLOCs for the first shipments from all of + * the moves that are associated with an order. If there are no shipments returned on a particular move, + * it will return the GBLOC of the new duty station address for that move. + */ +func (o Order) GetDestinationGBLOC(db *pop.Connection) (map[uuid.UUID]string, error) { + // Since this requires looking up the order in the DB, the order must have an ID. This means, the order has to have been created first. + if uuid.UUID.IsNil(o.ID) { + return nil, errors.WithMessage(ErrInvalidOrderID, "You must created the order in the DB before getting the destination GBLOC.") + } + + destinationPostalCodesMap, err := o.GetDestinationPostalCodeForAssociatedMoves(db) + if err != nil { + return nil, err + } + + destinationGBLOCsMap := make(map[uuid.UUID]string) + for k, v := range destinationPostalCodesMap { + var gblocResult PostalCodeToGBLOC + gblocResult, err = FetchGBLOCForPostalCode(db, v) + if err != nil { + return nil, errors.WithMessage(err, "Could not get GBLOC for postal code "+v+" for move ID "+k.String()) + } + destinationGBLOCsMap[k] = gblocResult.GBLOC + } + + return destinationGBLOCsMap, nil +} + +/* +* GetDestinationPostalCodeForAssociatedMove returns a map of Postal Codes of the destination address for the first shipments from each of +* the moves that are associated with an order. If there are no shipments returned, it will return the +* Postal Code of the new duty station addresses. + */ +func (o Order) GetDestinationPostalCodeForAssociatedMoves(db *pop.Connection) (map[uuid.UUID]string, error) { + if uuid.UUID.IsNil(o.ID) { + return nil, errors.WithMessage(ErrInvalidOrderID, "You must created the order in the DB before getting the destination Postal Code.") + } + + err := db.Load(&o, "Moves", "NewDutyLocation.Address.PostalCode") + if err != nil { + if err.Error() == RecordNotFoundErrorString { + return nil, errors.WithMessage(err, "No Moves were found for the order ID "+o.ID.String()) + } + return nil, err + } + + // zipsMap is a map of key, value pairs where the key is the move id and the value is the destination postal code + zipsMap := make(map[uuid.UUID]string) + for i, m := range o.Moves { + err = db.Load(&o.Moves[i], "MTOShipments") + if err != nil { + if err.Error() == RecordNotFoundErrorString { + return nil, errors.WithMessage(err, "Could not find shipments for move "+m.ID.String()) + } + return nil, err + } + + var shipments MTOShipments + for j, s := range o.Moves[i].MTOShipments { + err = db.Load(&o.Moves[i].MTOShipments[j], "CreatedAt", "Status", "DeletedAt", "DestinationAddress", "ShipmentType", "PPMShipment", "PPMShipment.Status", "PPMShipment.DestinationAddress") + if err != nil { + if err.Error() == RecordNotFoundErrorString { + return nil, errors.WithMessage(err, "Could not load shipment with ID of "+s.ID.String()+" for move ID "+m.ID.String()) + } + return nil, err + } + + if o.Moves[i].MTOShipments[j].Status != MTOShipmentStatusRejected && + o.Moves[i].MTOShipments[j].Status != MTOShipmentStatusCanceled && + o.Moves[i].MTOShipments[j].ShipmentType != MTOShipmentTypeHHGIntoNTSDom && + o.Moves[i].MTOShipments[j].DeletedAt == nil { + shipments = append(shipments, o.Moves[i].MTOShipments[j]) + } + } + + // If we have valid shipments, use the first one's destination address + if len(shipments) > 0 { + sort.Slice(shipments, func(i, j int) bool { + return shipments[i].CreatedAt.Before(shipments[j].CreatedAt) + }) + + var addressResult *Address + addressResult, err = shipments[0].GetDestinationAddress(db) + if err != nil { + if err == ErrMissingDestinationAddress || err == ErrUnsupportedShipmentType { + zipsMap[o.Moves[i].ID] = o.NewDutyLocation.Address.PostalCode + } + return nil, err + } + + if addressResult != nil { + zipsMap[o.Moves[i].ID] = addressResult.PostalCode + } else { + return nil, errors.WithMessage(ErrMissingDestinationAddress, "No destination address was able to be found for the order ID "+o.ID.String()) + } + } else { + // No valid shipments, use new duty location + zipsMap[o.Moves[i].ID] = o.NewDutyLocation.Address.PostalCode + } + } + + if len(zipsMap) == 0 { + return nil, errors.New("No destination postal codes were found for the order ID " + o.ID.String()) + } + + return zipsMap, nil +} + +// UpdateDestinationGBLOC updates the destination GBLOC for the associated Order in the DB +func (o Order) UpdateDestinationGBLOC(db *pop.Connection) error { + // Since this requires looking up the order in the DB, the order must have an ID. This means, the order has to have been created first. + if uuid.UUID.IsNil(o.ID) { + return errors.WithMessage(ErrInvalidOrderID, "You must created the order in the DB before updating the destination GBLOC.") + } + + var dbOrder Order + err := db.Find(&dbOrder, o.ID) + if err != nil { + if err.Error() == RecordNotFoundErrorString { + return errors.WithMessage(err, "No Order was found for the order ID "+o.ID.String()) + } + return err + } + + err = db.Load(&o, "NewDutyLocation.Address.PostalCode") + if err != nil { + if err.Error() == RecordNotFoundErrorString { + return errors.WithMessage(err, "No New Duty Location Address Postal Code was found for the order ID "+o.ID.String()) + } + return err + } + + var gblocResult PostalCodeToGBLOC + gblocResult, err = FetchGBLOCForPostalCode(db, o.NewDutyLocation.Address.PostalCode) + if err != nil { + return errors.WithMessage(err, "Could not get GBLOC for postal code "+o.NewDutyLocation.Address.PostalCode) + } + + dbOrder.DestinationGBLOC = &gblocResult.GBLOC + + err = db.Save(&dbOrder) + if err != nil { + return errors.WithMessage(err, "Could not save the updated destination GBLOC for order ID "+o.ID.String()) + } + + return nil +} + // IsCompleteForGBL checks if orders have all fields necessary to generate a GBL func (o *Order) IsCompleteForGBL() bool { diff --git a/pkg/models/service_member.go b/pkg/models/service_member.go index 28edcf7b2a3..ac70f00306a 100644 --- a/pkg/models/service_member.go +++ b/pkg/models/service_member.go @@ -366,7 +366,7 @@ func (s ServiceMember) CreateOrder(appCtx appcontext.AppContext, entitlement *Entitlement, originDutyLocationGBLOC *string, packingAndShippingInstructions string, - newDutyLocationGBLOC *string) (Order, *validate.Errors, error) { + destinationGBLOC *string) (Order, *validate.Errors, error) { var newOrders Order responseVErrors := validate.NewErrors() @@ -395,7 +395,7 @@ func (s ServiceMember) CreateOrder(appCtx appcontext.AppContext, SpouseHasProGear: spouseHasProGear, NewDutyLocationID: newDutyLocation.ID, NewDutyLocation: newDutyLocation, - DestinationGBLOC: newDutyLocationGBLOC, + DestinationGBLOC: destinationGBLOC, UploadedOrders: uploadedOrders, UploadedOrdersID: uploadedOrders.ID, Status: OrderStatusDRAFT, diff --git a/src/components/Table/SearchResultsTable.jsx b/src/components/Table/SearchResultsTable.jsx index 9369fe66f32..b6cfe3fc2af 100644 --- a/src/components/Table/SearchResultsTable.jsx +++ b/src/components/Table/SearchResultsTable.jsx @@ -166,7 +166,7 @@ const moveSearchColumns = (moveLockFlag, handleEditProfileClick) => [ createHeader( 'Destination ZIP', (row) => { - return row.destinationDutyLocationPostalCode; + return row.destinationPostalCode; }, { id: 'destinationPostalCode', diff --git a/swagger-def/definitions/prime/MoveTaskOrder.yaml b/swagger-def/definitions/prime/MoveTaskOrder.yaml index 1cc5e0458d8..6540bd0543e 100644 --- a/swagger-def/definitions/prime/MoveTaskOrder.yaml +++ b/swagger-def/definitions/prime/MoveTaskOrder.yaml @@ -22,6 +22,14 @@ properties: type: string order: $ref: 'Order.yaml' + destinationGBLOC: + type: string + example: 'KKFA' + readOnly: true + destinationPostalCode: + type: string + example: '90210' + readOnly: true referenceId: example: 1001-3456 type: string diff --git a/swagger-def/definitions/prime/v2/MoveTaskOrder.yaml b/swagger-def/definitions/prime/v2/MoveTaskOrder.yaml index a609c3d4e41..3c73a547556 100644 --- a/swagger-def/definitions/prime/v2/MoveTaskOrder.yaml +++ b/swagger-def/definitions/prime/v2/MoveTaskOrder.yaml @@ -22,6 +22,14 @@ properties: type: string order: $ref: 'Order.yaml' + destinationGBLOC: + type: string + example: 'KKFA' + readOnly: true + destinationPostalCode: + type: string + example: '90210' + readOnly: true referenceId: example: 1001-3456 type: string diff --git a/swagger-def/definitions/prime/v3/MoveTaskOrder.yaml b/swagger-def/definitions/prime/v3/MoveTaskOrder.yaml index d10dae35eab..1cd3a9d41b1 100644 --- a/swagger-def/definitions/prime/v3/MoveTaskOrder.yaml +++ b/swagger-def/definitions/prime/v3/MoveTaskOrder.yaml @@ -22,6 +22,14 @@ properties: type: string order: $ref: 'Order.yaml' + destinationGBLOC: + type: string + example: 'KKFA' + readOnly: true + destinationPostalCode: + type: string + example: '90210' + readOnly: true referenceId: example: 1001-3456 type: string diff --git a/swagger-def/ghc.yaml b/swagger-def/ghc.yaml index a0df07f3800..43061a82e8f 100644 --- a/swagger-def/ghc.yaml +++ b/swagger-def/ghc.yaml @@ -7105,6 +7105,12 @@ definitions: example: c56a4180-65aa-42ec-a945-5fd21dec0538 format: uuid type: string + destinationGBLOC: + example: 'AGFM' + type: string + destinationPostalCode: + example: '90210' + type: string referenceId: example: 1001-3456 type: string @@ -7259,7 +7265,7 @@ definitions: title: ZIP example: '90210' pattern: ^(\d{5})$ - destinationDutyLocationPostalCode: + destinationPostalCode: format: zip type: string title: ZIP diff --git a/swagger-def/prime.yaml b/swagger-def/prime.yaml index 5fcb6cdd5e3..60e686475ba 100644 --- a/swagger-def/prime.yaml +++ b/swagger-def/prime.yaml @@ -1487,6 +1487,12 @@ definitions: example: c56a4180-65aa-42ec-a945-5fd21dec0538 format: uuid type: string + destinationGBLOC: + example: 'AGFM' + type: string + destinationPostalCode: + example: '90210' + type: string referenceId: example: 1001-3456 type: string diff --git a/swagger/ghc.yaml b/swagger/ghc.yaml index ab568b434c8..7faf82b166f 100644 --- a/swagger/ghc.yaml +++ b/swagger/ghc.yaml @@ -7445,6 +7445,12 @@ definitions: example: c56a4180-65aa-42ec-a945-5fd21dec0538 format: uuid type: string + destinationGBLOC: + example: AGFM + type: string + destinationPostalCode: + example: '90210' + type: string referenceId: example: 1001-3456 type: string @@ -7601,7 +7607,7 @@ definitions: title: ZIP example: '90210' pattern: ^(\d{5})$ - destinationDutyLocationPostalCode: + destinationPostalCode: format: zip type: string title: ZIP diff --git a/swagger/prime.yaml b/swagger/prime.yaml index 83ef52b06c7..5c661452350 100644 --- a/swagger/prime.yaml +++ b/swagger/prime.yaml @@ -1848,6 +1848,12 @@ definitions: example: c56a4180-65aa-42ec-a945-5fd21dec0538 format: uuid type: string + destinationGBLOC: + example: AGFM + type: string + destinationPostalCode: + example: '90210' + type: string referenceId: example: 1001-3456 type: string @@ -1904,6 +1910,14 @@ definitions: type: string order: $ref: '#/definitions/Order' + destinationGBLOC: + type: string + example: KKFA + readOnly: true + destinationPostalCode: + type: string + example: '90210' + readOnly: true referenceId: example: 1001-3456 type: string diff --git a/swagger/prime_v2.yaml b/swagger/prime_v2.yaml index 00c4e8d169b..e77b1367ec7 100644 --- a/swagger/prime_v2.yaml +++ b/swagger/prime_v2.yaml @@ -3028,6 +3028,14 @@ definitions: type: string order: $ref: '#/definitions/Order' + destinationGBLOC: + type: string + example: KKFA + readOnly: true + destinationPostalCode: + type: string + example: '90210' + readOnly: true referenceId: example: 1001-3456 type: string diff --git a/swagger/prime_v3.yaml b/swagger/prime_v3.yaml index c478289f287..c7fce10d0da 100644 --- a/swagger/prime_v3.yaml +++ b/swagger/prime_v3.yaml @@ -3559,6 +3559,14 @@ definitions: type: string order: $ref: '#/definitions/Order' + destinationGBLOC: + type: string + example: KKFA + readOnly: true + destinationPostalCode: + type: string + example: '90210' + readOnly: true referenceId: example: 1001-3456 type: string From b870130d79309e34d79c269e64b6f32d0503e20b Mon Sep 17 00:00:00 2001 From: AaronW Date: Wed, 8 Jan 2025 15:49:09 +0000 Subject: [PATCH 27/54] reverting changes erroneously brought in from B-21581 --- .../payloads/model_to_payload_test.go | 88 ------------- pkg/handlers/internalapi/orders.go | 6 - pkg/handlers/internalapi/orders_test.go | 124 ------------------ pkg/models/order.go | 43 ------ pkg/testdatagen/scenario/shared.go | 54 -------- pkg/testdatagen/testharness/dispatch.go | 3 - pkg/testdatagen/testharness/make_move.go | 16 --- .../servicesCounselingFlows.spec.js | 27 ---- playwright/tests/utils/testharness.js | 11 -- 9 files changed, 372 deletions(-) diff --git a/pkg/handlers/ghcapi/internal/payloads/model_to_payload_test.go b/pkg/handlers/ghcapi/internal/payloads/model_to_payload_test.go index 40fb6e67ebf..ec3072d2ca0 100644 --- a/pkg/handlers/ghcapi/internal/payloads/model_to_payload_test.go +++ b/pkg/handlers/ghcapi/internal/payloads/model_to_payload_test.go @@ -9,7 +9,6 @@ import ( "github.com/transcom/mymove/pkg/factory" "github.com/transcom/mymove/pkg/gen/ghcmessages" - "github.com/transcom/mymove/pkg/gen/internalmessages" "github.com/transcom/mymove/pkg/handlers" "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/models/roles" @@ -316,93 +315,6 @@ func (suite *PayloadsSuite) TestShipmentAddressUpdate() { }) } -func (suite *PayloadsSuite) TestMoveWithGBLOC() { - defaultOrdersNumber := "ORDER3" - defaultTACNumber := "F8E1" - defaultDepartmentIndicator := "AIR_AND_SPACE_FORCE" - defaultGrade := "E_1" - defaultHasDependents := false - defaultSpouseHasProGear := false - defaultOrdersType := internalmessages.OrdersTypePERMANENTCHANGEOFSTATION - defaultOrdersTypeDetail := internalmessages.OrdersTypeDetail("HHG_PERMITTED") - defaultStatus := models.OrderStatusDRAFT - testYear := 2018 - defaultIssueDate := time.Date(testYear, time.March, 15, 0, 0, 0, 0, time.UTC) - defaultReportByDate := time.Date(testYear, time.August, 1, 0, 0, 0, 0, time.UTC) - defaultGBLOC := "KKFA" - - originDutyLocation := models.DutyLocation{ - Name: "Custom Origin", - } - originDutyLocationTOName := "origin duty location transportation office" - firstName := "customFirst" - lastName := "customLast" - serviceMember := models.ServiceMember{ - FirstName: &firstName, - LastName: &lastName, - } - uploadedOrders := models.Document{ - ID: uuid.Must(uuid.NewV4()), - } - dependents := 7 - entitlement := models.Entitlement{ - TotalDependents: &dependents, - } - amendedOrders := models.Document{ - ID: uuid.Must(uuid.NewV4()), - } - // Create order - order := factory.BuildOrder(suite.DB(), []factory.Customization{ - { - Model: originDutyLocation, - Type: &factory.DutyLocations.OriginDutyLocation, - }, - { - Model: models.TransportationOffice{ - Name: originDutyLocationTOName, - }, - Type: &factory.TransportationOffices.OriginDutyLocation, - }, - { - Model: serviceMember, - }, - { - Model: uploadedOrders, - Type: &factory.Documents.UploadedOrders, - }, - { - Model: entitlement, - }, - { - Model: amendedOrders, - Type: &factory.Documents.UploadedAmendedOrders, - }, - }, nil) - - suite.Equal(defaultOrdersNumber, *order.OrdersNumber) - suite.Equal(defaultTACNumber, *order.TAC) - suite.Equal(defaultDepartmentIndicator, *order.DepartmentIndicator) - suite.Equal(defaultGrade, string(*order.Grade)) - suite.Equal(defaultHasDependents, order.HasDependents) - suite.Equal(defaultSpouseHasProGear, order.SpouseHasProGear) - suite.Equal(defaultOrdersType, order.OrdersType) - suite.Equal(defaultOrdersTypeDetail, *order.OrdersTypeDetail) - suite.Equal(defaultStatus, order.Status) - suite.Equal(defaultIssueDate, order.IssueDate) - suite.Equal(defaultReportByDate, order.ReportByDate) - suite.Equal(defaultGBLOC, *order.OriginDutyLocationGBLOC) - - suite.Equal(originDutyLocation.Name, order.OriginDutyLocation.Name) - suite.Equal(originDutyLocationTOName, order.OriginDutyLocation.TransportationOffice.Name) - suite.Equal(*serviceMember.FirstName, *order.ServiceMember.FirstName) - suite.Equal(*serviceMember.LastName, *order.ServiceMember.LastName) - suite.Equal(uploadedOrders.ID, order.UploadedOrdersID) - suite.Equal(uploadedOrders.ID, order.UploadedOrders.ID) - suite.Equal(*entitlement.TotalDependents, *order.Entitlement.TotalDependents) - suite.Equal(amendedOrders.ID, *order.UploadedAmendedOrdersID) - suite.Equal(amendedOrders.ID, order.UploadedAmendedOrders.ID) -} - func (suite *PayloadsSuite) TestWeightTicketUpload() { uploadID, _ := uuid.NewV4() testURL := "https://testurl.com" diff --git a/pkg/handlers/internalapi/orders.go b/pkg/handlers/internalapi/orders.go index 3936dcb39e2..c9b5125c827 100644 --- a/pkg/handlers/internalapi/orders.go +++ b/pkg/handlers/internalapi/orders.go @@ -382,12 +382,6 @@ func (h UpdateOrdersHandler) Handle(params ordersop.UpdateOrdersParams) middlewa order.OriginDutyLocation = &originDutyLocation order.OriginDutyLocationID = &originDutyLocationID - originGBLOC, originGBLOCerr := models.FetchGBLOCForPostalCode(appCtx.DB(), originDutyLocation.Address.PostalCode) - if originGBLOCerr != nil { - return handlers.ResponseForError(appCtx.Logger(), originGBLOCerr), originGBLOCerr - } - order.OriginDutyLocationGBLOC = &originGBLOC.GBLOC - if payload.MoveID != "" { moveID, err := uuid.FromString(payload.MoveID.String()) diff --git a/pkg/handlers/internalapi/orders_test.go b/pkg/handlers/internalapi/orders_test.go index 59df8daf475..b2ad7897f8a 100644 --- a/pkg/handlers/internalapi/orders_test.go +++ b/pkg/handlers/internalapi/orders_test.go @@ -723,130 +723,6 @@ func (suite *HandlerSuite) TestUpdateOrdersHandler() { }) } -func (suite *HandlerSuite) TestUpdateOrdersHandlerOriginPostalCodeAndGBLOC() { - factory.BuildPostalCodeToGBLOC(suite.DB(), []factory.Customization{ - { - Model: models.PostalCodeToGBLOC{ - PostalCode: "90210", - GBLOC: "KKFA", - }, - }, - }, nil) - factory.BuildPostalCodeToGBLOC(suite.DB(), []factory.Customization{ - { - Model: models.PostalCodeToGBLOC{ - PostalCode: "35023", - GBLOC: "CNNQ", - }, - }, - }, nil) - - firstAddress := factory.BuildAddress(suite.DB(), []factory.Customization{ - { - Model: models.Address{ - PostalCode: "90210", - }, - }, - }, nil) - updatedAddress := factory.BuildAddress(suite.DB(), []factory.Customization{ - { - Model: models.Address{ - PostalCode: "35023", - }, - }, - }, nil) - dutyLocation := factory.BuildDutyLocation(suite.DB(), []factory.Customization{ - { - Model: models.DutyLocation{ - AddressID: firstAddress.ID, - }, - }, - }, nil) - updatedDutyLocation := factory.BuildDutyLocation(suite.DB(), []factory.Customization{ - { - Model: models.DutyLocation{ - AddressID: updatedAddress.ID, - }, - }, - }, nil) - newDutyLocation := factory.BuildDutyLocation(suite.DB(), nil, nil) - - order := factory.BuildOrder(suite.DB(), []factory.Customization{ - { - Model: models.Order{ - OriginDutyLocationID: &dutyLocation.ID, - NewDutyLocationID: newDutyLocation.ID, - }, - }, - }, nil) - - fetchedOrder, err := models.FetchOrder(suite.DB(), order.ID) - suite.NoError(err) - - var fetchedPostalCode, fetchedGBLOC string - fetchedPostalCode, err = fetchedOrder.GetOriginPostalCode(suite.DB()) - suite.NoError(err) - fetchedGBLOC, err = fetchedOrder.GetOriginGBLOC(suite.DB()) - suite.NoError(err) - - suite.Equal("90210", fetchedPostalCode) - suite.Equal("KKFA", fetchedGBLOC) - - newOrdersType := internalmessages.OrdersTypePERMANENTCHANGEOFSTATION - issueDate := time.Date(2018, time.March, 10, 0, 0, 0, 0, time.UTC) - reportByDate := time.Date(2018, time.August, 1, 0, 0, 0, 0, time.UTC) - deptIndicator := internalmessages.DeptIndicatorAIRANDSPACEFORCE - - payload := &internalmessages.CreateUpdateOrders{ - OrdersType: &order.OrdersType, - NewDutyLocationID: handlers.FmtUUID(order.NewDutyLocationID), - OriginDutyLocationID: *handlers.FmtUUID(updatedDutyLocation.ID), - IssueDate: handlers.FmtDate(issueDate), - ReportByDate: handlers.FmtDate(reportByDate), - DepartmentIndicator: &deptIndicator, - HasDependents: handlers.FmtBool(false), - SpouseHasProGear: handlers.FmtBool(false), - ServiceMemberID: handlers.FmtUUID(order.ServiceMemberID), - Grade: models.ServiceMemberGradeE4.Pointer(), - } - - path := fmt.Sprintf("/orders/%v", order.ID.String()) - req := httptest.NewRequest("PUT", path, nil) - req = suite.AuthenticateRequest(req, order.ServiceMember) - - params := ordersop.UpdateOrdersParams{ - HTTPRequest: req, - OrdersID: *handlers.FmtUUID(order.ID), - UpdateOrders: payload, - } - - fakeS3 := storageTest.NewFakeS3Storage(true) - handlerConfig := suite.HandlerConfig() - handlerConfig.SetFileStorer(fakeS3) - - handler := UpdateOrdersHandler{handlerConfig} - - response := handler.Handle(params) - - suite.IsType(&ordersop.UpdateOrdersOK{}, response) - - okResponse := response.(*ordersop.UpdateOrdersOK) - - suite.NoError(okResponse.Payload.Validate(strfmt.Default)) - suite.Equal(string(newOrdersType), string(*okResponse.Payload.OrdersType)) - - fetchedOrder, err = models.FetchOrder(suite.DB(), order.ID) - suite.NoError(err) - - fetchedPostalCode, err = fetchedOrder.GetOriginPostalCode(suite.DB()) - suite.NoError(err) - fetchedGBLOC = *fetchedOrder.OriginDutyLocationGBLOC - suite.NoError(err) - - suite.Equal("35023", fetchedPostalCode) - suite.Equal("CNNQ", fetchedGBLOC) -} - func (suite *HandlerSuite) TestEntitlementHelperFunc() { orderGrade := internalmessages.OrderPayGrade("O-3") int64Dependents := int64(2) diff --git a/pkg/models/order.go b/pkg/models/order.go index 56c71e2265a..da23b6f6c6e 100644 --- a/pkg/models/order.go +++ b/pkg/models/order.go @@ -309,49 +309,6 @@ func (o *Order) CreateNewMove(db *pop.Connection, moveOptions MoveOptions) (*Mov return createNewMove(db, *o, moveOptions) } -/* - * GetOriginPostalCode returns the GBLOC for the postal code of the the origin duty location of the order. - */ -func (o Order) GetOriginPostalCode(db *pop.Connection) (string, error) { - // Since this requires looking up the order in the DB, the order must have an ID. This means, the order has to have been created first. - if uuid.UUID.IsNil(o.ID) { - return "", errors.WithMessage(ErrInvalidOrderID, "You must create the order in the DB before getting the origin GBLOC.") - } - - err := db.Load(&o, "OriginDutyLocation.Address") - if err != nil { - if err.Error() == RecordNotFoundErrorString { - return "", errors.WithMessage(err, "No Origin Duty Location was found for the order ID "+o.ID.String()) - } - return "", err - } - - return o.OriginDutyLocation.Address.PostalCode, nil -} - -/* - * GetOriginGBLOC returns the GBLOC for the postal code of the the origin duty location of the order. - */ -func (o Order) GetOriginGBLOC(db *pop.Connection) (string, error) { - // Since this requires looking up the order in the DB, the order must have an ID. This means, the order has to have been created first. - if uuid.UUID.IsNil(o.ID) { - return "", errors.WithMessage(ErrInvalidOrderID, "You must created the order in the DB before getting the destination GBLOC.") - } - - originPostalCode, err := o.GetOriginPostalCode(db) - if err != nil { - return "", err - } - - var originGBLOC PostalCodeToGBLOC - originGBLOC, err = FetchGBLOCForPostalCode(db, originPostalCode) - if err != nil { - return "", err - } - - return originGBLOC.GBLOC, nil -} - // IsComplete checks if orders have all fields necessary to approve a move func (o *Order) IsComplete() bool { diff --git a/pkg/testdatagen/scenario/shared.go b/pkg/testdatagen/scenario/shared.go index 4450a6964e9..8d04108fd6b 100644 --- a/pkg/testdatagen/scenario/shared.go +++ b/pkg/testdatagen/scenario/shared.go @@ -10507,60 +10507,6 @@ func CreateNeedsServicesCounseling(appCtx appcontext.AppContext, ordersType inte return move } -/* -Create Needs Service Counseling in a non-default GBLOC - pass in orders with all required information, shipment type, destination type, locator -*/ -func CreateNeedsServicesCounselingInOtherGBLOC(appCtx appcontext.AppContext, ordersType internalmessages.OrdersType, shipmentType models.MTOShipmentType, destinationType *models.DestinationType, locator string) models.Move { - db := appCtx.DB() - originDutyLocationAddress := factory.BuildAddress(db, []factory.Customization{ - { - Model: models.Address{ - PostalCode: "35023", - }, - }, - }, nil) - originDutyLocation := factory.BuildDutyLocation(db, []factory.Customization{ - { - Model: models.DutyLocation{ - Name: "Test Location", - AddressID: originDutyLocationAddress.ID, - }, - }, - }, nil) - order := factory.BuildOrder(db, []factory.Customization{ - { - Model: originDutyLocation, - }, - }, nil) - move := factory.BuildMove(db, []factory.Customization{ - { - Model: order, - LinkOnly: true, - }, - { - Model: models.Move{ - Locator: locator, - Status: models.MoveStatusNeedsServiceCounseling, - }, - }, - }, nil) - - factory.BuildMTOShipment(db, []factory.Customization{ - { - Model: move, - LinkOnly: true, - }, - { - Model: models.MTOShipment{ - ShipmentType: shipmentType, - Status: models.MTOShipmentStatusSubmitted, - }, - }, - }, nil) - - return move -} - func CreateNeedsServicesCounselingWithAmendedOrders(appCtx appcontext.AppContext, userUploader *uploader.UserUploader, ordersType internalmessages.OrdersType, shipmentType models.MTOShipmentType, destinationType *models.DestinationType, locator string) models.Move { db := appCtx.DB() submittedAt := time.Now() diff --git a/pkg/testdatagen/testharness/dispatch.go b/pkg/testdatagen/testharness/dispatch.go index dd4b421cc2c..e9cf4c27d5f 100644 --- a/pkg/testdatagen/testharness/dispatch.go +++ b/pkg/testdatagen/testharness/dispatch.go @@ -50,9 +50,6 @@ var actionDispatcher = map[string]actionFunc{ "HHGMoveNeedsSC": func(appCtx appcontext.AppContext) testHarnessResponse { return MakeHHGMoveNeedsSC(appCtx) }, - "HHGMoveNeedsSCOtherGBLOC": func(appCtx appcontext.AppContext) testHarnessResponse { - return MakeHHGMoveNeedsSCOtherGBLOC(appCtx) - }, "HHGMoveAsUSMCNeedsSC": func(appCtx appcontext.AppContext) testHarnessResponse { return MakeHHGMoveNeedsServicesCounselingUSMC(appCtx) }, diff --git a/pkg/testdatagen/testharness/make_move.go b/pkg/testdatagen/testharness/make_move.go index 50acd913415..fd337bb029f 100644 --- a/pkg/testdatagen/testharness/make_move.go +++ b/pkg/testdatagen/testharness/make_move.go @@ -4013,22 +4013,6 @@ func MakeHHGMoveNeedsSC(appCtx appcontext.AppContext) models.Move { return *newmove } -// MakeHHGMoveNeedsSCOtherGBLOC creates an fully ready move needing SC approval in a non-default GBLOC -func MakeHHGMoveNeedsSCOtherGBLOC(appCtx appcontext.AppContext) models.Move { - pcos := internalmessages.OrdersTypePERMANENTCHANGEOFSTATION - hhg := models.MTOShipmentTypeHHG - locator := models.GenerateLocator() - move := scenario.CreateNeedsServicesCounselingInOtherGBLOC(appCtx, pcos, hhg, nil, locator) - - // re-fetch the move so that we ensure we have exactly what is in - // the db - newmove, err := models.FetchMove(appCtx.DB(), &auth.Session{}, move.ID) - if err != nil { - log.Panic(fmt.Errorf("failed to fetch move: %w", err)) - } - return *newmove -} - // MakeBoatHaulAwayMoveNeedsSC creates an fully ready move with a boat haul-away shipment needing SC approval func MakeBoatHaulAwayMoveNeedsSC(appCtx appcontext.AppContext) models.Move { userUploader := newUserUploader(appCtx) diff --git a/playwright/tests/office/servicescounseling/servicesCounselingFlows.spec.js b/playwright/tests/office/servicescounseling/servicesCounselingFlows.spec.js index 73adcf940f2..39fe893bc73 100644 --- a/playwright/tests/office/servicescounseling/servicesCounselingFlows.spec.js +++ b/playwright/tests/office/servicescounseling/servicesCounselingFlows.spec.js @@ -13,33 +13,6 @@ const supportingDocsEnabled = process.env.FEATURE_FLAG_MANAGE_SUPPORTING_DOCS; const LocationLookup = 'BEVERLY HILLS, CA 90210 (LOS ANGELES)'; test.describe('Services counselor user', () => { - test.describe('GBLOC tests', () => { - test.describe('Origin Duty Location', () => { - let moveLocatorKKFA = ''; - let moveLocatorCNNQ = ''; - test.beforeEach(async ({ scPage }) => { - const moveKKFA = await scPage.testHarness.buildHHGMoveNeedsSC(); - moveLocatorKKFA = moveKKFA.locator; - const moveCNNQ = await scPage.testHarness.buildHHGMoveNeedsSC(); - moveLocatorCNNQ = moveCNNQ.locator; - }); - - test('when origin duty location GBLOC matches services counselor GBLOC', async ({ page }) => { - const locatorFilter = await page.getByTestId('locator').getByTestId('TextBoxFilter'); - await locatorFilter.fill(moveLocatorKKFA); - await locatorFilter.blur(); - await expect(page.getByTestId('locator-0')).toBeVisible(); - }); - - test('when origin duty location GBLOC does not match services counselor GBLOC', async ({ page }) => { - const locatorFilter = await page.getByTestId('locator').getByTestId('TextBoxFilter'); - await locatorFilter.fill(moveLocatorCNNQ); - await locatorFilter.blur(); - await expect(page.getByTestId('locator-0')).not.toBeVisible(); - }); - }); - }); - test.describe('with basic HHG move', () => { test.beforeEach(async ({ scPage }) => { const move = await scPage.testHarness.buildHHGMoveNeedsSC(); diff --git a/playwright/tests/utils/testharness.js b/playwright/tests/utils/testharness.js index 74feee4ffef..a01dac3461e 100644 --- a/playwright/tests/utils/testharness.js +++ b/playwright/tests/utils/testharness.js @@ -28,9 +28,6 @@ export class TestHarness { * @property {string} id * @property {string} locator * @property {Object} Orders - * @property {Object} OriginDutyLocation - * @property {Object} OriginDutyLocation.Address - * @property {string} OriginDutyLocation.Address.PostalCode * @property {Object} Orders.NewDutyLocation * @property {string} Orders.NewDutyLocation.name * @property {Object} Orders.ServiceMember @@ -395,14 +392,6 @@ export class TestHarness { return this.buildDefault('HHGMoveNeedsSC'); } - /** - * Use testharness to build hhg move needing SC approval in a non-default GBLOC - * @returns {Promise} - */ - async buildHHGMoveNeedsSCInOtherGBLOC() { - return this.buildDefault('HHGMoveNeedsSCOtherGBLOC'); - } - /** * Use testharness to build hhg move as USMC needing SC approval * @returns {Promise} From 9dfc76ce90e3cc1bf31eb04641c45c0f304c0340 Mon Sep 17 00:00:00 2001 From: Alex Lusk Date: Wed, 8 Jan 2025 16:00:43 +0000 Subject: [PATCH 28/54] counseling office in orders card test fix --- src/components/Office/DefinitionLists/OrdersList.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/Office/DefinitionLists/OrdersList.jsx b/src/components/Office/DefinitionLists/OrdersList.jsx index f915abc480c..27de47eb1fc 100644 --- a/src/components/Office/DefinitionLists/OrdersList.jsx +++ b/src/components/Office/DefinitionLists/OrdersList.jsx @@ -60,7 +60,7 @@ const OrdersList = ({ ordersInfo, moveInfo, showMissingWarnings }) => {
Counseling office
- {moveInfo.counselingOffice?.name ? moveInfo.counselingOffice?.name : '—'} + {moveInfo?.counselingOffice?.name ? moveInfo.counselingOffice.name : '—'}
Date: Wed, 8 Jan 2025 16:30:20 +0000 Subject: [PATCH 29/54] changes requested on PR --- .../internal/payloads/model_to_payload.go | 29 ++++++++++--------- .../servicesCounselingFlows.spec.js | 2 -- swagger-def/prime.yaml | 6 ++-- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go b/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go index 8869d79511a..e15a6fea2c4 100644 --- a/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go +++ b/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go @@ -2561,22 +2561,23 @@ func SearchMoves(appCtx appcontext.AppContext, moves models.Moves) *ghcmessages. originGBLOC = swag.StringValue(move.Orders.OriginDutyLocationGBLOC) } - var destinationGBLOC ghcmessages.GBLOC - var PostalCodeToGBLOC models.PostalCodeToGBLOC + // populates the destination gbloc of the move + var destinationGBLOC string var err error - if numShipments > 0 && move.MTOShipments[0].DestinationAddress != nil { - PostalCodeToGBLOC, err = models.FetchGBLOCForPostalCode(appCtx.DB(), move.MTOShipments[0].DestinationAddress.PostalCode) - } else { - // If the move has no shipments or the shipment has no destination address fall back to the origin duty location GBLOC - PostalCodeToGBLOC, err = models.FetchGBLOCForPostalCode(appCtx.DB(), move.Orders.NewDutyLocation.Address.PostalCode) + destinationGBLOC, err = move.GetDestinationGBLOC(appCtx.DB()) + if err != nil { + destinationGBLOC = "" } + if len(destinationGBLOC) > 0 && customer.Affiliation.String() == "MARINES" { + destinationGBLOC = "USMC/" + destinationGBLOC + } + destinationGblocMessage := ghcmessages.GBLOC(destinationGBLOC) + // populates the destination postal code of the move + var destinationPostalCode string + destinationPostalCode, err = move.GetDestinationPostalCode(appCtx.DB()) if err != nil { - destinationGBLOC = *ghcmessages.NewGBLOC("") - } else if customer.Affiliation.String() == "MARINES" { - destinationGBLOC = ghcmessages.GBLOC("USMC/" + PostalCodeToGBLOC.GBLOC) - } else { - destinationGBLOC = ghcmessages.GBLOC(PostalCodeToGBLOC.GBLOC) + destinationPostalCode = "" } searchMoves[i] = &ghcmessages.SearchMove{ @@ -2590,12 +2591,12 @@ func SearchMoves(appCtx appcontext.AppContext, moves models.Moves) *ghcmessages. Locator: move.Locator, ShipmentsCount: int64(numShipments), OriginDutyLocationPostalCode: move.Orders.OriginDutyLocation.Address.PostalCode, - DestinationPostalCode: move.Orders.NewDutyLocation.Address.PostalCode, + DestinationPostalCode: destinationPostalCode, OrderType: string(move.Orders.OrdersType), RequestedPickupDate: pickupDate, RequestedDeliveryDate: deliveryDate, OriginGBLOC: ghcmessages.GBLOC(originGBLOC), - DestinationGBLOC: destinationGBLOC, + DestinationGBLOC: destinationGblocMessage, LockedByOfficeUserID: handlers.FmtUUIDPtr(move.LockedByOfficeUserID), LockExpiresAt: handlers.FmtDateTimePtr(move.LockExpiresAt), } diff --git a/playwright/tests/office/servicescounseling/servicesCounselingFlows.spec.js b/playwright/tests/office/servicescounseling/servicesCounselingFlows.spec.js index 39fe893bc73..6ab07e9026c 100644 --- a/playwright/tests/office/servicescounseling/servicesCounselingFlows.spec.js +++ b/playwright/tests/office/servicescounseling/servicesCounselingFlows.spec.js @@ -387,8 +387,6 @@ test.describe('Services counselor user', () => { // Edit the shipment so that the tag disappears await page.locator('[data-testid="ShipmentContainer"] .usa-button').last().click(); await page.locator('select[name="destinationType"]').selectOption({ label: 'Home of selection (HOS)' }); - await page.getByLabel('Requested pickup date').fill('16 Mar 2022'); - await page.locator('[data-testid="submitForm"]').click(); await scPage.waitForLoading(); diff --git a/swagger-def/prime.yaml b/swagger-def/prime.yaml index 60e686475ba..8265e6ecfb6 100644 --- a/swagger-def/prime.yaml +++ b/swagger-def/prime.yaml @@ -1488,11 +1488,13 @@ definitions: format: uuid type: string destinationGBLOC: - example: 'AGFM' type: string + example: 'JFK' + readOnly: true destinationPostalCode: - example: '90210' type: string + example: '90210' + readOnly: true referenceId: example: 1001-3456 type: string From 03b723f37f3a32fabb359b8d950d20394d225f00 Mon Sep 17 00:00:00 2001 From: AaronW Date: Wed, 8 Jan 2025 16:44:41 +0000 Subject: [PATCH 30/54] let swagger regen scripts --- swagger/prime.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/swagger/prime.yaml b/swagger/prime.yaml index 5c661452350..a88c878a51a 100644 --- a/swagger/prime.yaml +++ b/swagger/prime.yaml @@ -1849,11 +1849,13 @@ definitions: format: uuid type: string destinationGBLOC: - example: AGFM type: string + example: JFK + readOnly: true destinationPostalCode: - example: '90210' type: string + example: '90210' + readOnly: true referenceId: example: 1001-3456 type: string From 91d2ebaf3e95715b191651f6afd40d834cf99240 Mon Sep 17 00:00:00 2001 From: AaronW Date: Wed, 8 Jan 2025 16:51:13 +0000 Subject: [PATCH 31/54] changes to swagger-def requested --- swagger-def/prime.yaml | 8 ++++---- swagger/prime.yaml | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/swagger-def/prime.yaml b/swagger-def/prime.yaml index 8265e6ecfb6..3145b3a8e08 100644 --- a/swagger-def/prime.yaml +++ b/swagger-def/prime.yaml @@ -1483,10 +1483,6 @@ definitions: format: date-time type: string readOnly: true - orderID: - example: c56a4180-65aa-42ec-a945-5fd21dec0538 - format: uuid - type: string destinationGBLOC: type: string example: 'JFK' @@ -1495,6 +1491,10 @@ definitions: type: string example: '90210' readOnly: true + orderID: + example: c56a4180-65aa-42ec-a945-5fd21dec0538 + format: uuid + type: string referenceId: example: 1001-3456 type: string diff --git a/swagger/prime.yaml b/swagger/prime.yaml index a88c878a51a..e1af75ce85b 100644 --- a/swagger/prime.yaml +++ b/swagger/prime.yaml @@ -1844,10 +1844,6 @@ definitions: format: date-time type: string readOnly: true - orderID: - example: c56a4180-65aa-42ec-a945-5fd21dec0538 - format: uuid - type: string destinationGBLOC: type: string example: JFK @@ -1856,6 +1852,10 @@ definitions: type: string example: '90210' readOnly: true + orderID: + example: c56a4180-65aa-42ec-a945-5fd21dec0538 + format: uuid + type: string referenceId: example: 1001-3456 type: string From dcfec8233ffa7a344f2b305f38623bcc245693e6 Mon Sep 17 00:00:00 2001 From: AaronW Date: Wed, 8 Jan 2025 17:10:06 +0000 Subject: [PATCH 32/54] some gen needed --- pkg/gen/primeapi/embedded_spec.go | 8 ++++++-- pkg/gen/primemessages/list_move.go | 30 +++++++++++++++++++++++++++++- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/pkg/gen/primeapi/embedded_spec.go b/pkg/gen/primeapi/embedded_spec.go index 4f00b706379..3cb80be3a33 100644 --- a/pkg/gen/primeapi/embedded_spec.go +++ b/pkg/gen/primeapi/embedded_spec.go @@ -1887,10 +1887,12 @@ func init() { }, "destinationGBLOC": { "type": "string", - "example": "AGFM" + "readOnly": true, + "example": "JFK" }, "destinationPostalCode": { "type": "string", + "readOnly": true, "example": "90210" }, "eTag": { @@ -6799,10 +6801,12 @@ func init() { }, "destinationGBLOC": { "type": "string", - "example": "AGFM" + "readOnly": true, + "example": "JFK" }, "destinationPostalCode": { "type": "string", + "readOnly": true, "example": "90210" }, "eTag": { diff --git a/pkg/gen/primemessages/list_move.go b/pkg/gen/primemessages/list_move.go index 866dd6e8810..27440ca263b 100644 --- a/pkg/gen/primemessages/list_move.go +++ b/pkg/gen/primemessages/list_move.go @@ -39,11 +39,13 @@ type ListMove struct { CreatedAt strfmt.DateTime `json:"createdAt,omitempty"` // destination g b l o c - // Example: AGFM + // Example: JFK + // Read Only: true DestinationGBLOC string `json:"destinationGBLOC,omitempty"` // destination postal code // Example: 90210 + // Read Only: true DestinationPostalCode string `json:"destinationPostalCode,omitempty"` // e tag @@ -274,6 +276,14 @@ func (m *ListMove) ContextValidate(ctx context.Context, formats strfmt.Registry) res = append(res, err) } + if err := m.contextValidateDestinationGBLOC(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateDestinationPostalCode(ctx, formats); err != nil { + res = append(res, err) + } + if err := m.contextValidateETag(ctx, formats); err != nil { res = append(res, err) } @@ -340,6 +350,24 @@ func (m *ListMove) contextValidateCreatedAt(ctx context.Context, formats strfmt. return nil } +func (m *ListMove) contextValidateDestinationGBLOC(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "destinationGBLOC", "body", string(m.DestinationGBLOC)); err != nil { + return err + } + + return nil +} + +func (m *ListMove) contextValidateDestinationPostalCode(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "destinationPostalCode", "body", string(m.DestinationPostalCode)); err != nil { + return err + } + + return nil +} + func (m *ListMove) contextValidateETag(ctx context.Context, formats strfmt.Registry) error { if err := validate.ReadOnly(ctx, "eTag", "body", string(m.ETag)); err != nil { From 4221cc2b3b4816185df8b97384167229261d98e9 Mon Sep 17 00:00:00 2001 From: Daniel Jordan Date: Thu, 26 Dec 2024 21:16:44 +0000 Subject: [PATCH 33/54] initial commit, added pricers, waiting on ordering issue regarding packing/unpacking --- migrations/app/migrations_manifest.txt | 1 + ...aram_values_to_service_params_table.up.sql | 103 +++++++++++++ .../allowed_payment_service_item_params.go | 15 ++ pkg/handlers/primeapi/payment_request_test.go | 14 -- pkg/services/ghc_rate_engine.go | 32 ++++ .../ghcrateengine/fuel_surcharge_pricer.go | 2 +- .../ghcrateengine/intl_hhg_pack_pricer.go | 56 +++++++ .../ghcrateengine/intl_hhg_unpack_pricer.go | 56 +++++++ .../intl_port_fuel_surcharge_pricer.go | 111 ++++++++++++++ .../intl_shipping_and_linehaul_pricer.go | 137 ++++++++++++++++++ .../ghcrateengine/pricer_helpers_intl.go | 86 +++++++++++ .../ghcrateengine/pricer_query_helpers.go | 18 +++ .../ghcrateengine/service_item_pricer.go | 10 ++ pkg/services/ghcrateengine/shared.go | 3 + .../payment_request_creator.go | 1 - 15 files changed, 629 insertions(+), 16 deletions(-) create mode 100644 migrations/app/schema/20241226173330_add_intl_param_values_to_service_params_table.up.sql create mode 100644 pkg/services/ghcrateengine/intl_hhg_pack_pricer.go create mode 100644 pkg/services/ghcrateengine/intl_hhg_unpack_pricer.go create mode 100644 pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer.go create mode 100644 pkg/services/ghcrateengine/intl_shipping_and_linehaul_pricer.go create mode 100644 pkg/services/ghcrateengine/pricer_helpers_intl.go diff --git a/migrations/app/migrations_manifest.txt b/migrations/app/migrations_manifest.txt index 32afaa072da..be5080ccd3d 100644 --- a/migrations/app/migrations_manifest.txt +++ b/migrations/app/migrations_manifest.txt @@ -1058,6 +1058,7 @@ 20241217180136_add_AK_zips_to_zip3_distances.up.sql 20241218201833_add_PPPO_BASE_ELIZABETH.up.sql 20241220171035_add_additional_AK_zips_to_zip3_distances.up.sql +20241226173330_add_intl_param_values_to_service_params_table.up.sql 20241227153723_remove_empty_string_emplid_values.up.sql 20241227202424_insert_transportation_offices_camp_pendelton.up.sql 20241230190638_remove_AK_zips_from_zip3.up.sql diff --git a/migrations/app/schema/20241226173330_add_intl_param_values_to_service_params_table.up.sql b/migrations/app/schema/20241226173330_add_intl_param_values_to_service_params_table.up.sql new file mode 100644 index 00000000000..26ccf6dff90 --- /dev/null +++ b/migrations/app/schema/20241226173330_add_intl_param_values_to_service_params_table.up.sql @@ -0,0 +1,103 @@ + +-- inserting params for PODFSC +INSERT INTO service_params (id,service_id,service_item_param_key_id,created_at,updated_at,is_optional) VALUES + ('9848562b-50c1-4e6e-aef0-f9539bf243fa'::uuid,'388115e8-abe9-441d-96cf-a39f24baa0a3','adeb57e5-6b1c-4c0f-b5c9-9e57e600303f','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('9c244768-07ce-4368-936b-0ac14a8078a4'::uuid,'388115e8-abe9-441d-96cf-a39f24baa0a3','14a93209-370d-42f3-8ca2-479c953be839','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('5668accf-afac-46a3-b097-177b74076fc9'::uuid,'388115e8-abe9-441d-96cf-a39f24baa0a3','54c9cc4e-0d46-4956-b92e-be9847f894de','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('0e8bd8d5-40fd-46fb-8228-5b66088681a2'::uuid,'388115e8-abe9-441d-96cf-a39f24baa0a3','b9739817-6408-4829-8719-1e26f8a9ceb3','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('a22090b4-3ce6-448d-82b0-36592655d822'::uuid,'388115e8-abe9-441d-96cf-a39f24baa0a3','117da2f5-fff0-41e0-bba1-837124373098','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('d59c674a-eaf9-4158-8303-dbcb50a7230b'::uuid,'388115e8-abe9-441d-96cf-a39f24baa0a3','6ba0aeca-19f8-4247-a317-fffa81c5d5c1','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('450dad51-dcc9-4258-ba56-db39de6a8637'::uuid,'388115e8-abe9-441d-96cf-a39f24baa0a3','0c95581d-67de-48ae-a54b-a3748851d613','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('bf006fc0-8f33-4553-b567-a529af04eafe'::uuid,'388115e8-abe9-441d-96cf-a39f24baa0a3','45fce5ce-6a4c-4a6c-ab37-16ee0133628c','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('ad005c4b-dd71-4d42-99d8-95de7b1ed571'::uuid,'388115e8-abe9-441d-96cf-a39f24baa0a3','e6096350-9ac4-40aa-90c4-bbdff6e0b194','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('274356bc-8139-4e34-9332-ce7396f42c79'::uuid,'388115e8-abe9-441d-96cf-a39f24baa0a3','a1d31d35-c87d-4a7d-b0b8-8b2646b96e43','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('aa68a318-fe17-445c-ab53-0505fe48d0bb'::uuid,'388115e8-abe9-441d-96cf-a39f24baa0a3','b79978a7-21b7-4656-af83-25585acffb20','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), + ('882e7978-9754-4c8e-bb71-8fe4f4059503'::uuid,'388115e8-abe9-441d-96cf-a39f24baa0a3','d87d82da-3ac2-44e8-bce0-cb4de40f9a72','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), + ('8fc4571d-235b-4d4f-90e4-77e7ad9250d5'::uuid,'388115e8-abe9-441d-96cf-a39f24baa0a3','1e6257e9-757d-4d59-8846-727dd8a055e7','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), + ('836606ce-894e-4765-bba5-b696cb5fe8cc'::uuid,'388115e8-abe9-441d-96cf-a39f24baa0a3','2cbc2251-eb7d-4c69-a120-9a83785c994b','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false); + +-- inserting params for POEFSC +INSERT INTO service_params (id,service_id,service_item_param_key_id,created_at,updated_at,is_optional) VALUES + ('a57c01b1-cb1c-40f7-87e0-99d1dfd69902'::uuid,'f75758d8-2fcd-40ba-9432-3ff3032a71d1','adeb57e5-6b1c-4c0f-b5c9-9e57e600303f','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('8f49b289-c1d0-438d-b9fc-3cb234167987'::uuid,'f75758d8-2fcd-40ba-9432-3ff3032a71d1','14a93209-370d-42f3-8ca2-479c953be839','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('46742d5d-dde9-4e3c-9e59-f2cf87ff016a'::uuid,'f75758d8-2fcd-40ba-9432-3ff3032a71d1','54c9cc4e-0d46-4956-b92e-be9847f894de','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('f6e178a9-5de3-4312-87c5-81d88ae0b45b'::uuid,'f75758d8-2fcd-40ba-9432-3ff3032a71d1','b9739817-6408-4829-8719-1e26f8a9ceb3','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('34d4c1a3-b218-4083-93b5-cbbc57688594'::uuid,'f75758d8-2fcd-40ba-9432-3ff3032a71d1','117da2f5-fff0-41e0-bba1-837124373098','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('6b07db8d-9f7a-4d33-9a5f-7d98fc7038f1'::uuid,'f75758d8-2fcd-40ba-9432-3ff3032a71d1','6ba0aeca-19f8-4247-a317-fffa81c5d5c1','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('9d8cc94b-4d5f-4c62-b9db-b87bb0213b8d'::uuid,'f75758d8-2fcd-40ba-9432-3ff3032a71d1','0c95581d-67de-48ae-a54b-a3748851d613','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('d63a9079-c99b-4d92-864f-46cc9bb18388'::uuid,'f75758d8-2fcd-40ba-9432-3ff3032a71d1','45fce5ce-6a4c-4a6c-ab37-16ee0133628c','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('625970f9-7c93-4c3d-97fe-62f5a9d598f1'::uuid,'f75758d8-2fcd-40ba-9432-3ff3032a71d1','e6096350-9ac4-40aa-90c4-bbdff6e0b194','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('34fa9839-8289-473a-9095-c2b1159ef5d3'::uuid,'f75758d8-2fcd-40ba-9432-3ff3032a71d1','a1d31d35-c87d-4a7d-b0b8-8b2646b96e43','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('f747f231-66f5-4a52-bb71-8d7b5f618d23'::uuid,'f75758d8-2fcd-40ba-9432-3ff3032a71d1','b79978a7-21b7-4656-af83-25585acffb20','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), + ('0177f93a-15f6-41e5-a3ca-dc8f5bb727ab'::uuid,'f75758d8-2fcd-40ba-9432-3ff3032a71d1','d87d82da-3ac2-44e8-bce0-cb4de40f9a72','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), + ('cbf5b41f-2d89-4284-858f-d2cda7b060f7'::uuid,'f75758d8-2fcd-40ba-9432-3ff3032a71d1','1e6257e9-757d-4d59-8846-727dd8a055e7','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), + ('ebed3001-93f1-49ba-a935-3d463b0d76fc'::uuid,'f75758d8-2fcd-40ba-9432-3ff3032a71d1','2cbc2251-eb7d-4c69-a120-9a83785c994b','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false); + + +-- inserting params for ISLH +INSERT INTO service_params (id,service_id,service_item_param_key_id,created_at,updated_at,is_optional) VALUES + ('7e2e4b79-2f4c-451e-a28f-df1ad61c4f3b'::uuid,'9f3d551a-0725-430e-897e-80ee9add3ae9','164050e3-e35b-480d-bf6e-ed2fab86f370','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), + ('adff4edb-1f78-45de-9269-29016d09d597'::uuid,'9f3d551a-0725-430e-897e-80ee9add3ae9','e6096350-9ac4-40aa-90c4-bbdff6e0b194','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('2239c77e-e073-47f3-aed7-e1edc6b8a9a4'::uuid,'9f3d551a-0725-430e-897e-80ee9add3ae9','45fce5ce-6a4c-4a6c-ab37-16ee0133628c','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('53ab68f9-8da7-48fa-80ac-bf91f05a4650'::uuid,'9f3d551a-0725-430e-897e-80ee9add3ae9','b9739817-6408-4829-8719-1e26f8a9ceb3','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('e9d3bc63-bc4a-43d0-98f1-48e3e51d2307'::uuid,'9f3d551a-0725-430e-897e-80ee9add3ae9','0c95581d-67de-48ae-a54b-a3748851d613','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('6632cbad-3fa0-46b8-a1ac-0b2bb5123401'::uuid,'9f3d551a-0725-430e-897e-80ee9add3ae9','b79978a7-21b7-4656-af83-25585acffb20','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), + ('4ef58b87-8a93-44ec-b5c8-5b5779d8392e'::uuid,'9f3d551a-0725-430e-897e-80ee9add3ae9','599bbc21-8d1d-4039-9a89-ff52e3582144','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('d53e5f61-e92f-4ecb-9e1d-72ab8a99790f'::uuid,'9f3d551a-0725-430e-897e-80ee9add3ae9','a335e38a-7d95-4ba3-9c8b-75a5e00948bc','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), + ('d94d2c5e-e91a-47d1-96b3-1c5d68a745dd'::uuid,'9f3d551a-0725-430e-897e-80ee9add3ae9','b03af5dc-7701-4e22-a986-d1889a2a8f27','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), + ('7b8db256-e881-451e-a722-6431784e957f'::uuid,'9f3d551a-0725-430e-897e-80ee9add3ae9','add5114b-2a23-4e23-92b3-6dd0778dfc33','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), + ('fedbd62d-d2fa-42b1-b6f6-c9c07e8c4014'::uuid,'9f3d551a-0725-430e-897e-80ee9add3ae9','a1d31d35-c87d-4a7d-b0b8-8b2646b96e43','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('c7a66e66-dabe-4f0b-a70d-f9639e87761a'::uuid,'9f3d551a-0725-430e-897e-80ee9add3ae9','2e091a7d-a1fd-4017-9f2d-73ad752a30c2','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('18f8b39f-37a5-4536-85d8-4b8b0a6bff94'::uuid,'9f3d551a-0725-430e-897e-80ee9add3ae9','739bbc23-cd08-4612-8e5d-da992202344e','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('7f20ed2e-1bdf-4370-b028-1251c07d3da1'::uuid,'9f3d551a-0725-430e-897e-80ee9add3ae9','95ee2e21-b232-4d74-9ec5-218564a8a8b9','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('a5be6b6f-e007-4d9f-8b1b-63e8ed5c4337'::uuid,'9f3d551a-0725-430e-897e-80ee9add3ae9','9de7fd2a-75c7-4c5c-ba5d-1a92f0b2f5f4','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('df5665d7-7b3d-487d-9d71-95d0e2832ae1'::uuid,'9f3d551a-0725-430e-897e-80ee9add3ae9','d87d82da-3ac2-44e8-bce0-cb4de40f9a72','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), + ('c08f7ab1-6c3c-4627-b22f-1e987ba6f4f2'::uuid,'9f3d551a-0725-430e-897e-80ee9add3ae9','1e6257e9-757d-4d59-8846-727dd8a055e7','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), + ('4d9ed9b0-957d-4e6a-a3d4-5e2e2784ef62'::uuid,'9f3d551a-0725-430e-897e-80ee9add3ae9','14a93209-370d-42f3-8ca2-479c953be839','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), + ('6acb30b9-65a0-4902-85ed-1acb6f4ac930'::uuid,'9f3d551a-0725-430e-897e-80ee9add3ae9','5335e243-ab5b-4906-b84f-bd8c35ba64b3','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('fd83c2ba-0c59-4598-81d6-b56cc8d9979d'::uuid,'9f3d551a-0725-430e-897e-80ee9add3ae9','2cbc2251-eb7d-4c69-a120-9a83785c994b','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false); + +-- inserting params fo IOSFSC +INSERT INTO service_params (id,service_id,service_item_param_key_id,created_at,updated_at,is_optional) VALUES + ('f61ab040-dab4-4505-906d-d9a3a5da3515'::uuid,'81e29d0c-02a6-4a7a-be02-554deb3ee49e','0c95581d-67de-48ae-a54b-a3748851d613','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('421053c8-7b4a-44fc-8c73-14b72755c1f7'::uuid,'81e29d0c-02a6-4a7a-be02-554deb3ee49e','117da2f5-fff0-41e0-bba1-837124373098','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('8f8a8783-0ca2-4f0f-961f-07d1e3cdbf64'::uuid,'81e29d0c-02a6-4a7a-be02-554deb3ee49e','14a93209-370d-42f3-8ca2-479c953be839','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('ab332c8f-f46e-4d49-b29a-6adf7e67f9c7'::uuid,'81e29d0c-02a6-4a7a-be02-554deb3ee49e','1e6257e9-757d-4d59-8846-727dd8a055e7','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), + ('a6e24b83-9cb4-4e56-9e38-7bdbd9d5c5fe'::uuid,'81e29d0c-02a6-4a7a-be02-554deb3ee49e','84d86517-9b88-4520-8d67-5ba892b85d10','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('4e05a7c7-bd0a-4c94-a99b-f052a8812aef'::uuid,'81e29d0c-02a6-4a7a-be02-554deb3ee49e','54c9cc4e-0d46-4956-b92e-be9847f894de','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('8ec5cd5d-d249-42e9-b11d-76a243d4045f'::uuid,'81e29d0c-02a6-4a7a-be02-554deb3ee49e','6ba0aeca-19f8-4247-a317-fffa81c5d5c1','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('2b6b9d89-65f3-4291-9e44-48d18d2a4070'::uuid,'81e29d0c-02a6-4a7a-be02-554deb3ee49e','a1d31d35-c87d-4a7d-b0b8-8b2646b96e43','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('a3d3c08f-d2a3-4ad1-b85f-b1daee12d71c'::uuid,'81e29d0c-02a6-4a7a-be02-554deb3ee49e','adeb57e5-6b1c-4c0f-b5c9-9e57e600303f','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('94d45661-82ac-479f-b7be-8e7a50ad46db'::uuid,'81e29d0c-02a6-4a7a-be02-554deb3ee49e','b79978a7-21b7-4656-af83-25585acffb20','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), + ('c897cfa3-9b06-47b5-8e12-522f0897e59a'::uuid,'81e29d0c-02a6-4a7a-be02-554deb3ee49e','b9739817-6408-4829-8719-1e26f8a9ceb3','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('61832b60-2a2d-4e35-a799-b7ff9fa6a01e'::uuid,'81e29d0c-02a6-4a7a-be02-554deb3ee49e','d87d82da-3ac2-44e8-bce0-cb4de40f9a72','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), + ('46531d32-91a5-4d98-a206-d0f1e14e2ff4'::uuid,'81e29d0c-02a6-4a7a-be02-554deb3ee49e','cd6d6ddf-7104-4d24-a8d6-d37fed61defe','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('a1d4f95e-f28f-4b6a-b83f-8f328f2b2498'::uuid,'81e29d0c-02a6-4a7a-be02-554deb3ee49e','f9753611-4b3e-4bf5-8e00-6d9ce9900f50','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false); + + +-- inserting params fo IDSFSC +INSERT INTO service_params (id,service_id,service_item_param_key_id,created_at,updated_at,is_optional) VALUES + ('25d90d5b-c58f-45e7-8c60-e7f63a0535b6'::uuid,'690a5fc1-0ea5-4554-8294-a367b5daefa9','0c95581d-67de-48ae-a54b-a3748851d613','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('8bb29da3-32e7-4e98-b241-63b8c8c81c3b'::uuid,'690a5fc1-0ea5-4554-8294-a367b5daefa9','117da2f5-fff0-41e0-bba1-837124373098','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('b8c12287-dcf6-4f88-bf7b-f7e99283f23d'::uuid,'690a5fc1-0ea5-4554-8294-a367b5daefa9','14a93209-370d-42f3-8ca2-479c953be839','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('3b9f3eab-8e18-4888-81f4-c442b4e951cf'::uuid,'690a5fc1-0ea5-4554-8294-a367b5daefa9','1e6257e9-757d-4d59-8846-727dd8a055e7','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), + ('7a8430f7-ff55-4a80-b174-e1d4a2f21f25'::uuid,'690a5fc1-0ea5-4554-8294-a367b5daefa9','70eecf7f-beae-4906-95ba-cbfe6797cf3a','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('2eaf2e5b-254e-48f0-a2c5-98d04087293f'::uuid,'690a5fc1-0ea5-4554-8294-a367b5daefa9','54c9cc4e-0d46-4956-b92e-be9847f894de','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('71f571c5-99a0-420a-b375-bb859e3488a2'::uuid,'690a5fc1-0ea5-4554-8294-a367b5daefa9','6ba0aeca-19f8-4247-a317-fffa81c5d5c1','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('cce73f9e-e3db-4d7f-a908-d9985f1b3f27'::uuid,'690a5fc1-0ea5-4554-8294-a367b5daefa9','a1d31d35-c87d-4a7d-b0b8-8b2646b96e43','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('46c559e5-9f49-4b7e-98b6-b9d8e2a4e2cf'::uuid,'690a5fc1-0ea5-4554-8294-a367b5daefa9','adeb57e5-6b1c-4c0f-b5c9-9e57e600303f','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('4512b905-cb68-4087-90b5-74e80ba9ec16'::uuid,'690a5fc1-0ea5-4554-8294-a367b5daefa9','b79978a7-21b7-4656-af83-25585acffb20','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), + ('d9887f60-e930-4f95-b53e-e9f0d8a445d3'::uuid,'690a5fc1-0ea5-4554-8294-a367b5daefa9','b9739817-6408-4829-8719-1e26f8a9ceb3','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('f65b54fa-0e1c-45cc-b7f4-b41d356b970d'::uuid,'690a5fc1-0ea5-4554-8294-a367b5daefa9','d87d82da-3ac2-44e8-bce0-cb4de40f9a72','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), + ('ce36b6e0-bcbf-4e96-9c5e-bd93fe9084c9'::uuid,'690a5fc1-0ea5-4554-8294-a367b5daefa9','cd6d6ddf-7104-4d24-a8d6-d37fed61defe','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('d9acb388-09a5-464b-bb50-bf418b25e96a'::uuid,'690a5fc1-0ea5-4554-8294-a367b5daefa9','f9753611-4b3e-4bf5-8e00-6d9ce9900f50','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false); + +-- inserting params fo IHPK +INSERT INTO service_params (id,service_id,service_item_param_key_id,created_at,updated_at,is_optional) VALUES + ('d9acb388-09a5-464b-bb50-bf418b25e96b'::uuid,'67ba1eaf-6ffd-49de-9a69-497be7789877','a1d31d35-c87d-4a7d-b0b8-8b2646b96e43','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('3c2297b0-1ec7-4261-a41d-37e58999258b'::uuid,'67ba1eaf-6ffd-49de-9a69-497be7789877','cd37b2a6-ac7d-4c93-a148-ca67f7f67cff','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false); + +-- inserting params fo IHUPK +INSERT INTO service_params (id,service_id,service_item_param_key_id,created_at,updated_at,is_optional) VALUES + ('d9acb388-09a5-464b-bb50-bf418b25e96c'::uuid,'56e91c2d-015d-4243-9657-3ed34867abaa','a1d31d35-c87d-4a7d-b0b8-8b2646b96e43','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('c045524a-90ec-4116-80a1-e2edb5cdf38f'::uuid,'56e91c2d-015d-4243-9657-3ed34867abaa','cd37b2a6-ac7d-4c93-a148-ca67f7f67cff','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false); diff --git a/pkg/handlers/primeapi/allowed_payment_service_item_params.go b/pkg/handlers/primeapi/allowed_payment_service_item_params.go index cc40edc4ff8..809582a592a 100644 --- a/pkg/handlers/primeapi/allowed_payment_service_item_params.go +++ b/pkg/handlers/primeapi/allowed_payment_service_item_params.go @@ -67,6 +67,21 @@ var ( models.ReServiceCodeDOSFSC: { models.ServiceItemParamNameWeightBilled, }, + models.ReServiceCodeISLH: { + models.ServiceItemParamNameWeightBilled, + }, + models.ReServiceCodeIHPK: { + models.ServiceItemParamNameWeightBilled, + }, + models.ReServiceCodeIHUPK: { + models.ServiceItemParamNameWeightBilled, + }, + models.ReServiceCodePOEFSC: { + models.ServiceItemParamNameWeightBilled, + }, + models.ReServiceCodePODFSC: { + models.ServiceItemParamNameWeightBilled, + }, } ) diff --git a/pkg/handlers/primeapi/payment_request_test.go b/pkg/handlers/primeapi/payment_request_test.go index 768e10c0033..114f03aa49b 100644 --- a/pkg/handlers/primeapi/payment_request_test.go +++ b/pkg/handlers/primeapi/payment_request_test.go @@ -947,13 +947,6 @@ func (suite *HandlerSuite) TestCreatePaymentRequestHandlerInvalidMTOReferenceID( suite.IsType(&paymentrequestop.CreatePaymentRequestUnprocessableEntity{}, response) typedResponse := response.(*paymentrequestop.CreatePaymentRequestUnprocessableEntity) - // Validate outgoing payload - // TODO: Can't validate the response because of the issue noted below. Figure out a way to - // either alter the service or relax the swagger requirements. - // suite.NoError(typedResponse.Payload.Validate(strfmt.Default)) - // CreatePaymentRequestCheck is returning apperror.InvalidCreateInputError without any validation errors - // so InvalidFields won't be added to the payload. - suite.Contains(*typedResponse.Payload.Detail, "has missing ReferenceID") }) @@ -1013,13 +1006,6 @@ func (suite *HandlerSuite) TestCreatePaymentRequestHandlerInvalidMTOReferenceID( suite.IsType(&paymentrequestop.CreatePaymentRequestUnprocessableEntity{}, response) typedResponse := response.(*paymentrequestop.CreatePaymentRequestUnprocessableEntity) - // Validate outgoing payload - // TODO: Can't validate the response because of the issue noted below. Figure out a way to - // either alter the service or relax the swagger requirements. - // suite.NoError(typedResponse.Payload.Validate(strfmt.Default)) - // CreatePaymentRequestCheck is returning apperror.InvalidCreateInputError without any validation errors - // so InvalidFields won't be added to the payload. - suite.Contains(*typedResponse.Payload.Detail, "has missing ReferenceID") }) } diff --git a/pkg/services/ghc_rate_engine.go b/pkg/services/ghc_rate_engine.go index 5d17e0388ce..9bee5c8f59f 100644 --- a/pkg/services/ghc_rate_engine.go +++ b/pkg/services/ghc_rate_engine.go @@ -232,3 +232,35 @@ type DomesticOriginSITFuelSurchargePricer interface { ) ParamsPricer } + +// IntlShippingAndLinehaulPricer prices international shipping and linehaul for a move +// +//go:generate mockery --name IntlShippingAndLinehaulPricer +type IntlShippingAndLinehaulPricer interface { + Price(appCtx appcontext.AppContext, contractCode string, requestedPickupDate time.Time, distance unit.Miles, weight unit.Pound, serviceArea string, isPPM bool) (unit.Cents, PricingDisplayParams, error) + ParamsPricer +} + +// IntlHHGPackPricer prices international packing for an HHG shipment within a move +// +//go:generate mockery --name IntlHHGPackPricer +type IntlHHGPackPricer interface { + Price(appCtx appcontext.AppContext, contractCode string, requestedPickupDate time.Time, weight unit.Pound, servicesScheduleOrigin int, isPPM bool) (unit.Cents, PricingDisplayParams, error) + ParamsPricer +} + +// IntlHHGUnpackPricer prices international unpacking for an HHG shipment within a move +// +//go:generate mockery --name IntlHHGUnpackPricer +type IntlHHGUnpackPricer interface { + Price(appCtx appcontext.AppContext, contractCode string, requestedPickupDate time.Time, weight unit.Pound, servicesScheduleDest int, isPPM bool) (unit.Cents, PricingDisplayParams, error) + ParamsPricer +} + +// IntlPortFuelSurchargePricer prices the POEFSC/PODFSC service items on a shipment within a move +// +//go:generate mockery --name IntlPortFuelSurchargePricer +type IntlPortFuelSurchargePricer interface { + Price(appCtx appcontext.AppContext, actualPickupDate time.Time, distance unit.Miles, weight unit.Pound, fscWeightBasedDistanceMultiplier float64, eiaFuelPrice unit.Millicents, isPPM bool) (unit.Cents, PricingDisplayParams, error) + ParamsPricer +} diff --git a/pkg/services/ghcrateengine/fuel_surcharge_pricer.go b/pkg/services/ghcrateengine/fuel_surcharge_pricer.go index 7371a7f397f..72b71bafce6 100644 --- a/pkg/services/ghcrateengine/fuel_surcharge_pricer.go +++ b/pkg/services/ghcrateengine/fuel_surcharge_pricer.go @@ -27,7 +27,7 @@ func NewFuelSurchargePricer() services.FuelSurchargePricer { return &fuelSurchargePricer{} } -// Price determines the price for a counseling service +// Price determines the price for fuel surcharge func (p fuelSurchargePricer) Price(_ appcontext.AppContext, actualPickupDate time.Time, distance unit.Miles, weight unit.Pound, fscWeightBasedDistanceMultiplier float64, eiaFuelPrice unit.Millicents, isPPM bool) (unit.Cents, services.PricingDisplayParams, error) { // Validate parameters if actualPickupDate.IsZero() { diff --git a/pkg/services/ghcrateengine/intl_hhg_pack_pricer.go b/pkg/services/ghcrateengine/intl_hhg_pack_pricer.go new file mode 100644 index 00000000000..9318f930e10 --- /dev/null +++ b/pkg/services/ghcrateengine/intl_hhg_pack_pricer.go @@ -0,0 +1,56 @@ +package ghcrateengine + +import ( + "time" + + "github.com/transcom/mymove/pkg/appcontext" + "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/unit" +) + +type intlHHGPackPricer struct { +} + +// NewDomesticPackPricer creates a new pricer for the domestic pack service +func NewIntlHHGPackPricer() services.IntlHHGPackPricer { + return &intlHHGPackPricer{} +} + +// Price determines the price for a domestic pack service +func (p intlHHGPackPricer) Price(appCtx appcontext.AppContext, contractCode string, referenceDate time.Time, weight unit.Pound, servicesScheduleOrigin int, isPPM bool) (unit.Cents, services.PricingDisplayParams, error) { + return priceIntlPackUnpack(appCtx, models.ReServiceCodeIHPK, contractCode, referenceDate, weight, servicesScheduleOrigin, isPPM) +} + +// PriceUsingParams determines the price for a domestic pack service given PaymentServiceItemParams +func (p intlHHGPackPricer) PriceUsingParams(appCtx appcontext.AppContext, params models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error) { + contractCode, err := getParamString(params, models.ServiceItemParamNameContractCode) + if err != nil { + return unit.Cents(0), nil, err + } + + referenceDate, err := getParamTime(params, models.ServiceItemParamNameReferenceDate) + if err != nil { + return unit.Cents(0), nil, err + } + + servicesScheduleOrigin, err := getParamInt(params, models.ServiceItemParamNameServicesScheduleOrigin) + if err != nil { + return unit.Cents(0), nil, err + } + + weightBilled, err := getParamInt(params, models.ServiceItemParamNameWeightBilled) + if err != nil { + return unit.Cents(0), nil, err + } + + var isPPM = false + if params[0].PaymentServiceItem.MTOServiceItem.MTOShipment.ShipmentType == models.MTOShipmentTypePPM { + // PPMs do not require minimums for a shipment's weight + // this flag is passed into the Price function to ensure the weight min + // are not enforced for PPMs + isPPM = true + } + + return p.Price(appCtx, contractCode, referenceDate, unit.Pound(weightBilled), servicesScheduleOrigin, isPPM) +} diff --git a/pkg/services/ghcrateengine/intl_hhg_unpack_pricer.go b/pkg/services/ghcrateengine/intl_hhg_unpack_pricer.go new file mode 100644 index 00000000000..47e428f1e6d --- /dev/null +++ b/pkg/services/ghcrateengine/intl_hhg_unpack_pricer.go @@ -0,0 +1,56 @@ +package ghcrateengine + +import ( + "time" + + "github.com/transcom/mymove/pkg/appcontext" + "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/unit" +) + +type intlHHGUnpackPricer struct { +} + +// NewDomesticUnpackPricer creates a new pricer for the domestic unpack service +func NewIntlHHGUnpackPricer() services.IntlHHGUnpackPricer { + return &intlHHGUnpackPricer{} +} + +// Price determines the price for a domestic unpack service +func (p intlHHGUnpackPricer) Price(appCtx appcontext.AppContext, contractCode string, referenceDate time.Time, weight unit.Pound, servicesScheduleDest int, isPPM bool) (unit.Cents, services.PricingDisplayParams, error) { + return priceDomesticPackUnpack(appCtx, models.ReServiceCodeDUPK, contractCode, referenceDate, weight, servicesScheduleDest, isPPM) +} + +// PriceUsingParams determines the price for a domestic unpack service given PaymentServiceItemParams +func (p intlHHGUnpackPricer) PriceUsingParams(appCtx appcontext.AppContext, params models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error) { + contractCode, err := getParamString(params, models.ServiceItemParamNameContractCode) + if err != nil { + return unit.Cents(0), nil, err + } + + referenceDate, err := getParamTime(params, models.ServiceItemParamNameReferenceDate) + if err != nil { + return unit.Cents(0), nil, err + } + + servicesScheduleDest, err := getParamInt(params, models.ServiceItemParamNameServicesScheduleDest) + if err != nil { + return unit.Cents(0), nil, err + } + + weightBilled, err := getParamInt(params, models.ServiceItemParamNameWeightBilled) + if err != nil { + return unit.Cents(0), nil, err + } + + var isPPM = false + if params[0].PaymentServiceItem.MTOServiceItem.MTOShipment.ShipmentType == models.MTOShipmentTypePPM { + // PPMs do not require minimums for a shipment's weight + // this flag is passed into the Price function to ensure the weight min + // are not enforced for PPMs + isPPM = true + } + + return p.Price(appCtx, contractCode, referenceDate, unit.Pound(weightBilled), servicesScheduleDest, isPPM) +} diff --git a/pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer.go b/pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer.go new file mode 100644 index 00000000000..0acac1eaffa --- /dev/null +++ b/pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer.go @@ -0,0 +1,111 @@ +package ghcrateengine + +import ( + "database/sql" + "fmt" + "math" + "time" + + "github.com/gofrs/uuid" + "github.com/pkg/errors" + + "github.com/transcom/mymove/pkg/appcontext" + "github.com/transcom/mymove/pkg/apperror" + "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/unit" +) + +// FuelSurchargePricer is a service object to price domestic shorthaul +type portFuelSurchargePricer struct { +} + +// NewFuelSurchargePricer is the public constructor for a domesticFuelSurchargePricer using Pop +func NewPortFuelSurchargePricer() services.IntlPortFuelSurchargePricer { + return &portFuelSurchargePricer{} +} + +// Price determines the price for fuel surcharge +func (p portFuelSurchargePricer) Price(_ appcontext.AppContext, actualPickupDate time.Time, distance unit.Miles, weight unit.Pound, fscWeightBasedDistanceMultiplier float64, eiaFuelPrice unit.Millicents, isPPM bool) (unit.Cents, services.PricingDisplayParams, error) { + // Validate parameters + if actualPickupDate.IsZero() { + return 0, nil, errors.New("ActualPickupDate is required") + } + if distance <= 0 { + return 0, nil, errors.New("Distance must be greater than 0") + } + if !isPPM && weight < minIntlWeightHHG { + return 0, nil, fmt.Errorf("weight must be a minimum of %d", minIntlWeightHHG) + } + if fscWeightBasedDistanceMultiplier == 0 { + return 0, nil, errors.New("WeightBasedDistanceMultiplier is required") + } + if eiaFuelPrice == 0 { + return 0, nil, errors.New("EIAFuelPrice is required") + } + + fscPriceDifferenceInCents := (eiaFuelPrice - baseGHCDieselFuelPrice).Float64() / 1000.0 + fscMultiplier := fscWeightBasedDistanceMultiplier * distance.Float64() + fscPrice := fscMultiplier * fscPriceDifferenceInCents * 100 + totalCost := unit.Cents(math.Round(fscPrice)) + + displayParams := services.PricingDisplayParams{ + {Key: models.ServiceItemParamNameFSCPriceDifferenceInCents, Value: FormatFloat(fscPriceDifferenceInCents, 1)}, + {Key: models.ServiceItemParamNameFSCMultiplier, Value: FormatFloat(fscMultiplier, 7)}, + } + + return totalCost, displayParams, nil +} + +func (p portFuelSurchargePricer) PriceUsingParams(appCtx appcontext.AppContext, params models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error) { + actualPickupDate, err := getParamTime(params, models.ServiceItemParamNameActualPickupDate) + if err != nil { + return unit.Cents(0), nil, err + } + + var paymentServiceItem models.PaymentServiceItem + mtoShipment := params[0].PaymentServiceItem.MTOServiceItem.MTOShipment + + if mtoShipment.ID == uuid.Nil { + err = appCtx.DB().Eager("MTOServiceItem", "MTOServiceItem.MTOShipment").Find(&paymentServiceItem, params[0].PaymentServiceItemID) + if err != nil { + switch err { + case sql.ErrNoRows: + return unit.Cents(0), nil, apperror.NewNotFoundError(params[0].PaymentServiceItemID, "looking for PaymentServiceItem") + default: + return unit.Cents(0), nil, apperror.NewQueryError("PaymentServiceItem", err, "") + } + } + mtoShipment = paymentServiceItem.MTOServiceItem.MTOShipment + } + + distance, err := getParamInt(params, models.ServiceItemParamNameDistanceZip) + if err != nil { + return unit.Cents(0), nil, err + } + + weightBilled, err := getParamInt(params, models.ServiceItemParamNameWeightBilled) + if err != nil { + return unit.Cents(0), nil, err + } + + fscWeightBasedDistanceMultiplier, err := getParamFloat(params, models.ServiceItemParamNameFSCWeightBasedDistanceMultiplier) + if err != nil { + return unit.Cents(0), nil, err + } + + eiaFuelPrice, err := getParamInt(params, models.ServiceItemParamNameEIAFuelPrice) + if err != nil { + return unit.Cents(0), nil, err + } + + var isPPM = false + if params[0].PaymentServiceItem.MTOServiceItem.MTOShipment.ShipmentType == models.MTOShipmentTypePPM { + // PPMs do not require minimums for a shipment's weight + // this flag is passed into the Price function to ensure the weight min + // are not enforced for PPMs + isPPM = true + } + + return p.Price(appCtx, actualPickupDate, unit.Miles(distance), unit.Pound(weightBilled), fscWeightBasedDistanceMultiplier, unit.Millicents(eiaFuelPrice), isPPM) +} diff --git a/pkg/services/ghcrateengine/intl_shipping_and_linehaul_pricer.go b/pkg/services/ghcrateengine/intl_shipping_and_linehaul_pricer.go new file mode 100644 index 00000000000..2fec86a9b57 --- /dev/null +++ b/pkg/services/ghcrateengine/intl_shipping_and_linehaul_pricer.go @@ -0,0 +1,137 @@ +package ghcrateengine + +import ( + "fmt" + "math" + "time" + + "github.com/pkg/errors" + + "github.com/transcom/mymove/pkg/appcontext" + "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/unit" +) + +type intlShippingAndLinehaulPricer struct { +} + +// NewDomesticLinehaulPricer creates a new pricer for domestic linehaul services +func NewIntlShippingAndLinehaulPricer() services.DomesticLinehaulPricer { + return &intlShippingAndLinehaulPricer{} +} + +// Price determines the price for a domestic linehaul +func (p intlShippingAndLinehaulPricer) Price(appCtx appcontext.AppContext, contractCode string, referenceDate time.Time, distance unit.Miles, weight unit.Pound, serviceArea string, isPPM bool) (unit.Cents, services.PricingDisplayParams, error) { + // Validate parameters + if len(contractCode) == 0 { + return 0, nil, errors.New("ContractCode is required") + } + if referenceDate.IsZero() { + return 0, nil, errors.New("ReferenceDate is required") + } + if !isPPM && weight < dlhPricerMinimumWeight { + return 0, nil, fmt.Errorf("Weight must be at least %d", dlhPricerMinimumWeight) + } + if len(serviceArea) == 0 { + return 0, nil, errors.New("ServiceArea is required") + } + + isPeakPeriod := IsPeakPeriod(referenceDate) + finalWeight := weight + + if isPPM && weight < dlhPricerMinimumWeight { + finalWeight = dlhPricerMinimumWeight + } + + domesticLinehaulPrice, err := fetchDomesticLinehaulPrice(appCtx, contractCode, isPeakPeriod, distance, finalWeight, serviceArea) + if err != nil { + return unit.Cents(0), nil, fmt.Errorf("could not fetch domestic linehaul rate: %w", err) + } + + basePrice := domesticLinehaulPrice.PriceMillicents.Float64() / 1000 + escalatedPrice, contractYear, err := escalatePriceForContractYear( + appCtx, + domesticLinehaulPrice.ContractID, + referenceDate, + true, + basePrice) + if err != nil { + return 0, nil, fmt.Errorf("could not calculate escalated price: %w", err) + } + + totalPrice := finalWeight.ToCWTFloat64() * distance.Float64() * escalatedPrice + totalPriceCents := unit.Cents(math.Round(totalPrice)) + + params := services.PricingDisplayParams{ + {Key: models.ServiceItemParamNameContractYearName, Value: contractYear.Name}, + {Key: models.ServiceItemParamNameEscalationCompounded, Value: FormatEscalation(contractYear.EscalationCompounded)}, + {Key: models.ServiceItemParamNameIsPeak, Value: FormatBool(isPeakPeriod)}, + {Key: models.ServiceItemParamNamePriceRateOrFactor, Value: FormatFloat(domesticLinehaulPrice.PriceMillicents.ToDollarFloatNoRound(), 3)}, + } + + if isPPM && weight < dlhPricerMinimumWeight { + weightFactor := float64(weight) / float64(dlhPricerMinimumWeight) + cost := float64(weightFactor) * float64(totalPriceCents) + return unit.Cents(cost), params, nil + } + + return totalPriceCents, params, nil +} + +// PriceUsingParams determines the price for a domestic linehaul given PaymentServiceItemParams +func (p intlShippingAndLinehaulPricer) PriceUsingParams(appCtx appcontext.AppContext, params models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error) { + contractCode, err := getParamString(params, models.ServiceItemParamNameContractCode) + if err != nil { + return unit.Cents(0), nil, err + } + + distance, err := getParamInt(params, models.ServiceItemParamNameDistanceZip) + if err != nil { + return unit.Cents(0), nil, err + } + + referenceDate, err := getParamTime(params, models.ServiceItemParamNameReferenceDate) + if err != nil { + return unit.Cents(0), nil, err + } + + serviceAreaOrigin, err := getParamString(params, models.ServiceItemParamNameServiceAreaOrigin) + if err != nil { + return unit.Cents(0), nil, err + } + + weightBilled, err := getParamInt(params, models.ServiceItemParamNameWeightBilled) + if err != nil { + return unit.Cents(0), nil, err + } + + var isPPM = false + if params[0].PaymentServiceItem.MTOServiceItem.MTOShipment.ShipmentType == models.MTOShipmentTypePPM { + // PPMs do not require minimums for a shipment's weight or distance + // this flag is passed into the Price function to ensure the weight and distance mins + // are not enforced for PPMs + isPPM = true + } + + return p.Price(appCtx, contractCode, referenceDate, unit.Miles(distance), unit.Pound(weightBilled), serviceAreaOrigin, isPPM) +} + +// func fetchDomesticLinehaulPrice(appCtx appcontext.AppContext, contractCode string, isPeakPeriod bool, distance unit.Miles, weight unit.Pound, serviceArea string) (models.ReDomesticLinehaulPrice, error) { +// var domesticLinehaulPrice models.ReDomesticLinehaulPrice +// err := appCtx.DB().Q(). +// Join("re_domestic_service_areas sa", "domestic_service_area_id = sa.id"). +// Join("re_contracts c", "re_domestic_linehaul_prices.contract_id = c.id"). +// Where("c.code = $1", contractCode). +// Where("re_domestic_linehaul_prices.is_peak_period = $2", isPeakPeriod). +// Where("$3 between weight_lower and weight_upper", weight). +// Where("$4 between miles_lower and miles_upper", distance). +// Where("sa.service_area = $5", serviceArea). +// First(&domesticLinehaulPrice) + +// if err != nil { +// return models.ReDomesticLinehaulPrice{}, err +// } + +// return domesticLinehaulPrice, nil +// } diff --git a/pkg/services/ghcrateengine/pricer_helpers_intl.go b/pkg/services/ghcrateengine/pricer_helpers_intl.go new file mode 100644 index 00000000000..9f88b2d4ef1 --- /dev/null +++ b/pkg/services/ghcrateengine/pricer_helpers_intl.go @@ -0,0 +1,86 @@ +package ghcrateengine + +import ( + "fmt" + "math" + "time" + + "github.com/pkg/errors" + + "github.com/transcom/mymove/pkg/appcontext" + "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/unit" +) + +func priceIntlPackUnpack(appCtx appcontext.AppContext, packUnpackCode models.ReServiceCode, contractCode string, referenceDate time.Time, weight unit.Pound, servicesSchedule int, isPPM bool) (unit.Cents, services.PricingDisplayParams, error) { + // Validate parameters + var intlOtherPriceCode models.ReServiceCode + switch packUnpackCode { + case models.ReServiceCodeIHPK: + intlOtherPriceCode = models.ReServiceCodeIHPK + case models.ReServiceCodeIHUPK: + intlOtherPriceCode = models.ReServiceCodeIHUPK + default: + return 0, nil, fmt.Errorf("unsupported pack/unpack code of %s", packUnpackCode) + } + if len(contractCode) == 0 { + return 0, nil, errors.New("ContractCode is required") + } + if referenceDate.IsZero() { + return 0, nil, errors.New("ReferenceDate is required") + } + if !isPPM && weight < minDomesticWeight { + return 0, nil, fmt.Errorf("Weight must be a minimum of %d", minDomesticWeight) + } + if servicesSchedule == 0 { + return 0, nil, errors.New("Services schedule is required") + } + + isPeakPeriod := IsPeakPeriod(referenceDate) + + intlOtherPrice, err := fetchIntlOtherPrice(appCtx, contractCode, intlOtherPriceCode, servicesSchedule, isPeakPeriod) + if err != nil { + return 0, nil, fmt.Errorf("could not lookup domestic other price: %w", err) + } + + finalWeight := weight + if isPPM && weight < minDomesticWeight { + finalWeight = minDomesticWeight + } + + basePrice := intlOtherPrice.PerUnitCents.Float64() + escalatedPrice, contractYear, err := escalatePriceForContractYear(appCtx, intlOtherPrice.ContractID, referenceDate, false, basePrice) + if err != nil { + return 0, nil, fmt.Errorf("could not calculate escalated price: %w", err) + } + + escalatedPrice = escalatedPrice * finalWeight.ToCWTFloat64() + + displayParams := services.PricingDisplayParams{ + { + Key: models.ServiceItemParamNameContractYearName, + Value: contractYear.Name, + }, + { + Key: models.ServiceItemParamNamePriceRateOrFactor, + Value: FormatCents(intlOtherPrice.PerUnitCents), + }, + { + Key: models.ServiceItemParamNameIsPeak, + Value: FormatBool(isPeakPeriod), + }, + { + Key: models.ServiceItemParamNameEscalationCompounded, + Value: FormatEscalation(contractYear.EscalationCompounded), + }, + } + + totalCost := unit.Cents(math.Round(escalatedPrice)) + if isPPM && weight < minDomesticWeight { + weightFactor := float64(weight) / float64(minDomesticWeight) + cost := float64(weightFactor) * float64(totalCost) + return unit.Cents(cost), displayParams, nil + } + return totalCost, displayParams, nil +} diff --git a/pkg/services/ghcrateengine/pricer_query_helpers.go b/pkg/services/ghcrateengine/pricer_query_helpers.go index 5fc88dd7d5e..05c7bf668df 100644 --- a/pkg/services/ghcrateengine/pricer_query_helpers.go +++ b/pkg/services/ghcrateengine/pricer_query_helpers.go @@ -45,6 +45,24 @@ func fetchDomOtherPrice(appCtx appcontext.AppContext, contractCode string, servi return domOtherPrice, nil } +func fetchIntlOtherPrice(appCtx appcontext.AppContext, contractCode string, serviceCode models.ReServiceCode, schedule int, isPeakPeriod bool) (models.ReIntlOtherPrice, error) { + var intlOtherPrice models.ReIntlOtherPrice + err := appCtx.DB().Q(). + Join("re_services", "service_id = re_services.id"). + Join("re_contracts", "re_contracts.id = re_intl_other_prices.contract_id"). + Where("re_contracts.code = $1", contractCode). + Where("re_services.code = $2", serviceCode). + Where("schedule = $3", schedule). + Where("is_peak_period = $4", isPeakPeriod). + First(&intlOtherPrice) + + if err != nil { + return models.ReIntlOtherPrice{}, err + } + + return intlOtherPrice, nil +} + func fetchDomServiceAreaPrice(appCtx appcontext.AppContext, contractCode string, serviceCode models.ReServiceCode, serviceArea string, isPeakPeriod bool) (models.ReDomesticServiceAreaPrice, error) { var domServiceAreaPrice models.ReDomesticServiceAreaPrice err := appCtx.DB().Q(). diff --git a/pkg/services/ghcrateengine/service_item_pricer.go b/pkg/services/ghcrateengine/service_item_pricer.go index 81ad0a42cf5..ecaf9b8139b 100644 --- a/pkg/services/ghcrateengine/service_item_pricer.go +++ b/pkg/services/ghcrateengine/service_item_pricer.go @@ -94,6 +94,16 @@ func PricerForServiceItem(serviceCode models.ReServiceCode) (services.ParamsPric return NewDomesticOriginSITPickupPricer(), nil case models.ReServiceCodeDDDSIT: return NewDomesticDestinationSITDeliveryPricer(), nil + case models.ReServiceCodeISLH: + return NewIntlShippingAndLinehaulPricer(), nil + case models.ReServiceCodeIHPK: + return NewIntlHHGPackPricer(), nil + case models.ReServiceCodeIHUPK: + return NewIntlHHGUnpackPricer(), nil + case models.ReServiceCodePOEFSC: + return NewPortFuelSurchargePricer(), nil + case models.ReServiceCodePODFSC: + return NewPortFuelSurchargePricer(), nil default: // TODO: We may want a different error type here after all pricers have been implemented return nil, apperror.NewNotImplementedError(fmt.Sprintf("pricer not found for code %s", serviceCode)) diff --git a/pkg/services/ghcrateengine/shared.go b/pkg/services/ghcrateengine/shared.go index 3f89a29cb40..171d8668bb1 100644 --- a/pkg/services/ghcrateengine/shared.go +++ b/pkg/services/ghcrateengine/shared.go @@ -9,6 +9,9 @@ import ( // minDomesticWeight is the minimum weight used in domestic calculations (weights below this are upgraded to the min) const minDomesticWeight = unit.Pound(500) +// minIntlWeightHHG is the minimum weight used in intl calculations (weights below this are upgraded to the min) +const minIntlWeightHHG = unit.Pound(500) + // dateInYear represents a specific date in a year (without caring what year it is) type dateInYear struct { month time.Month diff --git a/pkg/services/payment_request/payment_request_creator.go b/pkg/services/payment_request/payment_request_creator.go index 0b301193287..63b1f0950a7 100644 --- a/pkg/services/payment_request/payment_request_creator.go +++ b/pkg/services/payment_request/payment_request_creator.go @@ -402,7 +402,6 @@ func (p *paymentRequestCreator) createPaymentServiceItem(appCtx appcontext.AppCo paymentServiceItem.PaymentRequestID = paymentRequest.ID paymentServiceItem.PaymentRequest = *paymentRequest paymentServiceItem.Status = models.PaymentServiceItemStatusRequested - // No pricing at this point, so skipping the PriceCents field. paymentServiceItem.RequestedAt = requestedAt verrs, err := appCtx.DB().ValidateAndCreate(&paymentServiceItem) From 834cd2b2063dc0615501dbda34b215781e66ee41 Mon Sep 17 00:00:00 2001 From: Daniel Jordan Date: Thu, 2 Jan 2025 17:08:33 +0000 Subject: [PATCH 34/54] pack and unpack done, need tests --- ...aram_values_to_service_params_table.up.sql | 20 ++++-- pkg/models/re_rate_area.go | 17 +++++ pkg/models/service_item_param_key.go | 8 +++ .../per_unit_cents_lookup.go | 63 +++++++++++++++++++ .../port_name_lookup.go | 34 ++++++++++ .../service_param_value_lookups.go | 13 ++++ pkg/services/ghc_rate_engine.go | 4 +- .../ghcrateengine/intl_hhg_pack_pricer.go | 19 ++---- .../ghcrateengine/intl_hhg_unpack_pricer.go | 19 ++---- .../ghcrateengine/pricer_helpers_intl.go | 39 +++--------- .../ghcrateengine/pricer_query_helpers.go | 28 +++------ .../CreatePaymentRequestForm.jsx | 4 +- 12 files changed, 180 insertions(+), 88 deletions(-) create mode 100644 pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup.go create mode 100644 pkg/payment_request/service_param_value_lookups/port_name_lookup.go diff --git a/migrations/app/schema/20241226173330_add_intl_param_values_to_service_params_table.up.sql b/migrations/app/schema/20241226173330_add_intl_param_values_to_service_params_table.up.sql index 26ccf6dff90..14dc1422582 100644 --- a/migrations/app/schema/20241226173330_add_intl_param_values_to_service_params_table.up.sql +++ b/migrations/app/schema/20241226173330_add_intl_param_values_to_service_params_table.up.sql @@ -1,3 +1,7 @@ +-- need to add in param keys for international shipments, this will be used to show breakdowns to the TIO +INSERT INTO service_item_param_keys (id, key,description,type,origin,created_at,updated_at) VALUES + ('d9ad3878-4b94-4722-bbaf-d4b8080f339d','PortName','Name of the port for an international shipment','STRING','PRICER','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957'), + ('597bb77e-0ce7-4ba2-9624-24300962625f','PerUnitCents','Per unit cents for a service item','INTEGER','SYSTEM','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957'); -- inserting params for PODFSC INSERT INTO service_params (id,service_id,service_item_param_key_id,created_at,updated_at,is_optional) VALUES @@ -14,7 +18,8 @@ INSERT INTO service_params (id,service_id,service_item_param_key_id,created_at,u ('aa68a318-fe17-445c-ab53-0505fe48d0bb'::uuid,'388115e8-abe9-441d-96cf-a39f24baa0a3','b79978a7-21b7-4656-af83-25585acffb20','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), ('882e7978-9754-4c8e-bb71-8fe4f4059503'::uuid,'388115e8-abe9-441d-96cf-a39f24baa0a3','d87d82da-3ac2-44e8-bce0-cb4de40f9a72','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), ('8fc4571d-235b-4d4f-90e4-77e7ad9250d5'::uuid,'388115e8-abe9-441d-96cf-a39f24baa0a3','1e6257e9-757d-4d59-8846-727dd8a055e7','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), - ('836606ce-894e-4765-bba5-b696cb5fe8cc'::uuid,'388115e8-abe9-441d-96cf-a39f24baa0a3','2cbc2251-eb7d-4c69-a120-9a83785c994b','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false); + ('836606ce-894e-4765-bba5-b696cb5fe8cc'::uuid,'388115e8-abe9-441d-96cf-a39f24baa0a3','2cbc2251-eb7d-4c69-a120-9a83785c994b','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('08701fa6-6352-4808-88b6-1fe103068f29'::uuid,'388115e8-abe9-441d-96cf-a39f24baa0a3','d9ad3878-4b94-4722-bbaf-d4b8080f339d','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false); -- inserting params for POEFSC INSERT INTO service_params (id,service_id,service_item_param_key_id,created_at,updated_at,is_optional) VALUES @@ -31,7 +36,8 @@ INSERT INTO service_params (id,service_id,service_item_param_key_id,created_at,u ('f747f231-66f5-4a52-bb71-8d7b5f618d23'::uuid,'f75758d8-2fcd-40ba-9432-3ff3032a71d1','b79978a7-21b7-4656-af83-25585acffb20','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), ('0177f93a-15f6-41e5-a3ca-dc8f5bb727ab'::uuid,'f75758d8-2fcd-40ba-9432-3ff3032a71d1','d87d82da-3ac2-44e8-bce0-cb4de40f9a72','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), ('cbf5b41f-2d89-4284-858f-d2cda7b060f7'::uuid,'f75758d8-2fcd-40ba-9432-3ff3032a71d1','1e6257e9-757d-4d59-8846-727dd8a055e7','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), - ('ebed3001-93f1-49ba-a935-3d463b0d76fc'::uuid,'f75758d8-2fcd-40ba-9432-3ff3032a71d1','2cbc2251-eb7d-4c69-a120-9a83785c994b','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false); + ('ebed3001-93f1-49ba-a935-3d463b0d76fc'::uuid,'f75758d8-2fcd-40ba-9432-3ff3032a71d1','2cbc2251-eb7d-4c69-a120-9a83785c994b','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('aa7c3492-be44-46dd-983e-478623edc0be'::uuid,'f75758d8-2fcd-40ba-9432-3ff3032a71d1','d9ad3878-4b94-4722-bbaf-d4b8080f339d','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false); -- inserting params for ISLH @@ -94,10 +100,12 @@ INSERT INTO service_params (id,service_id,service_item_param_key_id,created_at,u -- inserting params fo IHPK INSERT INTO service_params (id,service_id,service_item_param_key_id,created_at,updated_at,is_optional) VALUES - ('d9acb388-09a5-464b-bb50-bf418b25e96b'::uuid,'67ba1eaf-6ffd-49de-9a69-497be7789877','a1d31d35-c87d-4a7d-b0b8-8b2646b96e43','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), - ('3c2297b0-1ec7-4261-a41d-37e58999258b'::uuid,'67ba1eaf-6ffd-49de-9a69-497be7789877','cd37b2a6-ac7d-4c93-a148-ca67f7f67cff','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false); + ('d9acb388-09a5-464b-bb50-bf418b25e96b'::uuid,'67ba1eaf-6ffd-49de-9a69-497be7789877','a1d31d35-c87d-4a7d-b0b8-8b2646b96e43','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), -- ContractCode + ('0b31db7a-fbab-4e49-8526-00458ac3900c'::uuid,'67ba1eaf-6ffd-49de-9a69-497be7789877','597bb77e-0ce7-4ba2-9624-24300962625f','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), -- PerUnitCents + ('9b0a74e3-afc4-4f42-8eb3-828f80fbfaf0'::uuid,'67ba1eaf-6ffd-49de-9a69-497be7789877','95ee2e21-b232-4d74-9ec5-218564a8a8b9','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false); -- IsPeak -- inserting params fo IHUPK INSERT INTO service_params (id,service_id,service_item_param_key_id,created_at,updated_at,is_optional) VALUES - ('d9acb388-09a5-464b-bb50-bf418b25e96c'::uuid,'56e91c2d-015d-4243-9657-3ed34867abaa','a1d31d35-c87d-4a7d-b0b8-8b2646b96e43','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), - ('c045524a-90ec-4116-80a1-e2edb5cdf38f'::uuid,'56e91c2d-015d-4243-9657-3ed34867abaa','cd37b2a6-ac7d-4c93-a148-ca67f7f67cff','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false); + ('cb110853-6b1d-452b-9607-345721a70313'::uuid,'56e91c2d-015d-4243-9657-3ed34867abaa','a1d31d35-c87d-4a7d-b0b8-8b2646b96e43','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), -- ContractCode + ('cc95d5df-1167-4fe9-8682-07f8fbe7c286'::uuid,'56e91c2d-015d-4243-9657-3ed34867abaa','597bb77e-0ce7-4ba2-9624-24300962625f','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), -- PerUnitCents + ('759bd482-b2f6-461b-a898-792415efa5f1'::uuid,'56e91c2d-015d-4243-9657-3ed34867abaa','95ee2e21-b232-4d74-9ec5-218564a8a8b9','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false); -- IsPeak diff --git a/pkg/models/re_rate_area.go b/pkg/models/re_rate_area.go index 167b70577ce..a2b2a274761 100644 --- a/pkg/models/re_rate_area.go +++ b/pkg/models/re_rate_area.go @@ -1,6 +1,7 @@ package models import ( + "fmt" "time" "github.com/gobuffalo/pop/v6" @@ -53,3 +54,19 @@ func FetchReRateAreaItem(tx *pop.Connection, contractID uuid.UUID, code string) return &area, err } + +// a db stored proc that takes in an address id & a service code to get the rate area id for an address +func FetchRateAreaID(db *pop.Connection, addressID uuid.UUID, serviceID uuid.UUID) (uuid.UUID, error) { + if addressID != uuid.Nil && serviceID != uuid.Nil { + var rateAreaID uuid.UUID + err := db.RawQuery("SELECT get_rate_area_id($1, $2)", addressID, serviceID). + First(&rateAreaID) + + if err != nil { + return uuid.Nil, fmt.Errorf("error fetching rate area id for shipment ID: %s and service ID %s: %s", addressID, serviceID, err) + } + return rateAreaID, nil + } + // Return error if required parameters are not provided + return uuid.Nil, fmt.Errorf("error fetching rate area ID - required parameters not provided") +} diff --git a/pkg/models/service_item_param_key.go b/pkg/models/service_item_param_key.go index 3bfd789dcc4..868c6785b55 100644 --- a/pkg/models/service_item_param_key.go +++ b/pkg/models/service_item_param_key.go @@ -61,6 +61,10 @@ const ( ServiceItemParamNameNTSPackingFactor ServiceItemParamName = "NTSPackingFactor" // ServiceItemParamNameNumberDaysSIT is the param key name NumberDaysSIT ServiceItemParamNameNumberDaysSIT ServiceItemParamName = "NumberDaysSIT" + // ServiceItemParamNamePerUnitCents is the param key name PerUnitCents + ServiceItemParamNamePerUnitCents ServiceItemParamName = "PerUnitCents" + // ServiceItemParamNamePortName is the param key name PortName + ServiceItemParamNamePortName ServiceItemParamName = "PortName" // ServiceItemParamNamePriceAreaDest is the param key name PriceAreaDest ServiceItemParamNamePriceAreaDest ServiceItemParamName = "PriceAreaDest" // ServiceItemParamNamePriceAreaIntlDest is the param key name PriceAreaIntlDest @@ -275,6 +279,8 @@ var ValidServiceItemParamNames = []ServiceItemParamName{ ServiceItemParamNameStandaloneCrateCap, ServiceItemParamNameUncappedRequestTotal, ServiceItemParamNameLockedPriceCents, + ServiceItemParamNamePerUnitCents, + ServiceItemParamNamePortName, } // ValidServiceItemParamNameStrings lists all valid service item param key names @@ -349,6 +355,8 @@ var ValidServiceItemParamNameStrings = []string{ string(ServiceItemParamNameStandaloneCrateCap), string(ServiceItemParamNameUncappedRequestTotal), string(ServiceItemParamNameLockedPriceCents), + string(ServiceItemParamNamePerUnitCents), + string(ServiceItemParamNamePortName), } // ValidServiceItemParamTypes lists all valid service item param types diff --git a/pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup.go b/pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup.go new file mode 100644 index 00000000000..07670b009fa --- /dev/null +++ b/pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup.go @@ -0,0 +1,63 @@ +package serviceparamvaluelookups + +import ( + "database/sql" + "fmt" + + "github.com/transcom/mymove/pkg/appcontext" + "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services/ghcrateengine" +) + +// PerUnitCents does lookup on the per unit cents value associated with a service item +type PerUnitCentsLookup struct { + ServiceItem models.MTOServiceItem + MTOShipment models.MTOShipment +} + +func (p PerUnitCentsLookup) lookup(appCtx appcontext.AppContext, s *ServiceItemParamKeyData) (string, error) { + var isPeakPeriod bool + serviceID := p.ServiceItem.ReServiceID + contractID := s.ContractID + if p.ServiceItem.ReService.Code == models.ReServiceCodeIHPK { + // IHPK we need the rate area id for the pickup address + rateAreaID, err := models.FetchRateAreaID(appCtx.DB(), *p.MTOShipment.PickupAddressID, p.ServiceItem.ReServiceID) + if err != nil { + return "", fmt.Errorf("error fetching rate area id for shipment ID: %s and service ID %s: %s", p.MTOShipment.ID, p.ServiceItem.ReServiceID, err) + } + isPeakPeriod = ghcrateengine.IsPeakPeriod(*p.MTOShipment.RequestedPickupDate) + var reIntlOtherPrice models.ReIntlOtherPrice + err = appCtx.DB().Q(). + Where("contract_id = ?", contractID). + Where("service_id = ?", serviceID). + Where("is_peak_period = ?", isPeakPeriod). + Where("rate_area_id = ?", rateAreaID). + First(&reIntlOtherPrice) + if err != nil { + return "", fmt.Errorf("error fetching IHPK per unit cents for contractID: %s, serviceID %s, isPeakPeriod: %t, and rateAreaID: %s: %s", contractID, serviceID, isPeakPeriod, rateAreaID, err) + } + return reIntlOtherPrice.PerUnitCents.ToMillicents().ToCents().String(), nil + } + + if p.ServiceItem.ReService.Code == models.ReServiceCodeIHUPK { + // IHUPK we need the rate area id for the destination address + rateAreaID, err := models.FetchRateAreaID(appCtx.DB(), *p.MTOShipment.PickupAddressID, p.ServiceItem.ReServiceID) + if err != nil && err != sql.ErrNoRows { + return "", fmt.Errorf("error fetching rate area id for shipment ID: %s and service ID %s: %s", p.MTOShipment.ID, p.ServiceItem.ReServiceID, err) + } + isPeakPeriod = ghcrateengine.IsPeakPeriod(*p.MTOShipment.RequestedPickupDate) + var reIntlOtherPrice models.ReIntlOtherPrice + err = appCtx.DB().Q(). + Where("contract_id = ?", contractID). + Where("service_id = ?", serviceID). + Where("is_peak_period = ?", isPeakPeriod). + Where("rate_area_id = ?", rateAreaID). + First(&reIntlOtherPrice) + if err != nil { + return "", fmt.Errorf("error fetching IHUPK per unit cents for contractID: %s, serviceID %s, isPeakPeriod: %t, and rateAreaID: %s: %s", contractID, serviceID, isPeakPeriod, rateAreaID, err) + } + return reIntlOtherPrice.PerUnitCents.ToMillicents().ToCents().String(), nil + } else { + return "", fmt.Errorf("unsupported service code to retrieve service item param PerUnitCents") + } +} diff --git a/pkg/payment_request/service_param_value_lookups/port_name_lookup.go b/pkg/payment_request/service_param_value_lookups/port_name_lookup.go new file mode 100644 index 00000000000..5013d3ae2c8 --- /dev/null +++ b/pkg/payment_request/service_param_value_lookups/port_name_lookup.go @@ -0,0 +1,34 @@ +package serviceparamvaluelookups + +import ( + "fmt" + + "github.com/gofrs/uuid" + + "github.com/transcom/mymove/pkg/appcontext" + "github.com/transcom/mymove/pkg/models" +) + +// PortNameLookup does lookup on the shipment and finds the port name +type PortNameLookup struct { + ServiceItem models.MTOServiceItem +} + +func (p PortNameLookup) lookup(appCtx appcontext.AppContext, _ *ServiceItemParamKeyData) (string, error) { + var portLocationID *uuid.UUID + if p.ServiceItem.PODLocationID != nil { + portLocationID = p.ServiceItem.PODLocationID + } else if p.ServiceItem.POELocationID != nil { + portLocationID = p.ServiceItem.POELocationID + } else { + return "", nil + } + var portLocation models.PortLocation + err := appCtx.DB().Q(). + EagerPreload("Port"). + Where("id = $1", portLocationID).First(&portLocation) + if err != nil { + return "", fmt.Errorf("unable to find port location with id %s", portLocationID) + } + return portLocation.Port.PortName, nil +} diff --git a/pkg/payment_request/service_param_value_lookups/service_param_value_lookups.go b/pkg/payment_request/service_param_value_lookups/service_param_value_lookups.go index 0bb499be70b..c3669e5cb41 100644 --- a/pkg/payment_request/service_param_value_lookups/service_param_value_lookups.go +++ b/pkg/payment_request/service_param_value_lookups/service_param_value_lookups.go @@ -21,6 +21,7 @@ type ServiceItemParamKeyData struct { MTOServiceItem models.MTOServiceItem PaymentRequestID uuid.UUID MoveTaskOrderID uuid.UUID + ContractID uuid.UUID ContractCode string mtoShipmentID *uuid.UUID paramCache *ServiceParamsCache @@ -85,6 +86,8 @@ var ServiceItemParamsWithLookups = []models.ServiceItemParamName{ models.ServiceItemParamNameStandaloneCrate, models.ServiceItemParamNameStandaloneCrateCap, models.ServiceItemParamNameLockedPriceCents, + models.ServiceItemParamNamePerUnitCents, + models.ServiceItemParamNamePortName, } // ServiceParamLookupInitialize initializes service parameter lookup @@ -120,6 +123,7 @@ func ServiceParamLookupInitialize( to this query. Otherwise the contract_code field could be added to the MTO. */ ContractCode: contract.Code, + ContractID: contract.ID, } // @@ -430,6 +434,15 @@ func InitializeLookups(appCtx appcontext.AppContext, shipment models.MTOShipment ServiceItem: serviceItem, } + lookups[models.ServiceItemParamNamePerUnitCents] = PerUnitCentsLookup{ + ServiceItem: serviceItem, + MTOShipment: shipment, + } + + lookups[models.ServiceItemParamNamePortName] = PortNameLookup{ + ServiceItem: serviceItem, + } + return lookups } diff --git a/pkg/services/ghc_rate_engine.go b/pkg/services/ghc_rate_engine.go index 9bee5c8f59f..440d710f4b2 100644 --- a/pkg/services/ghc_rate_engine.go +++ b/pkg/services/ghc_rate_engine.go @@ -245,7 +245,7 @@ type IntlShippingAndLinehaulPricer interface { // //go:generate mockery --name IntlHHGPackPricer type IntlHHGPackPricer interface { - Price(appCtx appcontext.AppContext, contractCode string, requestedPickupDate time.Time, weight unit.Pound, servicesScheduleOrigin int, isPPM bool) (unit.Cents, PricingDisplayParams, error) + Price(appCtx appcontext.AppContext, contractCode string, requestedPickupDate time.Time, weight unit.Pound, perUnitCents int) (unit.Cents, PricingDisplayParams, error) ParamsPricer } @@ -253,7 +253,7 @@ type IntlHHGPackPricer interface { // //go:generate mockery --name IntlHHGUnpackPricer type IntlHHGUnpackPricer interface { - Price(appCtx appcontext.AppContext, contractCode string, requestedPickupDate time.Time, weight unit.Pound, servicesScheduleDest int, isPPM bool) (unit.Cents, PricingDisplayParams, error) + Price(appCtx appcontext.AppContext, contractCode string, requestedPickupDate time.Time, weight unit.Pound, perUnitCents int) (unit.Cents, PricingDisplayParams, error) ParamsPricer } diff --git a/pkg/services/ghcrateengine/intl_hhg_pack_pricer.go b/pkg/services/ghcrateengine/intl_hhg_pack_pricer.go index 9318f930e10..12090aa3bde 100644 --- a/pkg/services/ghcrateengine/intl_hhg_pack_pricer.go +++ b/pkg/services/ghcrateengine/intl_hhg_pack_pricer.go @@ -12,17 +12,14 @@ import ( type intlHHGPackPricer struct { } -// NewDomesticPackPricer creates a new pricer for the domestic pack service func NewIntlHHGPackPricer() services.IntlHHGPackPricer { return &intlHHGPackPricer{} } -// Price determines the price for a domestic pack service -func (p intlHHGPackPricer) Price(appCtx appcontext.AppContext, contractCode string, referenceDate time.Time, weight unit.Pound, servicesScheduleOrigin int, isPPM bool) (unit.Cents, services.PricingDisplayParams, error) { - return priceIntlPackUnpack(appCtx, models.ReServiceCodeIHPK, contractCode, referenceDate, weight, servicesScheduleOrigin, isPPM) +func (p intlHHGPackPricer) Price(appCtx appcontext.AppContext, contractCode string, referenceDate time.Time, weight unit.Pound, perUnitCents int) (unit.Cents, services.PricingDisplayParams, error) { + return priceIntlPackUnpack(appCtx, models.ReServiceCodeIHPK, contractCode, referenceDate, weight, perUnitCents) } -// PriceUsingParams determines the price for a domestic pack service given PaymentServiceItemParams func (p intlHHGPackPricer) PriceUsingParams(appCtx appcontext.AppContext, params models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error) { contractCode, err := getParamString(params, models.ServiceItemParamNameContractCode) if err != nil { @@ -34,7 +31,7 @@ func (p intlHHGPackPricer) PriceUsingParams(appCtx appcontext.AppContext, params return unit.Cents(0), nil, err } - servicesScheduleOrigin, err := getParamInt(params, models.ServiceItemParamNameServicesScheduleOrigin) + perUnitCents, err := getParamInt(params, models.ServiceItemParamNamePerUnitCents) if err != nil { return unit.Cents(0), nil, err } @@ -44,13 +41,5 @@ func (p intlHHGPackPricer) PriceUsingParams(appCtx appcontext.AppContext, params return unit.Cents(0), nil, err } - var isPPM = false - if params[0].PaymentServiceItem.MTOServiceItem.MTOShipment.ShipmentType == models.MTOShipmentTypePPM { - // PPMs do not require minimums for a shipment's weight - // this flag is passed into the Price function to ensure the weight min - // are not enforced for PPMs - isPPM = true - } - - return p.Price(appCtx, contractCode, referenceDate, unit.Pound(weightBilled), servicesScheduleOrigin, isPPM) + return p.Price(appCtx, contractCode, referenceDate, unit.Pound(weightBilled), perUnitCents) } diff --git a/pkg/services/ghcrateengine/intl_hhg_unpack_pricer.go b/pkg/services/ghcrateengine/intl_hhg_unpack_pricer.go index 47e428f1e6d..d4cb95dd315 100644 --- a/pkg/services/ghcrateengine/intl_hhg_unpack_pricer.go +++ b/pkg/services/ghcrateengine/intl_hhg_unpack_pricer.go @@ -12,17 +12,14 @@ import ( type intlHHGUnpackPricer struct { } -// NewDomesticUnpackPricer creates a new pricer for the domestic unpack service func NewIntlHHGUnpackPricer() services.IntlHHGUnpackPricer { return &intlHHGUnpackPricer{} } -// Price determines the price for a domestic unpack service -func (p intlHHGUnpackPricer) Price(appCtx appcontext.AppContext, contractCode string, referenceDate time.Time, weight unit.Pound, servicesScheduleDest int, isPPM bool) (unit.Cents, services.PricingDisplayParams, error) { - return priceDomesticPackUnpack(appCtx, models.ReServiceCodeDUPK, contractCode, referenceDate, weight, servicesScheduleDest, isPPM) +func (p intlHHGUnpackPricer) Price(appCtx appcontext.AppContext, contractCode string, referenceDate time.Time, weight unit.Pound, perUnitCents int) (unit.Cents, services.PricingDisplayParams, error) { + return priceIntlPackUnpack(appCtx, models.ReServiceCodeIHUPK, contractCode, referenceDate, weight, perUnitCents) } -// PriceUsingParams determines the price for a domestic unpack service given PaymentServiceItemParams func (p intlHHGUnpackPricer) PriceUsingParams(appCtx appcontext.AppContext, params models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error) { contractCode, err := getParamString(params, models.ServiceItemParamNameContractCode) if err != nil { @@ -34,7 +31,7 @@ func (p intlHHGUnpackPricer) PriceUsingParams(appCtx appcontext.AppContext, para return unit.Cents(0), nil, err } - servicesScheduleDest, err := getParamInt(params, models.ServiceItemParamNameServicesScheduleDest) + perUnitCents, err := getParamInt(params, models.ServiceItemParamNamePerUnitCents) if err != nil { return unit.Cents(0), nil, err } @@ -44,13 +41,5 @@ func (p intlHHGUnpackPricer) PriceUsingParams(appCtx appcontext.AppContext, para return unit.Cents(0), nil, err } - var isPPM = false - if params[0].PaymentServiceItem.MTOServiceItem.MTOShipment.ShipmentType == models.MTOShipmentTypePPM { - // PPMs do not require minimums for a shipment's weight - // this flag is passed into the Price function to ensure the weight min - // are not enforced for PPMs - isPPM = true - } - - return p.Price(appCtx, contractCode, referenceDate, unit.Pound(weightBilled), servicesScheduleDest, isPPM) + return p.Price(appCtx, contractCode, referenceDate, unit.Pound(weightBilled), perUnitCents) } diff --git a/pkg/services/ghcrateengine/pricer_helpers_intl.go b/pkg/services/ghcrateengine/pricer_helpers_intl.go index 9f88b2d4ef1..73428847e51 100644 --- a/pkg/services/ghcrateengine/pricer_helpers_intl.go +++ b/pkg/services/ghcrateengine/pricer_helpers_intl.go @@ -13,15 +13,8 @@ import ( "github.com/transcom/mymove/pkg/unit" ) -func priceIntlPackUnpack(appCtx appcontext.AppContext, packUnpackCode models.ReServiceCode, contractCode string, referenceDate time.Time, weight unit.Pound, servicesSchedule int, isPPM bool) (unit.Cents, services.PricingDisplayParams, error) { - // Validate parameters - var intlOtherPriceCode models.ReServiceCode - switch packUnpackCode { - case models.ReServiceCodeIHPK: - intlOtherPriceCode = models.ReServiceCodeIHPK - case models.ReServiceCodeIHUPK: - intlOtherPriceCode = models.ReServiceCodeIHUPK - default: +func priceIntlPackUnpack(appCtx appcontext.AppContext, packUnpackCode models.ReServiceCode, contractCode string, referenceDate time.Time, weight unit.Pound, perUnitCents int) (unit.Cents, services.PricingDisplayParams, error) { + if packUnpackCode != models.ReServiceCodeIHPK && packUnpackCode != models.ReServiceCodeIHUPK { return 0, nil, fmt.Errorf("unsupported pack/unpack code of %s", packUnpackCode) } if len(contractCode) == 0 { @@ -30,32 +23,21 @@ func priceIntlPackUnpack(appCtx appcontext.AppContext, packUnpackCode models.ReS if referenceDate.IsZero() { return 0, nil, errors.New("ReferenceDate is required") } - if !isPPM && weight < minDomesticWeight { - return 0, nil, fmt.Errorf("Weight must be a minimum of %d", minDomesticWeight) - } - if servicesSchedule == 0 { - return 0, nil, errors.New("Services schedule is required") - } isPeakPeriod := IsPeakPeriod(referenceDate) - intlOtherPrice, err := fetchIntlOtherPrice(appCtx, contractCode, intlOtherPriceCode, servicesSchedule, isPeakPeriod) + contract, err := fetchContractsByContractCode(appCtx, contractCode) if err != nil { - return 0, nil, fmt.Errorf("could not lookup domestic other price: %w", err) + return 0, nil, fmt.Errorf("could not find contract with code: %s: %w", contractCode, err) } - finalWeight := weight - if isPPM && weight < minDomesticWeight { - finalWeight = minDomesticWeight - } - - basePrice := intlOtherPrice.PerUnitCents.Float64() - escalatedPrice, contractYear, err := escalatePriceForContractYear(appCtx, intlOtherPrice.ContractID, referenceDate, false, basePrice) + basePrice := float64(perUnitCents) + escalatedPrice, contractYear, err := escalatePriceForContractYear(appCtx, contract.ID, referenceDate, false, basePrice) if err != nil { return 0, nil, fmt.Errorf("could not calculate escalated price: %w", err) } - escalatedPrice = escalatedPrice * finalWeight.ToCWTFloat64() + escalatedPrice = escalatedPrice * weight.ToCWTFloat64() displayParams := services.PricingDisplayParams{ { @@ -64,7 +46,7 @@ func priceIntlPackUnpack(appCtx appcontext.AppContext, packUnpackCode models.ReS }, { Key: models.ServiceItemParamNamePriceRateOrFactor, - Value: FormatCents(intlOtherPrice.PerUnitCents), + Value: FormatCents(unit.Cents(perUnitCents)), }, { Key: models.ServiceItemParamNameIsPeak, @@ -77,10 +59,5 @@ func priceIntlPackUnpack(appCtx appcontext.AppContext, packUnpackCode models.ReS } totalCost := unit.Cents(math.Round(escalatedPrice)) - if isPPM && weight < minDomesticWeight { - weightFactor := float64(weight) / float64(minDomesticWeight) - cost := float64(weightFactor) * float64(totalCost) - return unit.Cents(cost), displayParams, nil - } return totalCost, displayParams, nil } diff --git a/pkg/services/ghcrateengine/pricer_query_helpers.go b/pkg/services/ghcrateengine/pricer_query_helpers.go index 05c7bf668df..51acb06f9bd 100644 --- a/pkg/services/ghcrateengine/pricer_query_helpers.go +++ b/pkg/services/ghcrateengine/pricer_query_helpers.go @@ -45,24 +45,6 @@ func fetchDomOtherPrice(appCtx appcontext.AppContext, contractCode string, servi return domOtherPrice, nil } -func fetchIntlOtherPrice(appCtx appcontext.AppContext, contractCode string, serviceCode models.ReServiceCode, schedule int, isPeakPeriod bool) (models.ReIntlOtherPrice, error) { - var intlOtherPrice models.ReIntlOtherPrice - err := appCtx.DB().Q(). - Join("re_services", "service_id = re_services.id"). - Join("re_contracts", "re_contracts.id = re_intl_other_prices.contract_id"). - Where("re_contracts.code = $1", contractCode). - Where("re_services.code = $2", serviceCode). - Where("schedule = $3", schedule). - Where("is_peak_period = $4", isPeakPeriod). - First(&intlOtherPrice) - - if err != nil { - return models.ReIntlOtherPrice{}, err - } - - return intlOtherPrice, nil -} - func fetchDomServiceAreaPrice(appCtx appcontext.AppContext, contractCode string, serviceCode models.ReServiceCode, serviceArea string, isPeakPeriod bool) (models.ReDomesticServiceAreaPrice, error) { var domServiceAreaPrice models.ReDomesticServiceAreaPrice err := appCtx.DB().Q(). @@ -121,6 +103,16 @@ func fetchContractsByContractId(appCtx appcontext.AppContext, contractID uuid.UU return contracts, nil } +func fetchContractsByContractCode(appCtx appcontext.AppContext, contractCode string) (models.ReContract, error) { + var contract models.ReContract + err := appCtx.DB().Where("code = $1", contractCode).First(&contract) + if err != nil { + return models.ReContract{}, err + } + + return contract, nil +} + func fetchShipmentTypePrice(appCtx appcontext.AppContext, contractCode string, serviceCode models.ReServiceCode, market models.Market) (models.ReShipmentTypePrice, error) { var shipmentTypePrice models.ReShipmentTypePrice err := appCtx.DB().Q(). diff --git a/src/components/PrimeUI/CreatePaymentRequestForm/CreatePaymentRequestForm.jsx b/src/components/PrimeUI/CreatePaymentRequestForm/CreatePaymentRequestForm.jsx index 3a3edafb556..5aa3fdc0187 100644 --- a/src/components/PrimeUI/CreatePaymentRequestForm/CreatePaymentRequestForm.jsx +++ b/src/components/PrimeUI/CreatePaymentRequestForm/CreatePaymentRequestForm.jsx @@ -143,7 +143,9 @@ const CreatePaymentRequestForm = ({ mtoServiceItem.reServiceCode === 'DPK' || mtoServiceItem.reServiceCode === 'DDSFSC' || mtoServiceItem.reServiceCode === 'DOSFSC' || - mtoServiceItem.reServiceCode === 'DDSHUT') && ( + mtoServiceItem.reServiceCode === 'DDSHUT' || + mtoServiceItem.reServiceCode === 'IHPK' || + mtoServiceItem.reServiceCode === 'IHUPK') && ( Date: Thu, 2 Jan 2025 17:53:50 +0000 Subject: [PATCH 35/54] POEFSC and PODFSC should be good, onto linehaul, still need tests --- ...aram_values_to_service_params_table.up.sql | 2 +- pkg/models/re_rate_area.go | 1 - pkg/services/ghc_rate_engine.go | 2 +- .../intl_port_fuel_surcharge_pricer.go | 21 ++++++++----------- pkg/services/ghcrateengine/pricer_helpers.go | 4 ++-- .../ghcrateengine/service_item_pricer.go | 1 - 6 files changed, 13 insertions(+), 18 deletions(-) diff --git a/migrations/app/schema/20241226173330_add_intl_param_values_to_service_params_table.up.sql b/migrations/app/schema/20241226173330_add_intl_param_values_to_service_params_table.up.sql index 14dc1422582..eec414779d0 100644 --- a/migrations/app/schema/20241226173330_add_intl_param_values_to_service_params_table.up.sql +++ b/migrations/app/schema/20241226173330_add_intl_param_values_to_service_params_table.up.sql @@ -1,6 +1,6 @@ -- need to add in param keys for international shipments, this will be used to show breakdowns to the TIO INSERT INTO service_item_param_keys (id, key,description,type,origin,created_at,updated_at) VALUES - ('d9ad3878-4b94-4722-bbaf-d4b8080f339d','PortName','Name of the port for an international shipment','STRING','PRICER','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957'), + ('d9ad3878-4b94-4722-bbaf-d4b8080f339d','PortName','Name of the port for an international shipment','STRING','SYSTEM','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957'), ('597bb77e-0ce7-4ba2-9624-24300962625f','PerUnitCents','Per unit cents for a service item','INTEGER','SYSTEM','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957'); -- inserting params for PODFSC diff --git a/pkg/models/re_rate_area.go b/pkg/models/re_rate_area.go index a2b2a274761..960f4258d8c 100644 --- a/pkg/models/re_rate_area.go +++ b/pkg/models/re_rate_area.go @@ -67,6 +67,5 @@ func FetchRateAreaID(db *pop.Connection, addressID uuid.UUID, serviceID uuid.UUI } return rateAreaID, nil } - // Return error if required parameters are not provided return uuid.Nil, fmt.Errorf("error fetching rate area ID - required parameters not provided") } diff --git a/pkg/services/ghc_rate_engine.go b/pkg/services/ghc_rate_engine.go index 440d710f4b2..4a74692a5bb 100644 --- a/pkg/services/ghc_rate_engine.go +++ b/pkg/services/ghc_rate_engine.go @@ -261,6 +261,6 @@ type IntlHHGUnpackPricer interface { // //go:generate mockery --name IntlPortFuelSurchargePricer type IntlPortFuelSurchargePricer interface { - Price(appCtx appcontext.AppContext, actualPickupDate time.Time, distance unit.Miles, weight unit.Pound, fscWeightBasedDistanceMultiplier float64, eiaFuelPrice unit.Millicents, isPPM bool) (unit.Cents, PricingDisplayParams, error) + Price(appCtx appcontext.AppContext, actualPickupDate time.Time, distance unit.Miles, weight unit.Pound, fscWeightBasedDistanceMultiplier float64, eiaFuelPrice unit.Millicents, portName string) (unit.Cents, PricingDisplayParams, error) ParamsPricer } diff --git a/pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer.go b/pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer.go index 0acac1eaffa..7d2c94e7096 100644 --- a/pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer.go +++ b/pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer.go @@ -16,17 +16,14 @@ import ( "github.com/transcom/mymove/pkg/unit" ) -// FuelSurchargePricer is a service object to price domestic shorthaul type portFuelSurchargePricer struct { } -// NewFuelSurchargePricer is the public constructor for a domesticFuelSurchargePricer using Pop func NewPortFuelSurchargePricer() services.IntlPortFuelSurchargePricer { return &portFuelSurchargePricer{} } -// Price determines the price for fuel surcharge -func (p portFuelSurchargePricer) Price(_ appcontext.AppContext, actualPickupDate time.Time, distance unit.Miles, weight unit.Pound, fscWeightBasedDistanceMultiplier float64, eiaFuelPrice unit.Millicents, isPPM bool) (unit.Cents, services.PricingDisplayParams, error) { +func (p portFuelSurchargePricer) Price(_ appcontext.AppContext, actualPickupDate time.Time, distance unit.Miles, weight unit.Pound, fscWeightBasedDistanceMultiplier float64, eiaFuelPrice unit.Millicents, portName string) (unit.Cents, services.PricingDisplayParams, error) { // Validate parameters if actualPickupDate.IsZero() { return 0, nil, errors.New("ActualPickupDate is required") @@ -34,7 +31,7 @@ func (p portFuelSurchargePricer) Price(_ appcontext.AppContext, actualPickupDate if distance <= 0 { return 0, nil, errors.New("Distance must be greater than 0") } - if !isPPM && weight < minIntlWeightHHG { + if weight < minIntlWeightHHG { return 0, nil, fmt.Errorf("weight must be a minimum of %d", minIntlWeightHHG) } if fscWeightBasedDistanceMultiplier == 0 { @@ -43,6 +40,9 @@ func (p portFuelSurchargePricer) Price(_ appcontext.AppContext, actualPickupDate if eiaFuelPrice == 0 { return 0, nil, errors.New("EIAFuelPrice is required") } + if portName == "" { + return 0, nil, errors.New("PortName is required") + } fscPriceDifferenceInCents := (eiaFuelPrice - baseGHCDieselFuelPrice).Float64() / 1000.0 fscMultiplier := fscWeightBasedDistanceMultiplier * distance.Float64() @@ -99,13 +99,10 @@ func (p portFuelSurchargePricer) PriceUsingParams(appCtx appcontext.AppContext, return unit.Cents(0), nil, err } - var isPPM = false - if params[0].PaymentServiceItem.MTOServiceItem.MTOShipment.ShipmentType == models.MTOShipmentTypePPM { - // PPMs do not require minimums for a shipment's weight - // this flag is passed into the Price function to ensure the weight min - // are not enforced for PPMs - isPPM = true + portName, err := getParamString(params, models.ServiceItemParamNamePortName) + if err != nil { + return unit.Cents(0), nil, err } - return p.Price(appCtx, actualPickupDate, unit.Miles(distance), unit.Pound(weightBilled), fscWeightBasedDistanceMultiplier, unit.Millicents(eiaFuelPrice), isPPM) + return p.Price(appCtx, actualPickupDate, unit.Miles(distance), unit.Pound(weightBilled), fscWeightBasedDistanceMultiplier, unit.Millicents(eiaFuelPrice), portName) } diff --git a/pkg/services/ghcrateengine/pricer_helpers.go b/pkg/services/ghcrateengine/pricer_helpers.go index faa802afb0b..cb804b0da49 100644 --- a/pkg/services/ghcrateengine/pricer_helpers.go +++ b/pkg/services/ghcrateengine/pricer_helpers.go @@ -448,10 +448,10 @@ func createPricerGeneratedParams(appCtx appcontext.AppContext, paymentServiceIte Where("key = ?", param.Key). First(&serviceItemParamKey) if err != nil { - return paymentServiceItemParams, fmt.Errorf("Unable to find service item param key for %v", param.Key) + return paymentServiceItemParams, fmt.Errorf("unable to find service item param key for %v", param.Key) } if serviceItemParamKey.Origin != models.ServiceItemParamOriginPricer { - return paymentServiceItemParams, fmt.Errorf("Service item param key is not a pricer param. Param key: %v", serviceItemParamKey.Key) + return paymentServiceItemParams, fmt.Errorf("service item param key is not a pricer param. Param key: %v", serviceItemParamKey.Key) } // Create the PaymentServiceItemParam from the PricingDisplayParam and store it in the DB diff --git a/pkg/services/ghcrateengine/service_item_pricer.go b/pkg/services/ghcrateengine/service_item_pricer.go index ecaf9b8139b..a673f832b63 100644 --- a/pkg/services/ghcrateengine/service_item_pricer.go +++ b/pkg/services/ghcrateengine/service_item_pricer.go @@ -36,7 +36,6 @@ func (p serviceItemPricer) PriceServiceItem(appCtx appcontext.AppContext, item m // createPricerGeneratedParams will throw an error if pricingParams is an empty slice // currently our pricers are returning empty slices for pricingParams // once all pricers have been updated to return pricingParams - // TODO: this conditional logic should be removed var displayParams models.PaymentServiceItemParams if len(pricingParams) > 0 { displayParams, err = createPricerGeneratedParams(appCtx, item.ID, pricingParams) From 1a0073e2fd9d3c9fe45a035fb45b2cc4c4ff3d5e Mon Sep 17 00:00:00 2001 From: Daniel Jordan Date: Thu, 2 Jan 2025 18:56:51 +0000 Subject: [PATCH 36/54] linehaul done, now it is time for tests and refinement please save me baby jesus in a tuxedo --- ...aram_values_to_service_params_table.up.sql | 3 +- .../per_unit_cents_lookup.go | 24 ++++- pkg/services/ghc_rate_engine.go | 2 +- .../intl_shipping_and_linehaul_pricer.go | 97 +++++++------------ .../ghcrateengine/pricer_helpers_intl.go | 2 +- 5 files changed, 61 insertions(+), 67 deletions(-) diff --git a/migrations/app/schema/20241226173330_add_intl_param_values_to_service_params_table.up.sql b/migrations/app/schema/20241226173330_add_intl_param_values_to_service_params_table.up.sql index eec414779d0..63c1e404e50 100644 --- a/migrations/app/schema/20241226173330_add_intl_param_values_to_service_params_table.up.sql +++ b/migrations/app/schema/20241226173330_add_intl_param_values_to_service_params_table.up.sql @@ -61,7 +61,8 @@ INSERT INTO service_params (id,service_id,service_item_param_key_id,created_at,u ('c08f7ab1-6c3c-4627-b22f-1e987ba6f4f2'::uuid,'9f3d551a-0725-430e-897e-80ee9add3ae9','1e6257e9-757d-4d59-8846-727dd8a055e7','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), ('4d9ed9b0-957d-4e6a-a3d4-5e2e2784ef62'::uuid,'9f3d551a-0725-430e-897e-80ee9add3ae9','14a93209-370d-42f3-8ca2-479c953be839','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',true), ('6acb30b9-65a0-4902-85ed-1acb6f4ac930'::uuid,'9f3d551a-0725-430e-897e-80ee9add3ae9','5335e243-ab5b-4906-b84f-bd8c35ba64b3','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), - ('fd83c2ba-0c59-4598-81d6-b56cc8d9979d'::uuid,'9f3d551a-0725-430e-897e-80ee9add3ae9','2cbc2251-eb7d-4c69-a120-9a83785c994b','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false); + ('fd83c2ba-0c59-4598-81d6-b56cc8d9979d'::uuid,'9f3d551a-0725-430e-897e-80ee9add3ae9','2cbc2251-eb7d-4c69-a120-9a83785c994b','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false), + ('b370c895-e356-4d2c-a200-c2c67ac51011'::uuid,'9f3d551a-0725-430e-897e-80ee9add3ae9','597bb77e-0ce7-4ba2-9624-24300962625f','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957',false); -- inserting params fo IOSFSC INSERT INTO service_params (id,service_id,service_item_param_key_id,created_at,updated_at,is_optional) VALUES diff --git a/pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup.go b/pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup.go index 07670b009fa..a25913f8e2c 100644 --- a/pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup.go +++ b/pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup.go @@ -38,7 +38,6 @@ func (p PerUnitCentsLookup) lookup(appCtx appcontext.AppContext, s *ServiceItemP } return reIntlOtherPrice.PerUnitCents.ToMillicents().ToCents().String(), nil } - if p.ServiceItem.ReService.Code == models.ReServiceCodeIHUPK { // IHUPK we need the rate area id for the destination address rateAreaID, err := models.FetchRateAreaID(appCtx.DB(), *p.MTOShipment.PickupAddressID, p.ServiceItem.ReServiceID) @@ -57,6 +56,29 @@ func (p PerUnitCentsLookup) lookup(appCtx appcontext.AppContext, s *ServiceItemP return "", fmt.Errorf("error fetching IHUPK per unit cents for contractID: %s, serviceID %s, isPeakPeriod: %t, and rateAreaID: %s: %s", contractID, serviceID, isPeakPeriod, rateAreaID, err) } return reIntlOtherPrice.PerUnitCents.ToMillicents().ToCents().String(), nil + } else if p.ServiceItem.ReService.Code == models.ReServiceCodeISLH { + // IHUPK we need the rate area id for the destination address + originRateAreaID, err := models.FetchRateAreaID(appCtx.DB(), *p.MTOShipment.PickupAddressID, p.ServiceItem.ReServiceID) + if err != nil && err != sql.ErrNoRows { + return "", fmt.Errorf("error fetching rate area id for origina address for shipment ID: %s and service ID %s: %s", p.MTOShipment.ID, p.ServiceItem.ReServiceID, err) + } + destRateAreaID, err := models.FetchRateAreaID(appCtx.DB(), *p.MTOShipment.DestinationAddressID, p.ServiceItem.ReServiceID) + if err != nil && err != sql.ErrNoRows { + return "", fmt.Errorf("error fetching rate area id for destination address for shipment ID: %s and service ID %s: %s", p.MTOShipment.ID, p.ServiceItem.ReServiceID, err) + } + isPeakPeriod = ghcrateengine.IsPeakPeriod(*p.MTOShipment.RequestedPickupDate) + var reIntlPrice models.ReIntlPrice + err = appCtx.DB().Q(). + Where("contract_id = ?", contractID). + Where("service_id = ?", serviceID). + Where("is_peak_period = ?", isPeakPeriod). + Where("origin_rate_area_id = ?", originRateAreaID). + Where("destination_rate_area_id = ?", destRateAreaID). + First(&reIntlPrice) + if err != nil { + return "", fmt.Errorf("error fetching ISLH per unit cents for contractID: %s, serviceID %s, isPeakPeriod: %t, originRateAreaID: %s, and destRateAreaid: %s: %s", contractID, serviceID, isPeakPeriod, originRateAreaID, destRateAreaID, err) + } + return reIntlPrice.PerUnitCents.ToMillicents().ToCents().String(), nil } else { return "", fmt.Errorf("unsupported service code to retrieve service item param PerUnitCents") } diff --git a/pkg/services/ghc_rate_engine.go b/pkg/services/ghc_rate_engine.go index 4a74692a5bb..b024ac1d4e3 100644 --- a/pkg/services/ghc_rate_engine.go +++ b/pkg/services/ghc_rate_engine.go @@ -237,7 +237,7 @@ type DomesticOriginSITFuelSurchargePricer interface { // //go:generate mockery --name IntlShippingAndLinehaulPricer type IntlShippingAndLinehaulPricer interface { - Price(appCtx appcontext.AppContext, contractCode string, requestedPickupDate time.Time, distance unit.Miles, weight unit.Pound, serviceArea string, isPPM bool) (unit.Cents, PricingDisplayParams, error) + Price(appCtx appcontext.AppContext, contractCode string, requestedPickupDate time.Time, distance unit.Miles, weight unit.Pound, perUnitCents int) (unit.Cents, PricingDisplayParams, error) ParamsPricer } diff --git a/pkg/services/ghcrateengine/intl_shipping_and_linehaul_pricer.go b/pkg/services/ghcrateengine/intl_shipping_and_linehaul_pricer.go index 2fec86a9b57..ca0624d75a2 100644 --- a/pkg/services/ghcrateengine/intl_shipping_and_linehaul_pricer.go +++ b/pkg/services/ghcrateengine/intl_shipping_and_linehaul_pricer.go @@ -13,46 +13,40 @@ import ( "github.com/transcom/mymove/pkg/unit" ) +const islhPricerMinimumWeight = unit.Pound(500) + type intlShippingAndLinehaulPricer struct { } -// NewDomesticLinehaulPricer creates a new pricer for domestic linehaul services -func NewIntlShippingAndLinehaulPricer() services.DomesticLinehaulPricer { +func NewIntlShippingAndLinehaulPricer() services.IntlShippingAndLinehaulPricer { return &intlShippingAndLinehaulPricer{} } -// Price determines the price for a domestic linehaul -func (p intlShippingAndLinehaulPricer) Price(appCtx appcontext.AppContext, contractCode string, referenceDate time.Time, distance unit.Miles, weight unit.Pound, serviceArea string, isPPM bool) (unit.Cents, services.PricingDisplayParams, error) { - // Validate parameters +func (p intlShippingAndLinehaulPricer) Price(appCtx appcontext.AppContext, contractCode string, referenceDate time.Time, distance unit.Miles, weight unit.Pound, perUnitCents int) (unit.Cents, services.PricingDisplayParams, error) { if len(contractCode) == 0 { return 0, nil, errors.New("ContractCode is required") } if referenceDate.IsZero() { - return 0, nil, errors.New("ReferenceDate is required") + return 0, nil, errors.New("referenceDate is required") } - if !isPPM && weight < dlhPricerMinimumWeight { - return 0, nil, fmt.Errorf("Weight must be at least %d", dlhPricerMinimumWeight) + if weight < islhPricerMinimumWeight { + return 0, nil, fmt.Errorf("weight must be at least %d", islhPricerMinimumWeight) } - if len(serviceArea) == 0 { - return 0, nil, errors.New("ServiceArea is required") + if perUnitCents == 0 { + return 0, nil, errors.New("PerUnitCents is required") } isPeakPeriod := IsPeakPeriod(referenceDate) - finalWeight := weight - - if isPPM && weight < dlhPricerMinimumWeight { - finalWeight = dlhPricerMinimumWeight - } - domesticLinehaulPrice, err := fetchDomesticLinehaulPrice(appCtx, contractCode, isPeakPeriod, distance, finalWeight, serviceArea) + contract, err := fetchContractsByContractCode(appCtx, contractCode) if err != nil { - return unit.Cents(0), nil, fmt.Errorf("could not fetch domestic linehaul rate: %w", err) + return 0, nil, fmt.Errorf("could not find contract with code: %s: %w", contractCode, err) } - basePrice := domesticLinehaulPrice.PriceMillicents.Float64() / 1000 + basePrice := float64(perUnitCents) escalatedPrice, contractYear, err := escalatePriceForContractYear( appCtx, - domesticLinehaulPrice.ContractID, + contract.ID, referenceDate, true, basePrice) @@ -60,26 +54,30 @@ func (p intlShippingAndLinehaulPricer) Price(appCtx appcontext.AppContext, contr return 0, nil, fmt.Errorf("could not calculate escalated price: %w", err) } - totalPrice := finalWeight.ToCWTFloat64() * distance.Float64() * escalatedPrice - totalPriceCents := unit.Cents(math.Round(totalPrice)) + escalatedPrice = escalatedPrice * weight.ToCWTFloat64() + totalPriceCents := unit.Cents(math.Round(escalatedPrice)) params := services.PricingDisplayParams{ - {Key: models.ServiceItemParamNameContractYearName, Value: contractYear.Name}, - {Key: models.ServiceItemParamNameEscalationCompounded, Value: FormatEscalation(contractYear.EscalationCompounded)}, - {Key: models.ServiceItemParamNameIsPeak, Value: FormatBool(isPeakPeriod)}, - {Key: models.ServiceItemParamNamePriceRateOrFactor, Value: FormatFloat(domesticLinehaulPrice.PriceMillicents.ToDollarFloatNoRound(), 3)}, - } - - if isPPM && weight < dlhPricerMinimumWeight { - weightFactor := float64(weight) / float64(dlhPricerMinimumWeight) - cost := float64(weightFactor) * float64(totalPriceCents) - return unit.Cents(cost), params, nil - } + { + Key: models.ServiceItemParamNameContractYearName, + Value: contractYear.Name, + }, + { + Key: models.ServiceItemParamNameEscalationCompounded, + Value: FormatEscalation(contractYear.EscalationCompounded), + }, + { + Key: models.ServiceItemParamNameIsPeak, + Value: FormatBool(isPeakPeriod), + }, + { + Key: models.ServiceItemParamNamePriceRateOrFactor, + Value: FormatCents(unit.Cents(perUnitCents)), + }} return totalPriceCents, params, nil } -// PriceUsingParams determines the price for a domestic linehaul given PaymentServiceItemParams func (p intlShippingAndLinehaulPricer) PriceUsingParams(appCtx appcontext.AppContext, params models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error) { contractCode, err := getParamString(params, models.ServiceItemParamNameContractCode) if err != nil { @@ -96,42 +94,15 @@ func (p intlShippingAndLinehaulPricer) PriceUsingParams(appCtx appcontext.AppCon return unit.Cents(0), nil, err } - serviceAreaOrigin, err := getParamString(params, models.ServiceItemParamNameServiceAreaOrigin) + weightBilled, err := getParamInt(params, models.ServiceItemParamNameWeightBilled) if err != nil { return unit.Cents(0), nil, err } - weightBilled, err := getParamInt(params, models.ServiceItemParamNameWeightBilled) + perUnitCents, err := getParamInt(params, models.ServiceItemParamNamePerUnitCents) if err != nil { return unit.Cents(0), nil, err } - var isPPM = false - if params[0].PaymentServiceItem.MTOServiceItem.MTOShipment.ShipmentType == models.MTOShipmentTypePPM { - // PPMs do not require minimums for a shipment's weight or distance - // this flag is passed into the Price function to ensure the weight and distance mins - // are not enforced for PPMs - isPPM = true - } - - return p.Price(appCtx, contractCode, referenceDate, unit.Miles(distance), unit.Pound(weightBilled), serviceAreaOrigin, isPPM) + return p.Price(appCtx, contractCode, referenceDate, unit.Miles(distance), unit.Pound(weightBilled), perUnitCents) } - -// func fetchDomesticLinehaulPrice(appCtx appcontext.AppContext, contractCode string, isPeakPeriod bool, distance unit.Miles, weight unit.Pound, serviceArea string) (models.ReDomesticLinehaulPrice, error) { -// var domesticLinehaulPrice models.ReDomesticLinehaulPrice -// err := appCtx.DB().Q(). -// Join("re_domestic_service_areas sa", "domestic_service_area_id = sa.id"). -// Join("re_contracts c", "re_domestic_linehaul_prices.contract_id = c.id"). -// Where("c.code = $1", contractCode). -// Where("re_domestic_linehaul_prices.is_peak_period = $2", isPeakPeriod). -// Where("$3 between weight_lower and weight_upper", weight). -// Where("$4 between miles_lower and miles_upper", distance). -// Where("sa.service_area = $5", serviceArea). -// First(&domesticLinehaulPrice) - -// if err != nil { -// return models.ReDomesticLinehaulPrice{}, err -// } - -// return domesticLinehaulPrice, nil -// } diff --git a/pkg/services/ghcrateengine/pricer_helpers_intl.go b/pkg/services/ghcrateengine/pricer_helpers_intl.go index 73428847e51..37f01ee9966 100644 --- a/pkg/services/ghcrateengine/pricer_helpers_intl.go +++ b/pkg/services/ghcrateengine/pricer_helpers_intl.go @@ -38,6 +38,7 @@ func priceIntlPackUnpack(appCtx appcontext.AppContext, packUnpackCode models.ReS } escalatedPrice = escalatedPrice * weight.ToCWTFloat64() + totalCost := unit.Cents(math.Round(escalatedPrice)) displayParams := services.PricingDisplayParams{ { @@ -58,6 +59,5 @@ func priceIntlPackUnpack(appCtx appcontext.AppContext, packUnpackCode models.ReS }, } - totalCost := unit.Cents(math.Round(escalatedPrice)) return totalCost, displayParams, nil } From d43303f650c2ca7a3e6751a6dc29294a2fd9636f Mon Sep 17 00:00:00 2001 From: Daniel Jordan Date: Thu, 2 Jan 2025 19:38:55 +0000 Subject: [PATCH 37/54] test for rate area id --- pkg/factory/address_factory.go | 10 +++++++++ pkg/factory/address_factory_test.go | 1 + pkg/models/re_rate_area.go | 10 ++++----- pkg/models/re_rate_area_test.go | 22 +++++++++++++++++++ .../per_unit_cents_lookup.go | 8 +++---- 5 files changed, 41 insertions(+), 10 deletions(-) diff --git a/pkg/factory/address_factory.go b/pkg/factory/address_factory.go index 91a49da4445..d6b7dff6ce5 100644 --- a/pkg/factory/address_factory.go +++ b/pkg/factory/address_factory.go @@ -72,6 +72,16 @@ func BuildAddress(db *pop.Connection, customs []Customization, traits []Trait) m address.County = models.StringPointer("db nil when created") } + if db != nil { + usprc, err := models.FindByZipCode(db, address.PostalCode) + if err != nil { + return models.Address{} + } else { + address.UsPostRegionCityID = &usprc.ID + address.UsPostRegionCity = usprc + } + } + // If db is false, it's a stub. No need to create in database. if db != nil { mustCreate(db, &address) diff --git a/pkg/factory/address_factory_test.go b/pkg/factory/address_factory_test.go index 2e17e564605..7f72a13e9b6 100644 --- a/pkg/factory/address_factory_test.go +++ b/pkg/factory/address_factory_test.go @@ -40,6 +40,7 @@ func (suite *FactorySuite) TestBuildAddress() { suite.Equal(defaultPostalCode, address.PostalCode) suite.Equal(country.ID, *address.CountryId) suite.Equal(defaultCounty, *address.County) + suite.NotNil(*address.UsPostRegionCityID) }) suite.Run("Successful creation of an address with customization", func() { diff --git a/pkg/models/re_rate_area.go b/pkg/models/re_rate_area.go index 960f4258d8c..7b613b42a28 100644 --- a/pkg/models/re_rate_area.go +++ b/pkg/models/re_rate_area.go @@ -56,14 +56,12 @@ func FetchReRateAreaItem(tx *pop.Connection, contractID uuid.UUID, code string) } // a db stored proc that takes in an address id & a service code to get the rate area id for an address -func FetchRateAreaID(db *pop.Connection, addressID uuid.UUID, serviceID uuid.UUID) (uuid.UUID, error) { - if addressID != uuid.Nil && serviceID != uuid.Nil { +func FetchRateAreaID(db *pop.Connection, addressID uuid.UUID, serviceID uuid.UUID, contractID uuid.UUID) (uuid.UUID, error) { + if addressID != uuid.Nil && serviceID != uuid.Nil && contractID != uuid.Nil { var rateAreaID uuid.UUID - err := db.RawQuery("SELECT get_rate_area_id($1, $2)", addressID, serviceID). - First(&rateAreaID) - + err := db.RawQuery("SELECT get_rate_area_id($1, $2, $3)", addressID, serviceID, contractID).First(&rateAreaID) if err != nil { - return uuid.Nil, fmt.Errorf("error fetching rate area id for shipment ID: %s and service ID %s: %s", addressID, serviceID, err) + return uuid.Nil, fmt.Errorf("error fetching rate area id for shipment ID: %s, service ID %s, and contract ID: %s: %s", addressID, serviceID, contractID, err) } return rateAreaID, nil } diff --git a/pkg/models/re_rate_area_test.go b/pkg/models/re_rate_area_test.go index a0769056783..87f310c2088 100644 --- a/pkg/models/re_rate_area_test.go +++ b/pkg/models/re_rate_area_test.go @@ -3,7 +3,9 @@ package models_test import ( "github.com/gofrs/uuid" + "github.com/transcom/mymove/pkg/factory" "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/testdatagen" ) func (suite *ModelSuite) TestReRateAreaValidation() { @@ -28,3 +30,23 @@ func (suite *ModelSuite) TestReRateAreaValidation() { suite.verifyValidationErrors(&emptyReRateArea, expErrors) }) } + +func (suite *ModelSuite) TestFetchRateAreaID() { + suite.Run("success - fetching a rate area ID", func() { + service := factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeIHPK) + contract := testdatagen.FetchOrMakeReContract(suite.DB(), testdatagen.Assertions{}) + address := factory.BuildAddress(suite.DB(), nil, nil) + rateAreaId, err := models.FetchRateAreaID(suite.DB(), address.ID, service.ID, contract.ID) + suite.NotNil(rateAreaId) + suite.NoError(err) + }) + + suite.Run("fail - receive error when not all values are provided", func() { + var nilUuid uuid.UUID + contract := testdatagen.FetchOrMakeReContract(suite.DB(), testdatagen.Assertions{}) + address := factory.BuildAddress(suite.DB(), nil, nil) + rateAreaId, err := models.FetchRateAreaID(suite.DB(), address.ID, nilUuid, contract.ID) + suite.Equal(uuid.Nil, rateAreaId) + suite.Error(err) + }) +} diff --git a/pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup.go b/pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup.go index a25913f8e2c..88a46c98e44 100644 --- a/pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup.go +++ b/pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup.go @@ -21,7 +21,7 @@ func (p PerUnitCentsLookup) lookup(appCtx appcontext.AppContext, s *ServiceItemP contractID := s.ContractID if p.ServiceItem.ReService.Code == models.ReServiceCodeIHPK { // IHPK we need the rate area id for the pickup address - rateAreaID, err := models.FetchRateAreaID(appCtx.DB(), *p.MTOShipment.PickupAddressID, p.ServiceItem.ReServiceID) + rateAreaID, err := models.FetchRateAreaID(appCtx.DB(), *p.MTOShipment.PickupAddressID, p.ServiceItem.ReServiceID, contractID) if err != nil { return "", fmt.Errorf("error fetching rate area id for shipment ID: %s and service ID %s: %s", p.MTOShipment.ID, p.ServiceItem.ReServiceID, err) } @@ -40,7 +40,7 @@ func (p PerUnitCentsLookup) lookup(appCtx appcontext.AppContext, s *ServiceItemP } if p.ServiceItem.ReService.Code == models.ReServiceCodeIHUPK { // IHUPK we need the rate area id for the destination address - rateAreaID, err := models.FetchRateAreaID(appCtx.DB(), *p.MTOShipment.PickupAddressID, p.ServiceItem.ReServiceID) + rateAreaID, err := models.FetchRateAreaID(appCtx.DB(), *p.MTOShipment.PickupAddressID, p.ServiceItem.ReServiceID, contractID) if err != nil && err != sql.ErrNoRows { return "", fmt.Errorf("error fetching rate area id for shipment ID: %s and service ID %s: %s", p.MTOShipment.ID, p.ServiceItem.ReServiceID, err) } @@ -58,11 +58,11 @@ func (p PerUnitCentsLookup) lookup(appCtx appcontext.AppContext, s *ServiceItemP return reIntlOtherPrice.PerUnitCents.ToMillicents().ToCents().String(), nil } else if p.ServiceItem.ReService.Code == models.ReServiceCodeISLH { // IHUPK we need the rate area id for the destination address - originRateAreaID, err := models.FetchRateAreaID(appCtx.DB(), *p.MTOShipment.PickupAddressID, p.ServiceItem.ReServiceID) + originRateAreaID, err := models.FetchRateAreaID(appCtx.DB(), *p.MTOShipment.PickupAddressID, p.ServiceItem.ReServiceID, contractID) if err != nil && err != sql.ErrNoRows { return "", fmt.Errorf("error fetching rate area id for origina address for shipment ID: %s and service ID %s: %s", p.MTOShipment.ID, p.ServiceItem.ReServiceID, err) } - destRateAreaID, err := models.FetchRateAreaID(appCtx.DB(), *p.MTOShipment.DestinationAddressID, p.ServiceItem.ReServiceID) + destRateAreaID, err := models.FetchRateAreaID(appCtx.DB(), *p.MTOShipment.DestinationAddressID, p.ServiceItem.ReServiceID, contractID) if err != nil && err != sql.ErrNoRows { return "", fmt.Errorf("error fetching rate area id for destination address for shipment ID: %s and service ID %s: %s", p.MTOShipment.ID, p.ServiceItem.ReServiceID, err) } From 776f7307919af7f5d3efeb963f0b812a75402cfb Mon Sep 17 00:00:00 2001 From: Daniel Jordan Date: Thu, 2 Jan 2025 20:34:42 +0000 Subject: [PATCH 38/54] added tests for service param value lookups folder --- .../per_unit_cents_lookup.go | 52 +++---- .../per_unit_cents_lookup_test.go | 132 ++++++++++++++++++ .../port_name_lookup_test.go | 87 ++++++++++++ 3 files changed, 246 insertions(+), 25 deletions(-) create mode 100644 pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup_test.go create mode 100644 pkg/payment_request/service_param_value_lookups/port_name_lookup_test.go diff --git a/pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup.go b/pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup.go index 88a46c98e44..d2062218620 100644 --- a/pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup.go +++ b/pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup.go @@ -1,7 +1,6 @@ package serviceparamvaluelookups import ( - "database/sql" "fmt" "github.com/transcom/mymove/pkg/appcontext" @@ -16,16 +15,17 @@ type PerUnitCentsLookup struct { } func (p PerUnitCentsLookup) lookup(appCtx appcontext.AppContext, s *ServiceItemParamKeyData) (string, error) { - var isPeakPeriod bool serviceID := p.ServiceItem.ReServiceID contractID := s.ContractID - if p.ServiceItem.ReService.Code == models.ReServiceCodeIHPK { - // IHPK we need the rate area id for the pickup address - rateAreaID, err := models.FetchRateAreaID(appCtx.DB(), *p.MTOShipment.PickupAddressID, p.ServiceItem.ReServiceID, contractID) + + switch p.ServiceItem.ReService.Code { + case models.ReServiceCodeIHPK: + // IHPK: Need rate area ID for the pickup address + rateAreaID, err := models.FetchRateAreaID(appCtx.DB(), *p.MTOShipment.PickupAddressID, serviceID, contractID) if err != nil { - return "", fmt.Errorf("error fetching rate area id for shipment ID: %s and service ID %s: %s", p.MTOShipment.ID, p.ServiceItem.ReServiceID, err) + return "", fmt.Errorf("error fetching rate area id for shipment ID: %s and service ID %s: %s", p.MTOShipment.ID, serviceID, err) } - isPeakPeriod = ghcrateengine.IsPeakPeriod(*p.MTOShipment.RequestedPickupDate) + isPeakPeriod := ghcrateengine.IsPeakPeriod(*p.MTOShipment.RequestedPickupDate) var reIntlOtherPrice models.ReIntlOtherPrice err = appCtx.DB().Q(). Where("contract_id = ?", contractID). @@ -37,14 +37,14 @@ func (p PerUnitCentsLookup) lookup(appCtx appcontext.AppContext, s *ServiceItemP return "", fmt.Errorf("error fetching IHPK per unit cents for contractID: %s, serviceID %s, isPeakPeriod: %t, and rateAreaID: %s: %s", contractID, serviceID, isPeakPeriod, rateAreaID, err) } return reIntlOtherPrice.PerUnitCents.ToMillicents().ToCents().String(), nil - } - if p.ServiceItem.ReService.Code == models.ReServiceCodeIHUPK { - // IHUPK we need the rate area id for the destination address - rateAreaID, err := models.FetchRateAreaID(appCtx.DB(), *p.MTOShipment.PickupAddressID, p.ServiceItem.ReServiceID, contractID) - if err != nil && err != sql.ErrNoRows { - return "", fmt.Errorf("error fetching rate area id for shipment ID: %s and service ID %s: %s", p.MTOShipment.ID, p.ServiceItem.ReServiceID, err) + + case models.ReServiceCodeIHUPK: + // IHUPK: Need rate area ID for the destination address + rateAreaID, err := models.FetchRateAreaID(appCtx.DB(), *p.MTOShipment.PickupAddressID, serviceID, contractID) + if err != nil { + return "", fmt.Errorf("error fetching rate area id for shipment ID: %s and service ID %s: %s", p.MTOShipment.ID, serviceID, err) } - isPeakPeriod = ghcrateengine.IsPeakPeriod(*p.MTOShipment.RequestedPickupDate) + isPeakPeriod := ghcrateengine.IsPeakPeriod(*p.MTOShipment.RequestedPickupDate) var reIntlOtherPrice models.ReIntlOtherPrice err = appCtx.DB().Q(). Where("contract_id = ?", contractID). @@ -56,17 +56,18 @@ func (p PerUnitCentsLookup) lookup(appCtx appcontext.AppContext, s *ServiceItemP return "", fmt.Errorf("error fetching IHUPK per unit cents for contractID: %s, serviceID %s, isPeakPeriod: %t, and rateAreaID: %s: %s", contractID, serviceID, isPeakPeriod, rateAreaID, err) } return reIntlOtherPrice.PerUnitCents.ToMillicents().ToCents().String(), nil - } else if p.ServiceItem.ReService.Code == models.ReServiceCodeISLH { - // IHUPK we need the rate area id for the destination address - originRateAreaID, err := models.FetchRateAreaID(appCtx.DB(), *p.MTOShipment.PickupAddressID, p.ServiceItem.ReServiceID, contractID) - if err != nil && err != sql.ErrNoRows { - return "", fmt.Errorf("error fetching rate area id for origina address for shipment ID: %s and service ID %s: %s", p.MTOShipment.ID, p.ServiceItem.ReServiceID, err) + + case models.ReServiceCodeISLH: + // ISLH: Need rate area IDs for origin and destination + originRateAreaID, err := models.FetchRateAreaID(appCtx.DB(), *p.MTOShipment.PickupAddressID, serviceID, contractID) + if err != nil { + return "", fmt.Errorf("error fetching rate area id for origin address for shipment ID: %s and service ID %s: %s", p.MTOShipment.ID, serviceID, err) } - destRateAreaID, err := models.FetchRateAreaID(appCtx.DB(), *p.MTOShipment.DestinationAddressID, p.ServiceItem.ReServiceID, contractID) - if err != nil && err != sql.ErrNoRows { - return "", fmt.Errorf("error fetching rate area id for destination address for shipment ID: %s and service ID %s: %s", p.MTOShipment.ID, p.ServiceItem.ReServiceID, err) + destRateAreaID, err := models.FetchRateAreaID(appCtx.DB(), *p.MTOShipment.DestinationAddressID, serviceID, contractID) + if err != nil { + return "", fmt.Errorf("error fetching rate area id for destination address for shipment ID: %s and service ID %s: %s", p.MTOShipment.ID, serviceID, err) } - isPeakPeriod = ghcrateengine.IsPeakPeriod(*p.MTOShipment.RequestedPickupDate) + isPeakPeriod := ghcrateengine.IsPeakPeriod(*p.MTOShipment.RequestedPickupDate) var reIntlPrice models.ReIntlPrice err = appCtx.DB().Q(). Where("contract_id = ?", contractID). @@ -76,10 +77,11 @@ func (p PerUnitCentsLookup) lookup(appCtx appcontext.AppContext, s *ServiceItemP Where("destination_rate_area_id = ?", destRateAreaID). First(&reIntlPrice) if err != nil { - return "", fmt.Errorf("error fetching ISLH per unit cents for contractID: %s, serviceID %s, isPeakPeriod: %t, originRateAreaID: %s, and destRateAreaid: %s: %s", contractID, serviceID, isPeakPeriod, originRateAreaID, destRateAreaID, err) + return "", fmt.Errorf("error fetching ISLH per unit cents for contractID: %s, serviceID %s, isPeakPeriod: %t, originRateAreaID: %s, and destRateAreaID: %s: %s", contractID, serviceID, isPeakPeriod, originRateAreaID, destRateAreaID, err) } return reIntlPrice.PerUnitCents.ToMillicents().ToCents().String(), nil - } else { + + default: return "", fmt.Errorf("unsupported service code to retrieve service item param PerUnitCents") } } diff --git a/pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup_test.go b/pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup_test.go new file mode 100644 index 00000000000..4d9ceab2512 --- /dev/null +++ b/pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup_test.go @@ -0,0 +1,132 @@ +package serviceparamvaluelookups + +import ( + "time" + + "github.com/gofrs/uuid" + + "github.com/transcom/mymove/pkg/factory" + "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/testdatagen" +) + +func (suite *ServiceParamValueLookupsSuite) TestPerUnitCentsLookup() { + key := models.ServiceItemParamNamePerUnitCents + var mtoServiceItem models.MTOServiceItem + setupTestData := func(serviceCode models.ReServiceCode) { + testdatagen.MakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + StartDate: time.Now().Add(-24 * time.Hour), + EndDate: time.Now().Add(24 * time.Hour), + }, + }) + mtoServiceItem = factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: models.ReService{ + Code: serviceCode, + }, + }, + }, []factory.Trait{factory.GetTraitAvailableToPrimeMove}) + + } + + suite.Run("success - returns perUnitCent value for IHPK", func() { + setupTestData(models.ReServiceCodeIHPK) + + paramLookup, err := ServiceParamLookupInitialize(suite.AppContextForTest(), suite.planner, mtoServiceItem, uuid.Must(uuid.NewV4()), mtoServiceItem.MoveTaskOrderID, nil) + suite.FatalNoError(err) + + perUnitCents, err := paramLookup.ServiceParamValue(suite.AppContextForTest(), key) + suite.FatalNoError(err) + suite.Equal(perUnitCents, "6997") + }) + + suite.Run("success - returns perUnitCent value for IHUPK", func() { + setupTestData(models.ReServiceCodeIHUPK) + + paramLookup, err := ServiceParamLookupInitialize(suite.AppContextForTest(), suite.planner, mtoServiceItem, uuid.Must(uuid.NewV4()), mtoServiceItem.MoveTaskOrderID, nil) + suite.FatalNoError(err) + + perUnitCents, err := paramLookup.ServiceParamValue(suite.AppContextForTest(), key) + suite.FatalNoError(err) + suite.Equal(perUnitCents, "752") + }) + + suite.Run("success - returns perUnitCent value for ISLH", func() { + testdatagen.MakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + StartDate: time.Now().Add(-24 * time.Hour), + EndDate: time.Now().Add(24 * time.Hour), + }, + }) + move := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) + destinationAddress := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "JBER", + City: "Anchorage", + State: "AK", + PostalCode: "99505", + IsOconus: models.BoolPointer(true), + }, + }, + }, nil) + pickupAddress := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "Tester Address", + City: "Des Moines", + State: "IA", + PostalCode: "50314", + IsOconus: models.BoolPointer(false), + }, + }, + }, nil) + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + PickupAddressID: &pickupAddress.ID, + DestinationAddressID: &destinationAddress.ID, + MarketCode: models.MarketCodeInternational, + }, + }, + { + Model: move, + LinkOnly: true, + }, + }, nil) + mtoServiceItem := factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.ReService{ + Code: models.ReServiceCodeISLH, + }, + }, + }, nil) + + paramLookup, err := ServiceParamLookupInitialize(suite.AppContextForTest(), suite.planner, mtoServiceItem, uuid.Must(uuid.NewV4()), mtoServiceItem.MoveTaskOrderID, nil) + suite.FatalNoError(err) + + perUnitCents, err := paramLookup.ServiceParamValue(suite.AppContextForTest(), key) + suite.FatalNoError(err) + suite.Equal(perUnitCents, "1605") + }) + + suite.Run("failure - unauthorized service code", func() { + setupTestData(models.ReServiceCodeDUPK) + + paramLookup, err := ServiceParamLookupInitialize(suite.AppContextForTest(), suite.planner, mtoServiceItem, uuid.Must(uuid.NewV4()), mtoServiceItem.MoveTaskOrderID, nil) + suite.FatalNoError(err) + + perUnitCents, err := paramLookup.ServiceParamValue(suite.AppContextForTest(), key) + suite.Error(err) + suite.Equal(perUnitCents, "") + }) +} diff --git a/pkg/payment_request/service_param_value_lookups/port_name_lookup_test.go b/pkg/payment_request/service_param_value_lookups/port_name_lookup_test.go new file mode 100644 index 00000000000..b0ccb4b2bfe --- /dev/null +++ b/pkg/payment_request/service_param_value_lookups/port_name_lookup_test.go @@ -0,0 +1,87 @@ +package serviceparamvaluelookups + +import ( + "time" + + "github.com/gofrs/uuid" + + "github.com/transcom/mymove/pkg/factory" + "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/testdatagen" +) + +func (suite *ServiceParamValueLookupsSuite) TestPortNameLookup() { + key := models.ServiceItemParamNamePortName + var mtoServiceItem models.MTOServiceItem + setupTestData := func(serviceCode models.ReServiceCode, portID uuid.UUID) { + testdatagen.MakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + StartDate: time.Now().Add(-24 * time.Hour), + EndDate: time.Now().Add(24 * time.Hour), + }, + }) + if serviceCode == models.ReServiceCodePOEFSC { + mtoServiceItem = factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: models.ReService{ + Code: serviceCode, + }, + }, + { + Model: models.MTOServiceItem{ + POELocationID: &portID, + }, + }, + }, []factory.Trait{factory.GetTraitAvailableToPrimeMove}) + } else { + mtoServiceItem = factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: models.ReService{ + Code: serviceCode, + }, + }, + { + Model: models.MTOServiceItem{ + PODLocationID: &portID, + }, + }, + }, []factory.Trait{factory.GetTraitAvailableToPrimeMove}) + } + } + + suite.Run("success - returns PortName value for POEFSC", func() { + port := factory.FetchPortLocation(suite.DB(), []factory.Customization{ + { + Model: models.Port{ + PortCode: "SEA", + }, + }, + }, nil) + setupTestData(models.ReServiceCodePOEFSC, port.ID) + + paramLookup, err := ServiceParamLookupInitialize(suite.AppContextForTest(), suite.planner, mtoServiceItem, uuid.Must(uuid.NewV4()), mtoServiceItem.MoveTaskOrderID, nil) + suite.FatalNoError(err) + + portName, err := paramLookup.ServiceParamValue(suite.AppContextForTest(), key) + suite.FatalNoError(err) + suite.Equal(portName, "SEATTLE TACOMA INTL") + }) + + suite.Run("success - returns PortName value for PODFSC", func() { + port := factory.FetchPortLocation(suite.DB(), []factory.Customization{ + { + Model: models.Port{ + PortCode: "PDX", + }, + }, + }, nil) + setupTestData(models.ReServiceCodePODFSC, port.ID) + + paramLookup, err := ServiceParamLookupInitialize(suite.AppContextForTest(), suite.planner, mtoServiceItem, uuid.Must(uuid.NewV4()), mtoServiceItem.MoveTaskOrderID, nil) + suite.FatalNoError(err) + + portName, err := paramLookup.ServiceParamValue(suite.AppContextForTest(), key) + suite.FatalNoError(err) + suite.Equal(portName, "PORTLAND INTL") + }) +} From 440003f6a5975a15e24d6ce328a0b87b272cac12 Mon Sep 17 00:00:00 2001 From: Daniel Jordan Date: Thu, 2 Jan 2025 20:39:15 +0000 Subject: [PATCH 39/54] mocks --- pkg/services/ghc_rate_engine.go | 6 +- pkg/services/mocks/IntlHHGPackPricer.go | 109 ++++++++++++++++++ pkg/services/mocks/IntlHHGUnpackPricer.go | 109 ++++++++++++++++++ .../mocks/IntlPortFuelSurchargePricer.go | 109 ++++++++++++++++++ .../mocks/IntlShippingAndLinehaulPricer.go | 109 ++++++++++++++++++ 5 files changed, 439 insertions(+), 3 deletions(-) create mode 100644 pkg/services/mocks/IntlHHGPackPricer.go create mode 100644 pkg/services/mocks/IntlHHGUnpackPricer.go create mode 100644 pkg/services/mocks/IntlPortFuelSurchargePricer.go create mode 100644 pkg/services/mocks/IntlShippingAndLinehaulPricer.go diff --git a/pkg/services/ghc_rate_engine.go b/pkg/services/ghc_rate_engine.go index b024ac1d4e3..8b3ef07de0f 100644 --- a/pkg/services/ghc_rate_engine.go +++ b/pkg/services/ghc_rate_engine.go @@ -241,7 +241,7 @@ type IntlShippingAndLinehaulPricer interface { ParamsPricer } -// IntlHHGPackPricer prices international packing for an HHG shipment within a move +// IntlHHGPackPricer prices international packing for an iHHG shipment within a move // //go:generate mockery --name IntlHHGPackPricer type IntlHHGPackPricer interface { @@ -249,7 +249,7 @@ type IntlHHGPackPricer interface { ParamsPricer } -// IntlHHGUnpackPricer prices international unpacking for an HHG shipment within a move +// IntlHHGUnpackPricer prices international unpacking for an iHHG shipment within a move // //go:generate mockery --name IntlHHGUnpackPricer type IntlHHGUnpackPricer interface { @@ -257,7 +257,7 @@ type IntlHHGUnpackPricer interface { ParamsPricer } -// IntlPortFuelSurchargePricer prices the POEFSC/PODFSC service items on a shipment within a move +// IntlPortFuelSurchargePricer prices the POEFSC/PODFSC service items on an iHHG shipment within a move // //go:generate mockery --name IntlPortFuelSurchargePricer type IntlPortFuelSurchargePricer interface { diff --git a/pkg/services/mocks/IntlHHGPackPricer.go b/pkg/services/mocks/IntlHHGPackPricer.go new file mode 100644 index 00000000000..a12bed589ec --- /dev/null +++ b/pkg/services/mocks/IntlHHGPackPricer.go @@ -0,0 +1,109 @@ +// Code generated by mockery. DO NOT EDIT. + +package mocks + +import ( + mock "github.com/stretchr/testify/mock" + appcontext "github.com/transcom/mymove/pkg/appcontext" + + models "github.com/transcom/mymove/pkg/models" + + services "github.com/transcom/mymove/pkg/services" + + time "time" + + unit "github.com/transcom/mymove/pkg/unit" +) + +// IntlHHGPackPricer is an autogenerated mock type for the IntlHHGPackPricer type +type IntlHHGPackPricer struct { + mock.Mock +} + +// Price provides a mock function with given fields: appCtx, contractCode, requestedPickupDate, weight, perUnitCents +func (_m *IntlHHGPackPricer) Price(appCtx appcontext.AppContext, contractCode string, requestedPickupDate time.Time, weight unit.Pound, perUnitCents int) (unit.Cents, services.PricingDisplayParams, error) { + ret := _m.Called(appCtx, contractCode, requestedPickupDate, weight, perUnitCents) + + if len(ret) == 0 { + panic("no return value specified for Price") + } + + var r0 unit.Cents + var r1 services.PricingDisplayParams + var r2 error + if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, time.Time, unit.Pound, int) (unit.Cents, services.PricingDisplayParams, error)); ok { + return rf(appCtx, contractCode, requestedPickupDate, weight, perUnitCents) + } + if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, time.Time, unit.Pound, int) unit.Cents); ok { + r0 = rf(appCtx, contractCode, requestedPickupDate, weight, perUnitCents) + } else { + r0 = ret.Get(0).(unit.Cents) + } + + if rf, ok := ret.Get(1).(func(appcontext.AppContext, string, time.Time, unit.Pound, int) services.PricingDisplayParams); ok { + r1 = rf(appCtx, contractCode, requestedPickupDate, weight, perUnitCents) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(services.PricingDisplayParams) + } + } + + if rf, ok := ret.Get(2).(func(appcontext.AppContext, string, time.Time, unit.Pound, int) error); ok { + r2 = rf(appCtx, contractCode, requestedPickupDate, weight, perUnitCents) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// PriceUsingParams provides a mock function with given fields: appCtx, params +func (_m *IntlHHGPackPricer) PriceUsingParams(appCtx appcontext.AppContext, params models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error) { + ret := _m.Called(appCtx, params) + + if len(ret) == 0 { + panic("no return value specified for PriceUsingParams") + } + + var r0 unit.Cents + var r1 services.PricingDisplayParams + var r2 error + if rf, ok := ret.Get(0).(func(appcontext.AppContext, models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error)); ok { + return rf(appCtx, params) + } + if rf, ok := ret.Get(0).(func(appcontext.AppContext, models.PaymentServiceItemParams) unit.Cents); ok { + r0 = rf(appCtx, params) + } else { + r0 = ret.Get(0).(unit.Cents) + } + + if rf, ok := ret.Get(1).(func(appcontext.AppContext, models.PaymentServiceItemParams) services.PricingDisplayParams); ok { + r1 = rf(appCtx, params) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(services.PricingDisplayParams) + } + } + + if rf, ok := ret.Get(2).(func(appcontext.AppContext, models.PaymentServiceItemParams) error); ok { + r2 = rf(appCtx, params) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// NewIntlHHGPackPricer creates a new instance of IntlHHGPackPricer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewIntlHHGPackPricer(t interface { + mock.TestingT + Cleanup(func()) +}) *IntlHHGPackPricer { + mock := &IntlHHGPackPricer{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/services/mocks/IntlHHGUnpackPricer.go b/pkg/services/mocks/IntlHHGUnpackPricer.go new file mode 100644 index 00000000000..d06f97791d5 --- /dev/null +++ b/pkg/services/mocks/IntlHHGUnpackPricer.go @@ -0,0 +1,109 @@ +// Code generated by mockery. DO NOT EDIT. + +package mocks + +import ( + mock "github.com/stretchr/testify/mock" + appcontext "github.com/transcom/mymove/pkg/appcontext" + + models "github.com/transcom/mymove/pkg/models" + + services "github.com/transcom/mymove/pkg/services" + + time "time" + + unit "github.com/transcom/mymove/pkg/unit" +) + +// IntlHHGUnpackPricer is an autogenerated mock type for the IntlHHGUnpackPricer type +type IntlHHGUnpackPricer struct { + mock.Mock +} + +// Price provides a mock function with given fields: appCtx, contractCode, requestedPickupDate, weight, perUnitCents +func (_m *IntlHHGUnpackPricer) Price(appCtx appcontext.AppContext, contractCode string, requestedPickupDate time.Time, weight unit.Pound, perUnitCents int) (unit.Cents, services.PricingDisplayParams, error) { + ret := _m.Called(appCtx, contractCode, requestedPickupDate, weight, perUnitCents) + + if len(ret) == 0 { + panic("no return value specified for Price") + } + + var r0 unit.Cents + var r1 services.PricingDisplayParams + var r2 error + if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, time.Time, unit.Pound, int) (unit.Cents, services.PricingDisplayParams, error)); ok { + return rf(appCtx, contractCode, requestedPickupDate, weight, perUnitCents) + } + if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, time.Time, unit.Pound, int) unit.Cents); ok { + r0 = rf(appCtx, contractCode, requestedPickupDate, weight, perUnitCents) + } else { + r0 = ret.Get(0).(unit.Cents) + } + + if rf, ok := ret.Get(1).(func(appcontext.AppContext, string, time.Time, unit.Pound, int) services.PricingDisplayParams); ok { + r1 = rf(appCtx, contractCode, requestedPickupDate, weight, perUnitCents) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(services.PricingDisplayParams) + } + } + + if rf, ok := ret.Get(2).(func(appcontext.AppContext, string, time.Time, unit.Pound, int) error); ok { + r2 = rf(appCtx, contractCode, requestedPickupDate, weight, perUnitCents) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// PriceUsingParams provides a mock function with given fields: appCtx, params +func (_m *IntlHHGUnpackPricer) PriceUsingParams(appCtx appcontext.AppContext, params models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error) { + ret := _m.Called(appCtx, params) + + if len(ret) == 0 { + panic("no return value specified for PriceUsingParams") + } + + var r0 unit.Cents + var r1 services.PricingDisplayParams + var r2 error + if rf, ok := ret.Get(0).(func(appcontext.AppContext, models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error)); ok { + return rf(appCtx, params) + } + if rf, ok := ret.Get(0).(func(appcontext.AppContext, models.PaymentServiceItemParams) unit.Cents); ok { + r0 = rf(appCtx, params) + } else { + r0 = ret.Get(0).(unit.Cents) + } + + if rf, ok := ret.Get(1).(func(appcontext.AppContext, models.PaymentServiceItemParams) services.PricingDisplayParams); ok { + r1 = rf(appCtx, params) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(services.PricingDisplayParams) + } + } + + if rf, ok := ret.Get(2).(func(appcontext.AppContext, models.PaymentServiceItemParams) error); ok { + r2 = rf(appCtx, params) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// NewIntlHHGUnpackPricer creates a new instance of IntlHHGUnpackPricer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewIntlHHGUnpackPricer(t interface { + mock.TestingT + Cleanup(func()) +}) *IntlHHGUnpackPricer { + mock := &IntlHHGUnpackPricer{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/services/mocks/IntlPortFuelSurchargePricer.go b/pkg/services/mocks/IntlPortFuelSurchargePricer.go new file mode 100644 index 00000000000..48e396d017e --- /dev/null +++ b/pkg/services/mocks/IntlPortFuelSurchargePricer.go @@ -0,0 +1,109 @@ +// Code generated by mockery. DO NOT EDIT. + +package mocks + +import ( + mock "github.com/stretchr/testify/mock" + appcontext "github.com/transcom/mymove/pkg/appcontext" + + models "github.com/transcom/mymove/pkg/models" + + services "github.com/transcom/mymove/pkg/services" + + time "time" + + unit "github.com/transcom/mymove/pkg/unit" +) + +// IntlPortFuelSurchargePricer is an autogenerated mock type for the IntlPortFuelSurchargePricer type +type IntlPortFuelSurchargePricer struct { + mock.Mock +} + +// Price provides a mock function with given fields: appCtx, actualPickupDate, distance, weight, fscWeightBasedDistanceMultiplier, eiaFuelPrice, portName +func (_m *IntlPortFuelSurchargePricer) Price(appCtx appcontext.AppContext, actualPickupDate time.Time, distance unit.Miles, weight unit.Pound, fscWeightBasedDistanceMultiplier float64, eiaFuelPrice unit.Millicents, portName string) (unit.Cents, services.PricingDisplayParams, error) { + ret := _m.Called(appCtx, actualPickupDate, distance, weight, fscWeightBasedDistanceMultiplier, eiaFuelPrice, portName) + + if len(ret) == 0 { + panic("no return value specified for Price") + } + + var r0 unit.Cents + var r1 services.PricingDisplayParams + var r2 error + if rf, ok := ret.Get(0).(func(appcontext.AppContext, time.Time, unit.Miles, unit.Pound, float64, unit.Millicents, string) (unit.Cents, services.PricingDisplayParams, error)); ok { + return rf(appCtx, actualPickupDate, distance, weight, fscWeightBasedDistanceMultiplier, eiaFuelPrice, portName) + } + if rf, ok := ret.Get(0).(func(appcontext.AppContext, time.Time, unit.Miles, unit.Pound, float64, unit.Millicents, string) unit.Cents); ok { + r0 = rf(appCtx, actualPickupDate, distance, weight, fscWeightBasedDistanceMultiplier, eiaFuelPrice, portName) + } else { + r0 = ret.Get(0).(unit.Cents) + } + + if rf, ok := ret.Get(1).(func(appcontext.AppContext, time.Time, unit.Miles, unit.Pound, float64, unit.Millicents, string) services.PricingDisplayParams); ok { + r1 = rf(appCtx, actualPickupDate, distance, weight, fscWeightBasedDistanceMultiplier, eiaFuelPrice, portName) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(services.PricingDisplayParams) + } + } + + if rf, ok := ret.Get(2).(func(appcontext.AppContext, time.Time, unit.Miles, unit.Pound, float64, unit.Millicents, string) error); ok { + r2 = rf(appCtx, actualPickupDate, distance, weight, fscWeightBasedDistanceMultiplier, eiaFuelPrice, portName) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// PriceUsingParams provides a mock function with given fields: appCtx, params +func (_m *IntlPortFuelSurchargePricer) PriceUsingParams(appCtx appcontext.AppContext, params models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error) { + ret := _m.Called(appCtx, params) + + if len(ret) == 0 { + panic("no return value specified for PriceUsingParams") + } + + var r0 unit.Cents + var r1 services.PricingDisplayParams + var r2 error + if rf, ok := ret.Get(0).(func(appcontext.AppContext, models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error)); ok { + return rf(appCtx, params) + } + if rf, ok := ret.Get(0).(func(appcontext.AppContext, models.PaymentServiceItemParams) unit.Cents); ok { + r0 = rf(appCtx, params) + } else { + r0 = ret.Get(0).(unit.Cents) + } + + if rf, ok := ret.Get(1).(func(appcontext.AppContext, models.PaymentServiceItemParams) services.PricingDisplayParams); ok { + r1 = rf(appCtx, params) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(services.PricingDisplayParams) + } + } + + if rf, ok := ret.Get(2).(func(appcontext.AppContext, models.PaymentServiceItemParams) error); ok { + r2 = rf(appCtx, params) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// NewIntlPortFuelSurchargePricer creates a new instance of IntlPortFuelSurchargePricer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewIntlPortFuelSurchargePricer(t interface { + mock.TestingT + Cleanup(func()) +}) *IntlPortFuelSurchargePricer { + mock := &IntlPortFuelSurchargePricer{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/services/mocks/IntlShippingAndLinehaulPricer.go b/pkg/services/mocks/IntlShippingAndLinehaulPricer.go new file mode 100644 index 00000000000..c6e4ca2b557 --- /dev/null +++ b/pkg/services/mocks/IntlShippingAndLinehaulPricer.go @@ -0,0 +1,109 @@ +// Code generated by mockery. DO NOT EDIT. + +package mocks + +import ( + mock "github.com/stretchr/testify/mock" + appcontext "github.com/transcom/mymove/pkg/appcontext" + + models "github.com/transcom/mymove/pkg/models" + + services "github.com/transcom/mymove/pkg/services" + + time "time" + + unit "github.com/transcom/mymove/pkg/unit" +) + +// IntlShippingAndLinehaulPricer is an autogenerated mock type for the IntlShippingAndLinehaulPricer type +type IntlShippingAndLinehaulPricer struct { + mock.Mock +} + +// Price provides a mock function with given fields: appCtx, contractCode, requestedPickupDate, distance, weight, perUnitCents +func (_m *IntlShippingAndLinehaulPricer) Price(appCtx appcontext.AppContext, contractCode string, requestedPickupDate time.Time, distance unit.Miles, weight unit.Pound, perUnitCents int) (unit.Cents, services.PricingDisplayParams, error) { + ret := _m.Called(appCtx, contractCode, requestedPickupDate, distance, weight, perUnitCents) + + if len(ret) == 0 { + panic("no return value specified for Price") + } + + var r0 unit.Cents + var r1 services.PricingDisplayParams + var r2 error + if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, time.Time, unit.Miles, unit.Pound, int) (unit.Cents, services.PricingDisplayParams, error)); ok { + return rf(appCtx, contractCode, requestedPickupDate, distance, weight, perUnitCents) + } + if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, time.Time, unit.Miles, unit.Pound, int) unit.Cents); ok { + r0 = rf(appCtx, contractCode, requestedPickupDate, distance, weight, perUnitCents) + } else { + r0 = ret.Get(0).(unit.Cents) + } + + if rf, ok := ret.Get(1).(func(appcontext.AppContext, string, time.Time, unit.Miles, unit.Pound, int) services.PricingDisplayParams); ok { + r1 = rf(appCtx, contractCode, requestedPickupDate, distance, weight, perUnitCents) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(services.PricingDisplayParams) + } + } + + if rf, ok := ret.Get(2).(func(appcontext.AppContext, string, time.Time, unit.Miles, unit.Pound, int) error); ok { + r2 = rf(appCtx, contractCode, requestedPickupDate, distance, weight, perUnitCents) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// PriceUsingParams provides a mock function with given fields: appCtx, params +func (_m *IntlShippingAndLinehaulPricer) PriceUsingParams(appCtx appcontext.AppContext, params models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error) { + ret := _m.Called(appCtx, params) + + if len(ret) == 0 { + panic("no return value specified for PriceUsingParams") + } + + var r0 unit.Cents + var r1 services.PricingDisplayParams + var r2 error + if rf, ok := ret.Get(0).(func(appcontext.AppContext, models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error)); ok { + return rf(appCtx, params) + } + if rf, ok := ret.Get(0).(func(appcontext.AppContext, models.PaymentServiceItemParams) unit.Cents); ok { + r0 = rf(appCtx, params) + } else { + r0 = ret.Get(0).(unit.Cents) + } + + if rf, ok := ret.Get(1).(func(appcontext.AppContext, models.PaymentServiceItemParams) services.PricingDisplayParams); ok { + r1 = rf(appCtx, params) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(services.PricingDisplayParams) + } + } + + if rf, ok := ret.Get(2).(func(appcontext.AppContext, models.PaymentServiceItemParams) error); ok { + r2 = rf(appCtx, params) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// NewIntlShippingAndLinehaulPricer creates a new instance of IntlShippingAndLinehaulPricer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewIntlShippingAndLinehaulPricer(t interface { + mock.TestingT + Cleanup(func()) +}) *IntlShippingAndLinehaulPricer { + mock := &IntlShippingAndLinehaulPricer{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} From 88cba28f91b0d613609c6028c8e90c9d85944f7d Mon Sep 17 00:00:00 2001 From: Daniel Jordan Date: Thu, 2 Jan 2025 22:17:22 +0000 Subject: [PATCH 40/54] tests on tests on tests --- .../intl_hhg_pack_pricer_test.go | 115 ++++++++++ .../intl_hhg_unpack_pricer_test.go | 114 ++++++++++ .../intl_port_fuel_surcharge_pricer_test.go | 209 ++++++++++++++++++ .../intl_shipping_and_linehaul_pricer.go | 2 +- .../intl_shipping_and_linehaul_pricer_test.go | 144 ++++++++++++ .../ghcrateengine/pricer_helpers_intl.go | 5 +- .../ghcrateengine/pricer_helpers_intl_test.go | 46 ++++ .../ghcrateengine/pricer_helpers_test.go | 2 +- .../ghcrateengine/pricer_query_helpers.go | 2 +- .../CreatePaymentRequestForm.jsx | 5 +- .../CreatePaymentRequestForm.test.jsx | 6 + 11 files changed, 645 insertions(+), 5 deletions(-) create mode 100644 pkg/services/ghcrateengine/intl_hhg_pack_pricer_test.go create mode 100644 pkg/services/ghcrateengine/intl_hhg_unpack_pricer_test.go create mode 100644 pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer_test.go create mode 100644 pkg/services/ghcrateengine/intl_shipping_and_linehaul_pricer_test.go create mode 100644 pkg/services/ghcrateengine/pricer_helpers_intl_test.go diff --git a/pkg/services/ghcrateengine/intl_hhg_pack_pricer_test.go b/pkg/services/ghcrateengine/intl_hhg_pack_pricer_test.go new file mode 100644 index 00000000000..9674775b906 --- /dev/null +++ b/pkg/services/ghcrateengine/intl_hhg_pack_pricer_test.go @@ -0,0 +1,115 @@ +package ghcrateengine + +import ( + "fmt" + "strconv" + "time" + + "github.com/transcom/mymove/pkg/factory" + "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/testdatagen" + "github.com/transcom/mymove/pkg/unit" +) + +const ( + ihpkTestContractYearName = "Base Period Year 1" + ihpkTestPerUnitCents = unit.Cents(15000) + ihpkTestTotalCost = unit.Cents(315000) + ihpkTestIsPeakPeriod = true + ihpkTestEscalationCompounded = 1.0000 + ihpkTestWeight = unit.Pound(2100) + ihpkTestPriceCents = unit.Cents(193064) +) + +var ihpkTestRequestedPickupDate = time.Date(testdatagen.TestYear, peakStart.month, peakStart.day, 0, 0, 0, 0, time.UTC) + +func (suite *GHCRateEngineServiceSuite) TestIntlHHGPackPricer() { + pricer := NewIntlHHGPackPricer() + + suite.Run("success using PaymentServiceItemParams", func() { + paymentServiceItem := suite.setupIntlPackServiceItem() + + totalCost, displayParams, err := pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.NoError(err) + suite.Equal(ihpkTestTotalCost, totalCost) + + expectedParams := services.PricingDisplayParams{ + {Key: models.ServiceItemParamNameContractYearName, Value: ihpkTestContractYearName}, + {Key: models.ServiceItemParamNameEscalationCompounded, Value: FormatEscalation(ihpkTestEscalationCompounded)}, + {Key: models.ServiceItemParamNameIsPeak, Value: FormatBool(ihpkTestIsPeakPeriod)}, + {Key: models.ServiceItemParamNamePriceRateOrFactor, Value: FormatCents(ihpkTestPerUnitCents)}, + } + suite.validatePricerCreatedParams(expectedParams, displayParams) + }) + + suite.Run("invalid parameters to PriceUsingParams", func() { + paymentServiceItem := suite.setupIntlPackServiceItem() + + // WeightBilled + paymentServiceItem.PaymentServiceItemParams[3].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean + _, _, err := pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.Error(err) + suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to an int", models.ServiceItemParamNameWeightBilled)) + + // PerUnitCents + paymentServiceItem.PaymentServiceItemParams[2].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean + _, _, err = pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.Error(err) + suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to an int", models.ServiceItemParamNamePerUnitCents)) + + // ReferenceDate + paymentServiceItem.PaymentServiceItemParams[1].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean + _, _, err = pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.Error(err) + suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to a time", models.ServiceItemParamNameReferenceDate)) + + // ContractCode + paymentServiceItem.PaymentServiceItemParams[0].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean + _, _, err = pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.Error(err) + suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to a string", models.ServiceItemParamNameContractCode)) + }) +} + +func (suite *GHCRateEngineServiceSuite) setupIntlPackServiceItem() models.PaymentServiceItem { + contract := testdatagen.FetchOrMakeReContract(suite.DB(), testdatagen.Assertions{}) + startDate := time.Date(2018, time.January, 1, 12, 0, 0, 0, time.UTC) + endDate := time.Date(2018, time.December, 31, 12, 0, 0, 0, time.UTC) + testdatagen.FetchOrMakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + Contract: contract, + ContractID: contract.ID, + StartDate: startDate, + EndDate: endDate, + Escalation: 1.0, + EscalationCompounded: 1.0, + }, + }) + return factory.BuildPaymentServiceItemWithParams( + suite.DB(), + models.ReServiceCodeIHPK, + []factory.CreatePaymentServiceItemParams{ + { + Key: models.ServiceItemParamNameContractCode, + KeyType: models.ServiceItemParamTypeString, + Value: contract.Code, + }, + { + Key: models.ServiceItemParamNameReferenceDate, + KeyType: models.ServiceItemParamTypeDate, + Value: ihpkTestRequestedPickupDate.Format(DateParamFormat), + }, + { + Key: models.ServiceItemParamNamePerUnitCents, + KeyType: models.ServiceItemParamTypeInteger, + Value: fmt.Sprintf("%d", int(ihpkTestPerUnitCents)), + }, + { + Key: models.ServiceItemParamNameWeightBilled, + KeyType: models.ServiceItemParamTypeInteger, + Value: strconv.Itoa(ihpkTestWeight.Int()), + }, + }, nil, nil, + ) +} diff --git a/pkg/services/ghcrateengine/intl_hhg_unpack_pricer_test.go b/pkg/services/ghcrateengine/intl_hhg_unpack_pricer_test.go new file mode 100644 index 00000000000..322027b37b5 --- /dev/null +++ b/pkg/services/ghcrateengine/intl_hhg_unpack_pricer_test.go @@ -0,0 +1,114 @@ +package ghcrateengine + +import ( + "fmt" + "strconv" + "time" + + "github.com/transcom/mymove/pkg/factory" + "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/testdatagen" + "github.com/transcom/mymove/pkg/unit" +) + +const ( + ihupkTestContractYearName = "Base Period Year 1" + ihupkTestPerUnitCents = unit.Cents(1200) + ihupkTestTotalCost = unit.Cents(25200) + ihupkTestIsPeakPeriod = true + ihupkTestEscalationCompounded = 1.0000 + ihpukTestWeight = unit.Pound(2100) +) + +var ihupkTestRequestedPickupDate = time.Date(testdatagen.TestYear, peakStart.month, peakStart.day, 0, 0, 0, 0, time.UTC) + +func (suite *GHCRateEngineServiceSuite) TestIntlHHGUnpackPricer() { + pricer := NewIntlHHGUnpackPricer() + + suite.Run("success using PaymentServiceItemParams", func() { + paymentServiceItem := suite.setupIntlUnpackServiceItem() + + totalCost, displayParams, err := pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.NoError(err) + suite.Equal(ihupkTestTotalCost, totalCost) + + expectedParams := services.PricingDisplayParams{ + {Key: models.ServiceItemParamNameContractYearName, Value: ihupkTestContractYearName}, + {Key: models.ServiceItemParamNameEscalationCompounded, Value: FormatEscalation(ihupkTestEscalationCompounded)}, + {Key: models.ServiceItemParamNameIsPeak, Value: FormatBool(ihupkTestIsPeakPeriod)}, + {Key: models.ServiceItemParamNamePriceRateOrFactor, Value: FormatCents(ihupkTestPerUnitCents)}, + } + suite.validatePricerCreatedParams(expectedParams, displayParams) + }) + + suite.Run("invalid parameters to PriceUsingParams", func() { + paymentServiceItem := suite.setupIntlPackServiceItem() + + // WeightBilled + paymentServiceItem.PaymentServiceItemParams[3].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean + _, _, err := pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.Error(err) + suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to an int", models.ServiceItemParamNameWeightBilled)) + + // PerUnitCents + paymentServiceItem.PaymentServiceItemParams[2].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean + _, _, err = pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.Error(err) + suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to an int", models.ServiceItemParamNamePerUnitCents)) + + // ReferenceDate + paymentServiceItem.PaymentServiceItemParams[1].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean + _, _, err = pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.Error(err) + suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to a time", models.ServiceItemParamNameReferenceDate)) + + // ContractCode + paymentServiceItem.PaymentServiceItemParams[0].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean + _, _, err = pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.Error(err) + suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to a string", models.ServiceItemParamNameContractCode)) + }) +} + +func (suite *GHCRateEngineServiceSuite) setupIntlUnpackServiceItem() models.PaymentServiceItem { + contract := testdatagen.FetchOrMakeReContract(suite.DB(), testdatagen.Assertions{}) + startDate := time.Date(2018, time.January, 1, 12, 0, 0, 0, time.UTC) + endDate := time.Date(2018, time.December, 31, 12, 0, 0, 0, time.UTC) + testdatagen.FetchOrMakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + Contract: contract, + ContractID: contract.ID, + StartDate: startDate, + EndDate: endDate, + Escalation: 1.0, + EscalationCompounded: 1.0, + }, + }) + return factory.BuildPaymentServiceItemWithParams( + suite.DB(), + models.ReServiceCodeIHUPK, + []factory.CreatePaymentServiceItemParams{ + { + Key: models.ServiceItemParamNameContractCode, + KeyType: models.ServiceItemParamTypeString, + Value: contract.Code, + }, + { + Key: models.ServiceItemParamNameReferenceDate, + KeyType: models.ServiceItemParamTypeDate, + Value: ihupkTestRequestedPickupDate.Format(DateParamFormat), + }, + { + Key: models.ServiceItemParamNamePerUnitCents, + KeyType: models.ServiceItemParamTypeInteger, + Value: fmt.Sprintf("%d", int(ihupkTestPerUnitCents)), + }, + { + Key: models.ServiceItemParamNameWeightBilled, + KeyType: models.ServiceItemParamTypeInteger, + Value: strconv.Itoa(ihpukTestWeight.Int()), + }, + }, nil, nil, + ) +} diff --git a/pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer_test.go b/pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer_test.go new file mode 100644 index 00000000000..9c07773bdb8 --- /dev/null +++ b/pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer_test.go @@ -0,0 +1,209 @@ +package ghcrateengine + +import ( + "fmt" + "time" + + "github.com/gofrs/uuid" + + "github.com/transcom/mymove/pkg/apperror" + "github.com/transcom/mymove/pkg/factory" + "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/testdatagen" + "github.com/transcom/mymove/pkg/unit" +) + +const ( + intlPortFscTestDistance = unit.Miles(2276) + intlPortFscTestWeight = unit.Pound(4025) + intlPortFscWeightDistanceMultiplier = float64(0.000417) + intlPortFscFuelPrice = unit.Millicents(281400) + intlPortFscPriceCents = unit.Cents(2980) + intlPortFscPortName = "PORTLAND INTL" +) + +var intlPortFscActualPickupDate = time.Date(testdatagen.TestYear, time.June, 5, 7, 33, 11, 456, time.UTC) + +func (suite *GHCRateEngineServiceSuite) TestIntlPortFuelSurchargePricer() { + intlPortFuelSurchargePricer := NewPortFuelSurchargePricer() + + intlPortFscPriceDifferenceInCents := (intlPortFscFuelPrice - baseGHCDieselFuelPrice).Float64() / 1000.0 + intlPortFscMultiplier := intlPortFscWeightDistanceMultiplier * intlPortFscTestDistance.Float64() + + suite.Run("success using PaymentServiceItemParams", func() { + paymentServiceItem := suite.setupPortFuelSurchargeServiceItem() + priceCents, displayParams, err := intlPortFuelSurchargePricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.NoError(err) + suite.Equal(intlPortFscPriceCents, priceCents) + + expectedParams := services.PricingDisplayParams{ + {Key: models.ServiceItemParamNameFSCPriceDifferenceInCents, Value: FormatFloat(intlPortFscPriceDifferenceInCents, 1)}, + {Key: models.ServiceItemParamNameFSCMultiplier, Value: FormatFloat(intlPortFscMultiplier, 7)}, + } + suite.validatePricerCreatedParams(expectedParams, displayParams) + }) + + suite.Run("success without PaymentServiceItemParams", func() { + priceCents, _, err := intlPortFuelSurchargePricer.Price(suite.AppContextForTest(), intlPortFscActualPickupDate, intlPortFscTestDistance, intlPortFscTestWeight, intlPortFscWeightDistanceMultiplier, intlPortFscFuelPrice, intlPortFscPortName) + suite.NoError(err) + suite.Equal(intlPortFscPriceCents, priceCents) + }) + + suite.Run("sending PaymentServiceItemParams without expected param", func() { + _, _, err := intlPortFuelSurchargePricer.PriceUsingParams(suite.AppContextForTest(), models.PaymentServiceItemParams{}) + suite.Error(err) + }) + + suite.Run("fails using PaymentServiceItemParams with below minimum weight for WeightBilled", func() { + paymentServiceItem := suite.setupPortFuelSurchargeServiceItem() + paramsWithBelowMinimumWeight := paymentServiceItem.PaymentServiceItemParams + weightBilledIndex := 2 + if paramsWithBelowMinimumWeight[weightBilledIndex].ServiceItemParamKey.Key != models.ServiceItemParamNameWeightBilled { + suite.FailNow("failed", "Test needs to adjust the weight of %s but the index is pointing to %s ", models.ServiceItemParamNameWeightBilled, paramsWithBelowMinimumWeight[4].ServiceItemParamKey.Key) + } + paramsWithBelowMinimumWeight[weightBilledIndex].Value = "200" + priceCents, _, err := intlPortFuelSurchargePricer.PriceUsingParams(suite.AppContextForTest(), paramsWithBelowMinimumWeight) + if suite.Error(err) { + suite.Equal("weight must be a minimum of 500", err.Error()) + suite.Equal(unit.Cents(0), priceCents) + } + }) + + suite.Run("FSC is negative if fuel price from EIA is below $2.50", func() { + priceCents, _, err := intlPortFuelSurchargePricer.Price(suite.AppContextForTest(), intlPortFscActualPickupDate, intlPortFscTestDistance, intlPortFscTestWeight, intlPortFscWeightDistanceMultiplier, 242400, intlPortFscPortName) + suite.NoError(err) + suite.Equal(unit.Cents(-721), priceCents) + }) + + suite.Run("Price validation errors", func() { + + // No actual pickup date + _, _, err := intlPortFuelSurchargePricer.Price(suite.AppContextForTest(), time.Time{}, intlPortFscTestDistance, intlPortFscTestWeight, intlPortFscWeightDistanceMultiplier, intlPortFscFuelPrice, intlPortFscPortName) + suite.Error(err) + suite.Equal("ActualPickupDate is required", err.Error()) + + // No distance + _, _, err = intlPortFuelSurchargePricer.Price(suite.AppContextForTest(), intlPortFscActualPickupDate, unit.Miles(0), intlPortFscTestWeight, intlPortFscWeightDistanceMultiplier, intlPortFscFuelPrice, intlPortFscPortName) + suite.Error(err) + suite.Equal("Distance must be greater than 0", err.Error()) + + // No weight + _, _, err = intlPortFuelSurchargePricer.Price(suite.AppContextForTest(), intlPortFscActualPickupDate, intlPortFscTestDistance, unit.Pound(0), intlPortFscWeightDistanceMultiplier, intlPortFscFuelPrice, intlPortFscPortName) + suite.Error(err) + suite.Equal(fmt.Sprintf("weight must be a minimum of %d", minDomesticWeight), err.Error()) + + // No weight based distance multiplier + _, _, err = intlPortFuelSurchargePricer.Price(suite.AppContextForTest(), intlPortFscActualPickupDate, intlPortFscTestDistance, intlPortFscTestWeight, 0, intlPortFscFuelPrice, intlPortFscPortName) + suite.Error(err) + suite.Equal("WeightBasedDistanceMultiplier is required", err.Error()) + + // No EIA fuel price + _, _, err = intlPortFuelSurchargePricer.Price(suite.AppContextForTest(), intlPortFscActualPickupDate, intlPortFscTestDistance, intlPortFscTestWeight, intlPortFscWeightDistanceMultiplier, 0, intlPortFscPortName) + suite.Error(err) + suite.Equal("EIAFuelPrice is required", err.Error()) + }) + + suite.Run("PriceUsingParams validation errors", func() { + paymentServiceItem := suite.setupPortFuelSurchargeServiceItem() + paramsWithBelowMinimumWeight := paymentServiceItem.PaymentServiceItemParams + weightBilledIndex := 2 + if paramsWithBelowMinimumWeight[weightBilledIndex].ServiceItemParamKey.Key != models.ServiceItemParamNameWeightBilled { + suite.FailNow("failed", "Test needs to adjust the weight of %s but the index is pointing to %s ", models.ServiceItemParamNameWeightBilled, paramsWithBelowMinimumWeight[4].ServiceItemParamKey.Key) + } + paramsWithBelowMinimumWeight[weightBilledIndex].Value = "200" + + // No ActualPickupDate + missingActualPickupDate := suite.removeOnePaymentServiceItem(paymentServiceItem.PaymentServiceItemParams, models.ServiceItemParamNameActualPickupDate) + _, _, err := intlPortFuelSurchargePricer.PriceUsingParams(suite.AppContextForTest(), missingActualPickupDate) + suite.Error(err) + suite.Equal("could not find param with key ActualPickupDate", err.Error()) + + // No WeightBilled + missingWeightBilled := suite.removeOnePaymentServiceItem(paymentServiceItem.PaymentServiceItemParams, models.ServiceItemParamNameWeightBilled) + _, _, err = intlPortFuelSurchargePricer.PriceUsingParams(suite.AppContextForTest(), missingWeightBilled) + suite.Error(err) + suite.Equal("could not find param with key WeightBilled", err.Error()) + + // No FSCWeightBasedDistanceMultiplier + missingFSCWeightBasedDistanceMultiplier := suite.removeOnePaymentServiceItem(paymentServiceItem.PaymentServiceItemParams, models.ServiceItemParamNameFSCWeightBasedDistanceMultiplier) + _, _, err = intlPortFuelSurchargePricer.PriceUsingParams(suite.AppContextForTest(), missingFSCWeightBasedDistanceMultiplier) + suite.Error(err) + suite.Equal("could not find param with key FSCWeightBasedDistanceMultiplier", err.Error()) + + // No EIAFuelPrice + missingEIAFuelPrice := suite.removeOnePaymentServiceItem(paymentServiceItem.PaymentServiceItemParams, models.ServiceItemParamNameEIAFuelPrice) + _, _, err = intlPortFuelSurchargePricer.PriceUsingParams(suite.AppContextForTest(), missingEIAFuelPrice) + suite.Error(err) + suite.Equal("could not find param with key EIAFuelPrice", err.Error()) + }) + + suite.Run("can't find distance", func() { + paymentServiceItem := suite.setupPortFuelSurchargeServiceItem() + paramsWithBelowMinimumWeight := paymentServiceItem.PaymentServiceItemParams + weightBilledIndex := 2 + if paramsWithBelowMinimumWeight[weightBilledIndex].ServiceItemParamKey.Key != models.ServiceItemParamNameWeightBilled { + suite.FailNow("failed", "Test needs to adjust the weight of %s but the index is pointing to %s ", models.ServiceItemParamNameWeightBilled, paramsWithBelowMinimumWeight[4].ServiceItemParamKey.Key) + } + paramsWithBelowMinimumWeight[weightBilledIndex].Value = "200" + + paramsWithBadReference := paymentServiceItem.PaymentServiceItemParams + paramsWithBadReference[0].PaymentServiceItemID = uuid.Nil + _, _, err := intlPortFuelSurchargePricer.PriceUsingParams(suite.AppContextForTest(), paramsWithBadReference) + suite.Error(err) + suite.IsType(apperror.NotFoundError{}, err) + }) +} + +func (suite *GHCRateEngineServiceSuite) setupPortFuelSurchargeServiceItem() models.PaymentServiceItem { + model := factory.BuildPaymentServiceItemWithParams( + suite.DB(), + models.ReServiceCodePOEFSC, + []factory.CreatePaymentServiceItemParams{ + { + Key: models.ServiceItemParamNameActualPickupDate, + KeyType: models.ServiceItemParamTypeDate, + Value: intlPortFscActualPickupDate.Format(DateParamFormat), + }, + { + Key: models.ServiceItemParamNameDistanceZip, + KeyType: models.ServiceItemParamTypeInteger, + Value: fmt.Sprintf("%d", int(intlPortFscTestDistance)), + }, + { + Key: models.ServiceItemParamNameWeightBilled, + KeyType: models.ServiceItemParamTypeInteger, + Value: fmt.Sprintf("%d", int(intlPortFscTestWeight)), + }, + { + Key: models.ServiceItemParamNameFSCWeightBasedDistanceMultiplier, + KeyType: models.ServiceItemParamTypeDecimal, + Value: fmt.Sprintf("%.7f", intlPortFscWeightDistanceMultiplier), + }, + { + Key: models.ServiceItemParamNameEIAFuelPrice, + KeyType: models.ServiceItemParamTypeInteger, + Value: fmt.Sprintf("%d", int(intlPortFscFuelPrice)), + }, + { + Key: models.ServiceItemParamNamePortName, + KeyType: models.ServiceItemParamTypeString, + Value: intlPortFscPortName, + }, + }, nil, nil, + ) + + var mtoServiceItem models.MTOServiceItem + err := suite.DB().Eager("MTOShipment").Find(&mtoServiceItem, model.MTOServiceItemID) + suite.NoError(err) + + distance := intlPortFscTestDistance + mtoServiceItem.MTOShipment.Distance = &distance + err = suite.DB().Save(&mtoServiceItem.MTOShipment) + suite.NoError(err) + + // the testdatagen factory has some dirty shipment data that we don't want to pass through to the pricer in the test + model.PaymentServiceItemParams[0].PaymentServiceItem.MTOServiceItem = models.MTOServiceItem{} + + return model +} diff --git a/pkg/services/ghcrateengine/intl_shipping_and_linehaul_pricer.go b/pkg/services/ghcrateengine/intl_shipping_and_linehaul_pricer.go index ca0624d75a2..3d0ea35c3ba 100644 --- a/pkg/services/ghcrateengine/intl_shipping_and_linehaul_pricer.go +++ b/pkg/services/ghcrateengine/intl_shipping_and_linehaul_pricer.go @@ -38,7 +38,7 @@ func (p intlShippingAndLinehaulPricer) Price(appCtx appcontext.AppContext, contr isPeakPeriod := IsPeakPeriod(referenceDate) - contract, err := fetchContractsByContractCode(appCtx, contractCode) + contract, err := fetchContractByContractCode(appCtx, contractCode) if err != nil { return 0, nil, fmt.Errorf("could not find contract with code: %s: %w", contractCode, err) } diff --git a/pkg/services/ghcrateengine/intl_shipping_and_linehaul_pricer_test.go b/pkg/services/ghcrateengine/intl_shipping_and_linehaul_pricer_test.go new file mode 100644 index 00000000000..ef3407b04f0 --- /dev/null +++ b/pkg/services/ghcrateengine/intl_shipping_and_linehaul_pricer_test.go @@ -0,0 +1,144 @@ +package ghcrateengine + +import ( + "fmt" + "strconv" + "time" + + "github.com/transcom/mymove/pkg/factory" + "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/testdatagen" + "github.com/transcom/mymove/pkg/unit" +) + +const ( + islhTestContractYearName = "Base Period Year 1" + islhTestPerUnitCents = unit.Cents(15000) + islhTestTotalCost = unit.Cents(315000) + islhTestIsPeakPeriod = true + islhTestEscalationCompounded = 1.0000 + islhTestWeight = unit.Pound(2100) + islhTestDistance = unit.Miles(1201) +) + +var islhTestRequestedPickupDate = time.Date(testdatagen.TestYear, peakStart.month, peakStart.day, 0, 0, 0, 0, time.UTC) + +func (suite *GHCRateEngineServiceSuite) TestIntlShippingAndLinehaulPricer() { + pricer := NewIntlShippingAndLinehaulPricer() + + suite.Run("success using PaymentServiceItemParams", func() { + paymentServiceItem := suite.setupIntlShippingAndLinehaulServiceItem() + + totalCost, displayParams, err := pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.NoError(err) + suite.Equal(islhTestTotalCost, totalCost) + + expectedParams := services.PricingDisplayParams{ + {Key: models.ServiceItemParamNameContractYearName, Value: islhTestContractYearName}, + {Key: models.ServiceItemParamNameEscalationCompounded, Value: FormatEscalation(islhTestEscalationCompounded)}, + {Key: models.ServiceItemParamNameIsPeak, Value: FormatBool(islhTestIsPeakPeriod)}, + {Key: models.ServiceItemParamNamePriceRateOrFactor, Value: FormatCents(islhTestPerUnitCents)}, + } + suite.validatePricerCreatedParams(expectedParams, displayParams) + }) + + suite.Run("invalid parameters to PriceUsingParams", func() { + paymentServiceItem := suite.setupIntlShippingAndLinehaulServiceItem() + + // PerUnitCents + paymentServiceItem.PaymentServiceItemParams[3].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean + _, _, err := pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.Error(err) + suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to an int", models.ServiceItemParamNamePerUnitCents)) + + // ReferenceDate + paymentServiceItem.PaymentServiceItemParams[2].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean + _, _, err = pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.Error(err) + suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to a time", models.ServiceItemParamNameReferenceDate)) + + // DistanceZip + paymentServiceItem.PaymentServiceItemParams[1].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean + _, _, err = pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.Error(err) + suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to an int", models.ServiceItemParamNameDistanceZip)) + + // ContractCode + paymentServiceItem.PaymentServiceItemParams[0].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean + _, _, err = pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.Error(err) + suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to a string", models.ServiceItemParamNameContractCode)) + }) + + suite.Run("Price validation errors", func() { + + // No contract code + _, _, err := pricer.Price(suite.AppContextForTest(), "", islhTestRequestedPickupDate, islhTestDistance, islhTestWeight, islhTestPerUnitCents.Int()) + suite.Error(err) + suite.Equal("ContractCode is required", err.Error()) + + // No reference date + _, _, err = pricer.Price(suite.AppContextForTest(), testdatagen.DefaultContractCode, time.Time{}, islhTestDistance, islhTestWeight, islhTestPerUnitCents.Int()) + suite.Error(err) + suite.Equal("referenceDate is required", err.Error()) + + // No weight + _, _, err = pricer.Price(suite.AppContextForTest(), testdatagen.DefaultContractCode, islhTestRequestedPickupDate, islhTestDistance, 0, islhTestPerUnitCents.Int()) + suite.Error(err) + suite.Equal(fmt.Sprintf("weight must be at least %d", minIntlWeightHHG), err.Error()) + + // No per unit cents + _, _, err = pricer.Price(suite.AppContextForTest(), testdatagen.DefaultContractCode, islhTestRequestedPickupDate, islhTestDistance, islhTestWeight, 0) + suite.Error(err) + suite.Equal("PerUnitCents is required", err.Error()) + + }) +} + +func (suite *GHCRateEngineServiceSuite) setupIntlShippingAndLinehaulServiceItem() models.PaymentServiceItem { + contract := testdatagen.FetchOrMakeReContract(suite.DB(), testdatagen.Assertions{}) + startDate := time.Date(2018, time.January, 1, 12, 0, 0, 0, time.UTC) + endDate := time.Date(2018, time.December, 31, 12, 0, 0, 0, time.UTC) + testdatagen.FetchOrMakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + Contract: contract, + ContractID: contract.ID, + StartDate: startDate, + EndDate: endDate, + Escalation: 1.0, + EscalationCompounded: 1.0, + }, + }) + return factory.BuildPaymentServiceItemWithParams( + suite.DB(), + models.ReServiceCodeISLH, + []factory.CreatePaymentServiceItemParams{ + { + Key: models.ServiceItemParamNameContractCode, + KeyType: models.ServiceItemParamTypeString, + Value: contract.Code, + }, + { + Key: models.ServiceItemParamNameDistanceZip, + KeyType: models.ServiceItemParamTypeInteger, + Value: fmt.Sprintf("%d", int(islhTestDistance)), + }, + { + Key: models.ServiceItemParamNameReferenceDate, + KeyType: models.ServiceItemParamTypeDate, + Value: islhTestRequestedPickupDate.Format(DateParamFormat), + }, + { + Key: models.ServiceItemParamNamePerUnitCents, + KeyType: models.ServiceItemParamTypeInteger, + Value: fmt.Sprintf("%d", int(islhTestPerUnitCents)), + }, + { + Key: models.ServiceItemParamNameWeightBilled, + KeyType: models.ServiceItemParamTypeInteger, + Value: strconv.Itoa(islhTestWeight.Int()), + }, + }, nil, nil, + ) +} diff --git a/pkg/services/ghcrateengine/pricer_helpers_intl.go b/pkg/services/ghcrateengine/pricer_helpers_intl.go index 37f01ee9966..924dad55537 100644 --- a/pkg/services/ghcrateengine/pricer_helpers_intl.go +++ b/pkg/services/ghcrateengine/pricer_helpers_intl.go @@ -23,10 +23,13 @@ func priceIntlPackUnpack(appCtx appcontext.AppContext, packUnpackCode models.ReS if referenceDate.IsZero() { return 0, nil, errors.New("ReferenceDate is required") } + if perUnitCents == 0 { + return 0, nil, errors.New("PerUnitCents is required") + } isPeakPeriod := IsPeakPeriod(referenceDate) - contract, err := fetchContractsByContractCode(appCtx, contractCode) + contract, err := fetchContractByContractCode(appCtx, contractCode) if err != nil { return 0, nil, fmt.Errorf("could not find contract with code: %s: %w", contractCode, err) } diff --git a/pkg/services/ghcrateengine/pricer_helpers_intl_test.go b/pkg/services/ghcrateengine/pricer_helpers_intl_test.go new file mode 100644 index 00000000000..19539e4c976 --- /dev/null +++ b/pkg/services/ghcrateengine/pricer_helpers_intl_test.go @@ -0,0 +1,46 @@ +package ghcrateengine + +import ( + "time" + + "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/testdatagen" +) + +func (suite *GHCRateEngineServiceSuite) TestPriceIntlPackUnpack() { + suite.Run("success with IHPK", func() { + suite.setupIntlPackServiceItem() + totalCost, displayParams, err := priceIntlPackUnpack(suite.AppContextForTest(), models.ReServiceCodeIHPK, testdatagen.DefaultContractCode, ihpkTestRequestedPickupDate, ihpkTestWeight, ihpkTestPerUnitCents.Int()) + suite.NoError(err) + suite.Equal(ihpkTestTotalCost, totalCost) + + expectedParams := services.PricingDisplayParams{ + {Key: models.ServiceItemParamNameContractYearName, Value: ihpkTestContractYearName}, + {Key: models.ServiceItemParamNameEscalationCompounded, Value: FormatEscalation(ihpkTestEscalationCompounded)}, + {Key: models.ServiceItemParamNameIsPeak, Value: FormatBool(ihpkTestIsPeakPeriod)}, + {Key: models.ServiceItemParamNamePriceRateOrFactor, Value: FormatCents(ihpkTestPerUnitCents)}, + } + suite.validatePricerCreatedParams(expectedParams, displayParams) + }) + + suite.Run("Invalid parameters to Price", func() { + suite.setupIntlPackServiceItem() + _, _, err := priceIntlPackUnpack(suite.AppContextForTest(), models.ReServiceCodeDLH, testdatagen.DefaultContractCode, ihpkTestRequestedPickupDate, ihpkTestWeight, ihpkTestPerUnitCents.Int()) + suite.Error(err) + suite.Contains(err.Error(), "unsupported pack/unpack code") + + _, _, err = priceIntlPackUnpack(suite.AppContextForTest(), models.ReServiceCodeIHPK, "", ihpkTestRequestedPickupDate, ihpkTestWeight, ihpkTestPerUnitCents.Int()) + suite.Error(err) + suite.Contains(err.Error(), "ContractCode is required") + + _, _, err = priceIntlPackUnpack(suite.AppContextForTest(), models.ReServiceCodeIHPK, testdatagen.DefaultContractCode, time.Time{}, ihpkTestWeight, ihpkTestPerUnitCents.Int()) + suite.Error(err) + suite.Contains(err.Error(), "ReferenceDate is required") + + _, _, err = priceIntlPackUnpack(suite.AppContextForTest(), models.ReServiceCodeIHPK, testdatagen.DefaultContractCode, ihpkTestRequestedPickupDate, ihpkTestWeight, 0) + suite.Error(err) + suite.Contains(err.Error(), "PerUnitCents is required") + }) + +} diff --git a/pkg/services/ghcrateengine/pricer_helpers_test.go b/pkg/services/ghcrateengine/pricer_helpers_test.go index be3648927e2..06b9ec30044 100644 --- a/pkg/services/ghcrateengine/pricer_helpers_test.go +++ b/pkg/services/ghcrateengine/pricer_helpers_test.go @@ -529,7 +529,7 @@ func (suite *GHCRateEngineServiceSuite) Test_createPricerGeneratedParams() { _, err := createPricerGeneratedParams(suite.AppContextForTest(), subtestData.paymentServiceItem.ID, invalidParam) suite.Error(err) - suite.Contains(err.Error(), "Service item param key is not a pricer param") + suite.Contains(err.Error(), "service item param key is not a pricer param") }) suite.Run("errors if no PricingParms passed from the Pricer", func() { diff --git a/pkg/services/ghcrateengine/pricer_query_helpers.go b/pkg/services/ghcrateengine/pricer_query_helpers.go index 51acb06f9bd..84cde4fc64c 100644 --- a/pkg/services/ghcrateengine/pricer_query_helpers.go +++ b/pkg/services/ghcrateengine/pricer_query_helpers.go @@ -103,7 +103,7 @@ func fetchContractsByContractId(appCtx appcontext.AppContext, contractID uuid.UU return contracts, nil } -func fetchContractsByContractCode(appCtx appcontext.AppContext, contractCode string) (models.ReContract, error) { +func fetchContractByContractCode(appCtx appcontext.AppContext, contractCode string) (models.ReContract, error) { var contract models.ReContract err := appCtx.DB().Where("code = $1", contractCode).First(&contract) if err != nil { diff --git a/src/components/PrimeUI/CreatePaymentRequestForm/CreatePaymentRequestForm.jsx b/src/components/PrimeUI/CreatePaymentRequestForm/CreatePaymentRequestForm.jsx index 5aa3fdc0187..410e65506f1 100644 --- a/src/components/PrimeUI/CreatePaymentRequestForm/CreatePaymentRequestForm.jsx +++ b/src/components/PrimeUI/CreatePaymentRequestForm/CreatePaymentRequestForm.jsx @@ -145,7 +145,10 @@ const CreatePaymentRequestForm = ({ mtoServiceItem.reServiceCode === 'DOSFSC' || mtoServiceItem.reServiceCode === 'DDSHUT' || mtoServiceItem.reServiceCode === 'IHPK' || - mtoServiceItem.reServiceCode === 'IHUPK') && ( + mtoServiceItem.reServiceCode === 'IHUPK' || + mtoServiceItem.reServiceCode === 'ISLH' || + mtoServiceItem.reServiceCode === 'POEFSC' || + mtoServiceItem.reServiceCode === 'PODFSC') && ( { { id: '6', reServiceCode: 'DDFSIT', reServiceName: 'Domestic destination 1st day SIT' }, ], 2: [{ id: '5', reServiceCode: 'FSC' }], + 3: [ + { id: '7', reServiceCode: 'IHPK' }, + { id: '8', reServiceCode: 'IHUPK' }, + { id: '8', reServiceCode: 'ISLH' }, + { id: '8', reServiceCode: 'POEFSC' }, + ], }; it('renders the form', async () => { From c82e97c82e93477123ccaaa649cd42ee1956d85b Mon Sep 17 00:00:00 2001 From: Daniel Jordan Date: Thu, 2 Jan 2025 22:28:29 +0000 Subject: [PATCH 41/54] updating address facotry to handle bogus postal code values when getting post region city ids --- pkg/factory/address_factory.go | 40 +++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/pkg/factory/address_factory.go b/pkg/factory/address_factory.go index d6b7dff6ce5..1ef93086ad8 100644 --- a/pkg/factory/address_factory.go +++ b/pkg/factory/address_factory.go @@ -1,7 +1,10 @@ package factory import ( + "database/sql" + "github.com/gobuffalo/pop/v6" + "github.com/gofrs/uuid" "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/testdatagen" @@ -24,15 +27,17 @@ func BuildAddress(db *pop.Connection, customs []Customization, traits []Trait) m } // Create default Address + beverlyHillsUsprc := uuid.FromStringOrNil("3b9f0ae6-3b2b-44a6-9fcd-8ead346648c4") address := models.Address{ - StreetAddress1: "123 Any Street", - StreetAddress2: models.StringPointer("P.O. Box 12345"), - StreetAddress3: models.StringPointer("c/o Some Person"), - City: "Beverly Hills", - State: "CA", - PostalCode: "90210", - County: models.StringPointer("LOS ANGELES"), - IsOconus: models.BoolPointer(false), + StreetAddress1: "123 Any Street", + StreetAddress2: models.StringPointer("P.O. Box 12345"), + StreetAddress3: models.StringPointer("c/o Some Person"), + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + County: models.StringPointer("LOS ANGELES"), + IsOconus: models.BoolPointer(false), + UsPostRegionCityID: &beverlyHillsUsprc, } // Find/create the Country if customization is provided @@ -56,7 +61,7 @@ func BuildAddress(db *pop.Connection, customs []Customization, traits []Trait) m // Overwrite values with those from customizations testdatagen.MergeModels(&address, cAddress) - // This helps assign counties when the factory is called for seed data or tests + // This helps assign counties & us_post_region_cities_id values when the factory is called for seed data or tests // Additionally, also only run if not 90210. 90210's county is by default populated if db != nil && address.PostalCode != "90210" { county, err := models.FindCountyByZipCode(db, address.PostalCode) @@ -67,19 +72,18 @@ func BuildAddress(db *pop.Connection, customs []Customization, traits []Trait) m // The zip code successfully found a county address.County = county } - } else if db == nil && address.PostalCode != "90210" { - // If no db supplied, mark that - address.County = models.StringPointer("db nil when created") - } - - if db != nil { + // seeing if the postal code provided has a related us_post_region_cities_id usprc, err := models.FindByZipCode(db, address.PostalCode) - if err != nil { - return models.Address{} - } else { + if err != nil && err != sql.ErrNoRows { + address.UsPostRegionCityID = nil + address.UsPostRegionCity = nil + } else if usprc.ID != uuid.Nil { address.UsPostRegionCityID = &usprc.ID address.UsPostRegionCity = usprc } + } else if db == nil && address.PostalCode != "90210" { + // If no db supplied, mark that + address.County = models.StringPointer("db nil when created") } // If db is false, it's a stub. No need to create in database. From 5c81091f67693ad0c2199595f24b3cddfc9f82c9 Mon Sep 17 00:00:00 2001 From: Daniel Jordan Date: Fri, 3 Jan 2025 00:06:46 +0000 Subject: [PATCH 42/54] fixing some issues with tests and address factory --- pkg/factory/address_factory.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pkg/factory/address_factory.go b/pkg/factory/address_factory.go index 1ef93086ad8..27d92999d00 100644 --- a/pkg/factory/address_factory.go +++ b/pkg/factory/address_factory.go @@ -72,7 +72,12 @@ func BuildAddress(db *pop.Connection, customs []Customization, traits []Trait) m // The zip code successfully found a county address.County = county } - // seeing if the postal code provided has a related us_post_region_cities_id + } else if db == nil && address.PostalCode != "90210" { + // If no db supplied, mark that + address.County = models.StringPointer("db nil when created") + } + + if db != nil && address.PostalCode != "90210" && cAddress.UsPostRegionCityID == nil { usprc, err := models.FindByZipCode(db, address.PostalCode) if err != nil && err != sql.ErrNoRows { address.UsPostRegionCityID = nil @@ -81,9 +86,6 @@ func BuildAddress(db *pop.Connection, customs []Customization, traits []Trait) m address.UsPostRegionCityID = &usprc.ID address.UsPostRegionCity = usprc } - } else if db == nil && address.PostalCode != "90210" { - // If no db supplied, mark that - address.County = models.StringPointer("db nil when created") } // If db is false, it's a stub. No need to create in database. From f39bfa372574e0e052ba52f32191c30c7a6c1740 Mon Sep 17 00:00:00 2001 From: Daniel Jordan Date: Fri, 3 Jan 2025 19:19:22 +0000 Subject: [PATCH 43/54] PR fix feedback --- ...aram_values_to_service_params_table.up.sql | 3 ++ .../per_unit_cents_lookup.go | 3 ++ .../per_unit_cents_lookup_test.go | 31 +++++++++++++ .../port_name_lookup.go | 2 +- .../port_name_lookup_test.go | 31 ++++++++++++- pkg/services/ghc_rate_engine.go | 2 +- .../intl_port_fuel_surcharge_pricer.go | 9 ++-- .../intl_port_fuel_surcharge_pricer_test.go | 14 +++--- .../CreatePaymentRequestForm.jsx | 43 ++++++++++--------- src/constants/serviceItems.js | 3 ++ 10 files changed, 103 insertions(+), 38 deletions(-) diff --git a/migrations/app/schema/20241226173330_add_intl_param_values_to_service_params_table.up.sql b/migrations/app/schema/20241226173330_add_intl_param_values_to_service_params_table.up.sql index 63c1e404e50..859bbcb47b6 100644 --- a/migrations/app/schema/20241226173330_add_intl_param_values_to_service_params_table.up.sql +++ b/migrations/app/schema/20241226173330_add_intl_param_values_to_service_params_table.up.sql @@ -1,3 +1,6 @@ +-- dropping function that is not needed anymore +DROP FUNCTION IF EXISTS get_rate_area_id(UUID, UUID); + -- need to add in param keys for international shipments, this will be used to show breakdowns to the TIO INSERT INTO service_item_param_keys (id, key,description,type,origin,created_at,updated_at) VALUES ('d9ad3878-4b94-4722-bbaf-d4b8080f339d','PortName','Name of the port for an international shipment','STRING','SYSTEM','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957'), diff --git a/pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup.go b/pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup.go index d2062218620..b339fbf43dd 100644 --- a/pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup.go +++ b/pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup.go @@ -17,6 +17,9 @@ type PerUnitCentsLookup struct { func (p PerUnitCentsLookup) lookup(appCtx appcontext.AppContext, s *ServiceItemParamKeyData) (string, error) { serviceID := p.ServiceItem.ReServiceID contractID := s.ContractID + if p.MTOShipment.RequestedPickupDate == nil { + return "", fmt.Errorf("requested pickup date is required for shipment with id: %s", p.MTOShipment.ID) + } switch p.ServiceItem.ReService.Code { case models.ReServiceCodeIHPK: diff --git a/pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup_test.go b/pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup_test.go index 4d9ceab2512..9937f86217b 100644 --- a/pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup_test.go +++ b/pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup_test.go @@ -129,4 +129,35 @@ func (suite *ServiceParamValueLookupsSuite) TestPerUnitCentsLookup() { suite.Error(err) suite.Equal(perUnitCents, "") }) + + suite.Run("failure - no requested pickup date on shipment", func() { + testdatagen.MakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + StartDate: time.Now().Add(-24 * time.Hour), + EndDate: time.Now().Add(24 * time.Hour), + }, + }) + mtoServiceItem = factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: models.ReService{ + Code: models.ReServiceCodeIHPK, + }, + }, + { + Model: models.MTOShipment{ + RequestedPickupDate: nil, + }, + }, + }, []factory.Trait{factory.GetTraitAvailableToPrimeMove}) + + mtoServiceItem.MTOShipment.RequestedPickupDate = nil + suite.MustSave(&mtoServiceItem.MTOShipment) + + paramLookup, err := ServiceParamLookupInitialize(suite.AppContextForTest(), suite.planner, mtoServiceItem, uuid.Must(uuid.NewV4()), mtoServiceItem.MoveTaskOrderID, nil) + suite.FatalNoError(err) + + perUnitCents, err := paramLookup.ServiceParamValue(suite.AppContextForTest(), key) + suite.Error(err) + suite.Equal(perUnitCents, "") + }) } diff --git a/pkg/payment_request/service_param_value_lookups/port_name_lookup.go b/pkg/payment_request/service_param_value_lookups/port_name_lookup.go index 5013d3ae2c8..a925ceaa099 100644 --- a/pkg/payment_request/service_param_value_lookups/port_name_lookup.go +++ b/pkg/payment_request/service_param_value_lookups/port_name_lookup.go @@ -21,7 +21,7 @@ func (p PortNameLookup) lookup(appCtx appcontext.AppContext, _ *ServiceItemParam } else if p.ServiceItem.POELocationID != nil { portLocationID = p.ServiceItem.POELocationID } else { - return "", nil + return "", fmt.Errorf("unable to find port location for service item id: %s", p.ServiceItem.ID) } var portLocation models.PortLocation err := appCtx.DB().Q(). diff --git a/pkg/payment_request/service_param_value_lookups/port_name_lookup_test.go b/pkg/payment_request/service_param_value_lookups/port_name_lookup_test.go index b0ccb4b2bfe..a16b174dc1c 100644 --- a/pkg/payment_request/service_param_value_lookups/port_name_lookup_test.go +++ b/pkg/payment_request/service_param_value_lookups/port_name_lookup_test.go @@ -64,7 +64,7 @@ func (suite *ServiceParamValueLookupsSuite) TestPortNameLookup() { portName, err := paramLookup.ServiceParamValue(suite.AppContextForTest(), key) suite.FatalNoError(err) - suite.Equal(portName, "SEATTLE TACOMA INTL") + suite.Equal(portName, port.Port.PortName) }) suite.Run("success - returns PortName value for PODFSC", func() { @@ -82,6 +82,33 @@ func (suite *ServiceParamValueLookupsSuite) TestPortNameLookup() { portName, err := paramLookup.ServiceParamValue(suite.AppContextForTest(), key) suite.FatalNoError(err) - suite.Equal(portName, "PORTLAND INTL") + suite.Equal(portName, port.Port.PortName) + }) + + suite.Run("failure - no port value on service item", func() { + testdatagen.MakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + StartDate: time.Now().Add(-24 * time.Hour), + EndDate: time.Now().Add(24 * time.Hour), + }, + }) + mtoServiceItem = factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: models.ReService{ + Code: models.ReServiceCodePOEFSC, + }, + }, + { + Model: models.MTOServiceItem{ + POELocationID: nil, + }, + }, + }, []factory.Trait{factory.GetTraitAvailableToPrimeMove}) + + paramLookup, err := ServiceParamLookupInitialize(suite.AppContextForTest(), suite.planner, mtoServiceItem, uuid.Must(uuid.NewV4()), mtoServiceItem.MoveTaskOrderID, nil) + suite.FatalNoError(err) + + _, err = paramLookup.ServiceParamValue(suite.AppContextForTest(), key) + suite.Error(err) }) } diff --git a/pkg/services/ghc_rate_engine.go b/pkg/services/ghc_rate_engine.go index 8b3ef07de0f..2247e3d7426 100644 --- a/pkg/services/ghc_rate_engine.go +++ b/pkg/services/ghc_rate_engine.go @@ -261,6 +261,6 @@ type IntlHHGUnpackPricer interface { // //go:generate mockery --name IntlPortFuelSurchargePricer type IntlPortFuelSurchargePricer interface { - Price(appCtx appcontext.AppContext, actualPickupDate time.Time, distance unit.Miles, weight unit.Pound, fscWeightBasedDistanceMultiplier float64, eiaFuelPrice unit.Millicents, portName string) (unit.Cents, PricingDisplayParams, error) + Price(appCtx appcontext.AppContext, actualPickupDate time.Time, distance unit.Miles, weight unit.Pound, fscWeightBasedDistanceMultiplier float64, eiaFuelPrice unit.Millicents) (unit.Cents, PricingDisplayParams, error) ParamsPricer } diff --git a/pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer.go b/pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer.go index 7d2c94e7096..3e6dcfe1bdd 100644 --- a/pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer.go +++ b/pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer.go @@ -23,7 +23,7 @@ func NewPortFuelSurchargePricer() services.IntlPortFuelSurchargePricer { return &portFuelSurchargePricer{} } -func (p portFuelSurchargePricer) Price(_ appcontext.AppContext, actualPickupDate time.Time, distance unit.Miles, weight unit.Pound, fscWeightBasedDistanceMultiplier float64, eiaFuelPrice unit.Millicents, portName string) (unit.Cents, services.PricingDisplayParams, error) { +func (p portFuelSurchargePricer) Price(_ appcontext.AppContext, actualPickupDate time.Time, distance unit.Miles, weight unit.Pound, fscWeightBasedDistanceMultiplier float64, eiaFuelPrice unit.Millicents) (unit.Cents, services.PricingDisplayParams, error) { // Validate parameters if actualPickupDate.IsZero() { return 0, nil, errors.New("ActualPickupDate is required") @@ -40,9 +40,6 @@ func (p portFuelSurchargePricer) Price(_ appcontext.AppContext, actualPickupDate if eiaFuelPrice == 0 { return 0, nil, errors.New("EIAFuelPrice is required") } - if portName == "" { - return 0, nil, errors.New("PortName is required") - } fscPriceDifferenceInCents := (eiaFuelPrice - baseGHCDieselFuelPrice).Float64() / 1000.0 fscMultiplier := fscWeightBasedDistanceMultiplier * distance.Float64() @@ -99,10 +96,10 @@ func (p portFuelSurchargePricer) PriceUsingParams(appCtx appcontext.AppContext, return unit.Cents(0), nil, err } - portName, err := getParamString(params, models.ServiceItemParamNamePortName) + _, err = getParamString(params, models.ServiceItemParamNamePortName) if err != nil { return unit.Cents(0), nil, err } - return p.Price(appCtx, actualPickupDate, unit.Miles(distance), unit.Pound(weightBilled), fscWeightBasedDistanceMultiplier, unit.Millicents(eiaFuelPrice), portName) + return p.Price(appCtx, actualPickupDate, unit.Miles(distance), unit.Pound(weightBilled), fscWeightBasedDistanceMultiplier, unit.Millicents(eiaFuelPrice)) } diff --git a/pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer_test.go b/pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer_test.go index 9c07773bdb8..fa660bd796d 100644 --- a/pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer_test.go +++ b/pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer_test.go @@ -45,7 +45,7 @@ func (suite *GHCRateEngineServiceSuite) TestIntlPortFuelSurchargePricer() { }) suite.Run("success without PaymentServiceItemParams", func() { - priceCents, _, err := intlPortFuelSurchargePricer.Price(suite.AppContextForTest(), intlPortFscActualPickupDate, intlPortFscTestDistance, intlPortFscTestWeight, intlPortFscWeightDistanceMultiplier, intlPortFscFuelPrice, intlPortFscPortName) + priceCents, _, err := intlPortFuelSurchargePricer.Price(suite.AppContextForTest(), intlPortFscActualPickupDate, intlPortFscTestDistance, intlPortFscTestWeight, intlPortFscWeightDistanceMultiplier, intlPortFscFuelPrice) suite.NoError(err) suite.Equal(intlPortFscPriceCents, priceCents) }) @@ -71,7 +71,7 @@ func (suite *GHCRateEngineServiceSuite) TestIntlPortFuelSurchargePricer() { }) suite.Run("FSC is negative if fuel price from EIA is below $2.50", func() { - priceCents, _, err := intlPortFuelSurchargePricer.Price(suite.AppContextForTest(), intlPortFscActualPickupDate, intlPortFscTestDistance, intlPortFscTestWeight, intlPortFscWeightDistanceMultiplier, 242400, intlPortFscPortName) + priceCents, _, err := intlPortFuelSurchargePricer.Price(suite.AppContextForTest(), intlPortFscActualPickupDate, intlPortFscTestDistance, intlPortFscTestWeight, intlPortFscWeightDistanceMultiplier, 242400) suite.NoError(err) suite.Equal(unit.Cents(-721), priceCents) }) @@ -79,27 +79,27 @@ func (suite *GHCRateEngineServiceSuite) TestIntlPortFuelSurchargePricer() { suite.Run("Price validation errors", func() { // No actual pickup date - _, _, err := intlPortFuelSurchargePricer.Price(suite.AppContextForTest(), time.Time{}, intlPortFscTestDistance, intlPortFscTestWeight, intlPortFscWeightDistanceMultiplier, intlPortFscFuelPrice, intlPortFscPortName) + _, _, err := intlPortFuelSurchargePricer.Price(suite.AppContextForTest(), time.Time{}, intlPortFscTestDistance, intlPortFscTestWeight, intlPortFscWeightDistanceMultiplier, intlPortFscFuelPrice) suite.Error(err) suite.Equal("ActualPickupDate is required", err.Error()) // No distance - _, _, err = intlPortFuelSurchargePricer.Price(suite.AppContextForTest(), intlPortFscActualPickupDate, unit.Miles(0), intlPortFscTestWeight, intlPortFscWeightDistanceMultiplier, intlPortFscFuelPrice, intlPortFscPortName) + _, _, err = intlPortFuelSurchargePricer.Price(suite.AppContextForTest(), intlPortFscActualPickupDate, unit.Miles(0), intlPortFscTestWeight, intlPortFscWeightDistanceMultiplier, intlPortFscFuelPrice) suite.Error(err) suite.Equal("Distance must be greater than 0", err.Error()) // No weight - _, _, err = intlPortFuelSurchargePricer.Price(suite.AppContextForTest(), intlPortFscActualPickupDate, intlPortFscTestDistance, unit.Pound(0), intlPortFscWeightDistanceMultiplier, intlPortFscFuelPrice, intlPortFscPortName) + _, _, err = intlPortFuelSurchargePricer.Price(suite.AppContextForTest(), intlPortFscActualPickupDate, intlPortFscTestDistance, unit.Pound(0), intlPortFscWeightDistanceMultiplier, intlPortFscFuelPrice) suite.Error(err) suite.Equal(fmt.Sprintf("weight must be a minimum of %d", minDomesticWeight), err.Error()) // No weight based distance multiplier - _, _, err = intlPortFuelSurchargePricer.Price(suite.AppContextForTest(), intlPortFscActualPickupDate, intlPortFscTestDistance, intlPortFscTestWeight, 0, intlPortFscFuelPrice, intlPortFscPortName) + _, _, err = intlPortFuelSurchargePricer.Price(suite.AppContextForTest(), intlPortFscActualPickupDate, intlPortFscTestDistance, intlPortFscTestWeight, 0, intlPortFscFuelPrice) suite.Error(err) suite.Equal("WeightBasedDistanceMultiplier is required", err.Error()) // No EIA fuel price - _, _, err = intlPortFuelSurchargePricer.Price(suite.AppContextForTest(), intlPortFscActualPickupDate, intlPortFscTestDistance, intlPortFscTestWeight, intlPortFscWeightDistanceMultiplier, 0, intlPortFscPortName) + _, _, err = intlPortFuelSurchargePricer.Price(suite.AppContextForTest(), intlPortFscActualPickupDate, intlPortFscTestDistance, intlPortFscTestWeight, intlPortFscWeightDistanceMultiplier, 0) suite.Error(err) suite.Equal("EIAFuelPrice is required", err.Error()) }) diff --git a/src/components/PrimeUI/CreatePaymentRequestForm/CreatePaymentRequestForm.jsx b/src/components/PrimeUI/CreatePaymentRequestForm/CreatePaymentRequestForm.jsx index 410e65506f1..d5b3f283829 100644 --- a/src/components/PrimeUI/CreatePaymentRequestForm/CreatePaymentRequestForm.jsx +++ b/src/components/PrimeUI/CreatePaymentRequestForm/CreatePaymentRequestForm.jsx @@ -18,6 +18,7 @@ import ServiceItem from 'components/PrimeUI/ServiceItem/ServiceItem'; import Shipment from 'components/PrimeUI/Shipment/Shipment'; import { DatePickerInput } from 'components/form/fields'; import TextField from 'components/form/fields/TextField/TextField'; +import { SERVICE_ITEM_CODES } from 'constants/serviceItems'; const CreatePaymentRequestForm = ({ initialValues, @@ -128,27 +129,27 @@ const CreatePaymentRequestForm = ({ /> )} - {(mtoServiceItem.reServiceCode === 'DLH' || - mtoServiceItem.reServiceCode === 'DSH' || - mtoServiceItem.reServiceCode === 'FSC' || - mtoServiceItem.reServiceCode === 'DUPK' || - mtoServiceItem.reServiceCode === 'DNPK' || - mtoServiceItem.reServiceCode === 'DOFSIT' || - mtoServiceItem.reServiceCode === 'DOPSIT' || - mtoServiceItem.reServiceCode === 'DOSHUT' || - mtoServiceItem.reServiceCode === 'DDFSIT' || - mtoServiceItem.reServiceCode === 'DDDSIT' || - mtoServiceItem.reServiceCode === 'DOP' || - mtoServiceItem.reServiceCode === 'DDP' || - mtoServiceItem.reServiceCode === 'DPK' || - mtoServiceItem.reServiceCode === 'DDSFSC' || - mtoServiceItem.reServiceCode === 'DOSFSC' || - mtoServiceItem.reServiceCode === 'DDSHUT' || - mtoServiceItem.reServiceCode === 'IHPK' || - mtoServiceItem.reServiceCode === 'IHUPK' || - mtoServiceItem.reServiceCode === 'ISLH' || - mtoServiceItem.reServiceCode === 'POEFSC' || - mtoServiceItem.reServiceCode === 'PODFSC') && ( + {(mtoServiceItem.reServiceCode === SERVICE_ITEM_CODES.DLH || + mtoServiceItem.reServiceCode === SERVICE_ITEM_CODES.DSH || + mtoServiceItem.reServiceCode === SERVICE_ITEM_CODES.FSC || + mtoServiceItem.reServiceCode === SERVICE_ITEM_CODES.DUPK || + mtoServiceItem.reServiceCode === SERVICE_ITEM_CODES.DNPK || + mtoServiceItem.reServiceCode === SERVICE_ITEM_CODES.DOFSIT || + mtoServiceItem.reServiceCode === SERVICE_ITEM_CODES.DOPSIT || + mtoServiceItem.reServiceCode === SERVICE_ITEM_CODES.DOSHUT || + mtoServiceItem.reServiceCode === SERVICE_ITEM_CODES.DDFSIT || + mtoServiceItem.reServiceCode === SERVICE_ITEM_CODES.DDDSIT || + mtoServiceItem.reServiceCode === SERVICE_ITEM_CODES.DOP || + mtoServiceItem.reServiceCode === SERVICE_ITEM_CODES.DDP || + mtoServiceItem.reServiceCode === SERVICE_ITEM_CODES.DPK || + mtoServiceItem.reServiceCode === SERVICE_ITEM_CODES.DDSFSC || + mtoServiceItem.reServiceCode === SERVICE_ITEM_CODES.DOSFSC || + mtoServiceItem.reServiceCode === SERVICE_ITEM_CODES.DDSHUT || + mtoServiceItem.reServiceCode === SERVICE_ITEM_CODES.IHPK || + mtoServiceItem.reServiceCode === SERVICE_ITEM_CODES.IHUPK || + mtoServiceItem.reServiceCode === SERVICE_ITEM_CODES.ISLH || + mtoServiceItem.reServiceCode === SERVICE_ITEM_CODES.POEFSC || + mtoServiceItem.reServiceCode === SERVICE_ITEM_CODES.PODFSC) && ( Date: Fri, 3 Jan 2025 20:21:47 +0000 Subject: [PATCH 44/54] mocks again --- .../mocks/IntlPortFuelSurchargePricer.go | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/pkg/services/mocks/IntlPortFuelSurchargePricer.go b/pkg/services/mocks/IntlPortFuelSurchargePricer.go index 48e396d017e..1780857c419 100644 --- a/pkg/services/mocks/IntlPortFuelSurchargePricer.go +++ b/pkg/services/mocks/IntlPortFuelSurchargePricer.go @@ -20,9 +20,9 @@ type IntlPortFuelSurchargePricer struct { mock.Mock } -// Price provides a mock function with given fields: appCtx, actualPickupDate, distance, weight, fscWeightBasedDistanceMultiplier, eiaFuelPrice, portName -func (_m *IntlPortFuelSurchargePricer) Price(appCtx appcontext.AppContext, actualPickupDate time.Time, distance unit.Miles, weight unit.Pound, fscWeightBasedDistanceMultiplier float64, eiaFuelPrice unit.Millicents, portName string) (unit.Cents, services.PricingDisplayParams, error) { - ret := _m.Called(appCtx, actualPickupDate, distance, weight, fscWeightBasedDistanceMultiplier, eiaFuelPrice, portName) +// Price provides a mock function with given fields: appCtx, actualPickupDate, distance, weight, fscWeightBasedDistanceMultiplier, eiaFuelPrice +func (_m *IntlPortFuelSurchargePricer) Price(appCtx appcontext.AppContext, actualPickupDate time.Time, distance unit.Miles, weight unit.Pound, fscWeightBasedDistanceMultiplier float64, eiaFuelPrice unit.Millicents) (unit.Cents, services.PricingDisplayParams, error) { + ret := _m.Called(appCtx, actualPickupDate, distance, weight, fscWeightBasedDistanceMultiplier, eiaFuelPrice) if len(ret) == 0 { panic("no return value specified for Price") @@ -31,25 +31,25 @@ func (_m *IntlPortFuelSurchargePricer) Price(appCtx appcontext.AppContext, actua var r0 unit.Cents var r1 services.PricingDisplayParams var r2 error - if rf, ok := ret.Get(0).(func(appcontext.AppContext, time.Time, unit.Miles, unit.Pound, float64, unit.Millicents, string) (unit.Cents, services.PricingDisplayParams, error)); ok { - return rf(appCtx, actualPickupDate, distance, weight, fscWeightBasedDistanceMultiplier, eiaFuelPrice, portName) + if rf, ok := ret.Get(0).(func(appcontext.AppContext, time.Time, unit.Miles, unit.Pound, float64, unit.Millicents) (unit.Cents, services.PricingDisplayParams, error)); ok { + return rf(appCtx, actualPickupDate, distance, weight, fscWeightBasedDistanceMultiplier, eiaFuelPrice) } - if rf, ok := ret.Get(0).(func(appcontext.AppContext, time.Time, unit.Miles, unit.Pound, float64, unit.Millicents, string) unit.Cents); ok { - r0 = rf(appCtx, actualPickupDate, distance, weight, fscWeightBasedDistanceMultiplier, eiaFuelPrice, portName) + if rf, ok := ret.Get(0).(func(appcontext.AppContext, time.Time, unit.Miles, unit.Pound, float64, unit.Millicents) unit.Cents); ok { + r0 = rf(appCtx, actualPickupDate, distance, weight, fscWeightBasedDistanceMultiplier, eiaFuelPrice) } else { r0 = ret.Get(0).(unit.Cents) } - if rf, ok := ret.Get(1).(func(appcontext.AppContext, time.Time, unit.Miles, unit.Pound, float64, unit.Millicents, string) services.PricingDisplayParams); ok { - r1 = rf(appCtx, actualPickupDate, distance, weight, fscWeightBasedDistanceMultiplier, eiaFuelPrice, portName) + if rf, ok := ret.Get(1).(func(appcontext.AppContext, time.Time, unit.Miles, unit.Pound, float64, unit.Millicents) services.PricingDisplayParams); ok { + r1 = rf(appCtx, actualPickupDate, distance, weight, fscWeightBasedDistanceMultiplier, eiaFuelPrice) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(services.PricingDisplayParams) } } - if rf, ok := ret.Get(2).(func(appcontext.AppContext, time.Time, unit.Miles, unit.Pound, float64, unit.Millicents, string) error); ok { - r2 = rf(appCtx, actualPickupDate, distance, weight, fscWeightBasedDistanceMultiplier, eiaFuelPrice, portName) + if rf, ok := ret.Get(2).(func(appcontext.AppContext, time.Time, unit.Miles, unit.Pound, float64, unit.Millicents) error); ok { + r2 = rf(appCtx, actualPickupDate, distance, weight, fscWeightBasedDistanceMultiplier, eiaFuelPrice) } else { r2 = ret.Error(2) } From 114ee8e5c6974ec5c6bb2dce739b8eba00ad89d3 Mon Sep 17 00:00:00 2001 From: Daniel Jordan Date: Fri, 3 Jan 2025 21:49:10 +0000 Subject: [PATCH 45/54] updating prime docs --- pkg/gen/primeapi/embedded_spec.go | 4 +- .../payment_request/create_payment_request.go | 201 ++++++++++++++- .../payment_request/payment_request_client.go | 201 ++++++++++++++- swagger-def/prime.yaml | 171 ++++++++++++- swagger/prime.yaml | 233 ++++++++++++++++-- 5 files changed, 757 insertions(+), 53 deletions(-) diff --git a/pkg/gen/primeapi/embedded_spec.go b/pkg/gen/primeapi/embedded_spec.go index 52039d783cc..69ed189d17f 100644 --- a/pkg/gen/primeapi/embedded_spec.go +++ b/pkg/gen/primeapi/embedded_spec.go @@ -1063,7 +1063,7 @@ func init() { }, "/payment-requests": { "post": { - "description": "Creates a new instance of a paymentRequest and is assigned the status ` + "`" + `PENDING` + "`" + `.\nA move task order can have multiple payment requests, and\na final payment request can be marked using boolean ` + "`" + `isFinal` + "`" + `.\n\nIf a ` + "`" + `PENDING` + "`" + ` payment request is recalculated,\na new payment request is created and the original request is\nmarked with the status ` + "`" + `DEPRECATED` + "`" + `.\n\n**NOTE**: In order to create a payment request for most service items, the shipment *must*\nbe updated with the ` + "`" + `PrimeActualWeight` + "`" + ` value via [updateMTOShipment](#operation/updateMTOShipment).\n\n**FSC - Fuel Surcharge** service items require ` + "`" + `ActualPickupDate` + "`" + ` to be updated on the shipment.\n\nA service item can be on several payment requests in the case of partial payment requests and payments.\n\nIn the request, if no params are necessary, then just the ` + "`" + `serviceItem` + "`" + ` ` + "`" + `id` + "`" + ` is required. For example:\n` + "`" + `` + "`" + `` + "`" + `json\n{\n \"isFinal\": false,\n \"moveTaskOrderID\": \"uuid\",\n \"serviceItems\": [\n {\n \"id\": \"uuid\",\n },\n {\n \"id\": \"uuid\",\n \"params\": [\n {\n \"key\": \"Service Item Parameter Name\",\n \"value\": \"Service Item Parameter Value\"\n }\n ]\n }\n ],\n \"pointOfContact\": \"string\"\n}\n` + "`" + `` + "`" + `` + "`" + `\n\nSIT Service Items \u0026 Accepted Payment Request Parameters:\n---\nIf ` + "`" + `WeightBilled` + "`" + ` is not provided then the full shipment weight (` + "`" + `PrimeActualWeight` + "`" + `) will be considered in the calculation.\n\n**NOTE**: Diversions have a unique calcuation for payment requests without a ` + "`" + `WeightBilled` + "`" + ` parameter.\n\nIf you created a payment request for a diversion and ` + "`" + `WeightBilled` + "`" + ` is not provided, then the following will be used in the calculation:\n- The lowest shipment weight (` + "`" + `PrimeActualWeight` + "`" + `) found in the diverted shipment chain.\n- The lowest reweigh weight found in the diverted shipment chain.\n\nThe diverted shipment chain is created by referencing the ` + "`" + `diversion` + "`" + ` boolean, ` + "`" + `divertedFromShipmentId` + "`" + ` UUID, and matching destination to pickup addresses.\nIf the chain cannot be established it will fall back to the ` + "`" + `PrimeActualWeight` + "`" + ` of the current shipment. This is utilized because diverted shipments are all one single shipment, but going to different locations.\nThe lowest weight found is the true shipment weight, and thus we search the chain of shipments for the lowest weight found.\n\n**DOFSIT - Domestic origin 1st day SIT**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DOASIT - Domestic origin add'l SIT** *(SITPaymentRequestStart \u0026 SITPaymentRequestEnd are **REQUIRED**)*\n*To create a paymentRequest for this service item, the ` + "`" + `SITPaymentRequestStart` + "`" + ` and ` + "`" + `SITPaymentRequestEnd` + "`" + ` dates must not overlap previously requested SIT dates.*\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n },\n {\n \"key\": \"SITPaymentRequestStart\",\n \"value\": \"date\"\n },\n {\n \"key\": \"SITPaymentRequestEnd\",\n \"value\": \"date\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DOPSIT - Domestic origin SIT pickup**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DOSHUT - Domestic origin shuttle service**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDFSIT - Domestic destination 1st day SIT**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDASIT - Domestic destination add'l SIT** *(SITPaymentRequestStart \u0026 SITPaymentRequestEnd are **REQUIRED**)*\n*To create a paymentRequest for this service item, the ` + "`" + `SITPaymentRequestStart` + "`" + ` and ` + "`" + `SITPaymentRequestEnd` + "`" + ` dates must not overlap previously requested SIT dates.*\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n },\n {\n \"key\": \"SITPaymentRequestStart\",\n \"value\": \"date\"\n },\n {\n \"key\": \"SITPaymentRequestEnd\",\n \"value\": \"date\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDDSIT - Domestic destination SIT delivery**\n*To create a paymentRequest for this service item, it must first have a final address set via [updateMTOServiceItem](#operation/updateMTOServiceItem).*\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDSHUT - Domestic destination shuttle service**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n---\n", + "description": "Creates a new instance of a paymentRequest and is assigned the status ` + "`" + `PENDING` + "`" + `.\nA move task order can have multiple payment requests, and\na final payment request can be marked using boolean ` + "`" + `isFinal` + "`" + `.\n\nIf a ` + "`" + `PENDING` + "`" + ` payment request is recalculated,\na new payment request is created and the original request is\nmarked with the status ` + "`" + `DEPRECATED` + "`" + `.\n\n**NOTE**: In order to create a payment request for most service items, the shipment *must*\nbe updated with the ` + "`" + `PrimeActualWeight` + "`" + ` value via [updateMTOShipment](#operation/updateMTOShipment).\n\nIf ` + "`" + `WeightBilled` + "`" + ` is not provided then the full shipment weight (` + "`" + `PrimeActualWeight` + "`" + `) will be considered in the calculation.\n\n**NOTE**: Diversions have a unique calcuation for payment requests without a ` + "`" + `WeightBilled` + "`" + ` parameter.\n\nIf you created a payment request for a diversion and ` + "`" + `WeightBilled` + "`" + ` is not provided, then the following will be used in the calculation:\n- The lowest shipment weight (` + "`" + `PrimeActualWeight` + "`" + `) found in the diverted shipment chain.\n- The lowest reweigh weight found in the diverted shipment chain.\n\nThe diverted shipment chain is created by referencing the ` + "`" + `diversion` + "`" + ` boolean, ` + "`" + `divertedFromShipmentId` + "`" + ` UUID, and matching destination to pickup addresses.\nIf the chain cannot be established it will fall back to the ` + "`" + `PrimeActualWeight` + "`" + ` of the current shipment. This is utilized because diverted shipments are all one single shipment, but going to different locations.\nThe lowest weight found is the true shipment weight, and thus we search the chain of shipments for the lowest weight found.\n\nA service item can be on several payment requests in the case of partial payment requests and payments.\n\nIn the request, if no params are necessary, then just the ` + "`" + `serviceItem` + "`" + ` ` + "`" + `id` + "`" + ` is required. For example:\n` + "`" + `` + "`" + `` + "`" + `json\n{\n \"isFinal\": false,\n \"moveTaskOrderID\": \"uuid\",\n \"serviceItems\": [\n {\n \"id\": \"uuid\",\n },\n {\n \"id\": \"uuid\",\n \"params\": [\n {\n \"key\": \"Service Item Parameter Name\",\n \"value\": \"Service Item Parameter Value\"\n }\n ]\n }\n ],\n \"pointOfContact\": \"string\"\n}\n` + "`" + `` + "`" + `` + "`" + `\n\nDomestic Basic Service Items \u0026 Accepted Payment Request Parameters:\n---\n\n**DLH - Domestic Linehaul**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DSH - Domestic Shorthaul**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**FSC - Fuel Surcharge**\n**NOTE**: FSC requires ` + "`" + `ActualPickupDate` + "`" + ` to be updated on the shipment.\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DUPK - Domestic Unpacking**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DPK - Domestic Packing**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DNPK - Domestic NTS Packing**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DPK - Domestic Packing**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DOP - Domestic Origin Price**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDP - Domestic Destination Price**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\nDomestic SIT Service Items \u0026 Accepted Payment Request Parameters:\n---\n\n**DOFSIT - Domestic origin 1st day SIT**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DOASIT - Domestic origin add'l SIT** *(SITPaymentRequestStart \u0026 SITPaymentRequestEnd are **REQUIRED**)*\n*To create a paymentRequest for this service item, the ` + "`" + `SITPaymentRequestStart` + "`" + ` and ` + "`" + `SITPaymentRequestEnd` + "`" + ` dates must not overlap previously requested SIT dates.*\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n },\n {\n \"key\": \"SITPaymentRequestStart\",\n \"value\": \"date\"\n },\n {\n \"key\": \"SITPaymentRequestEnd\",\n \"value\": \"date\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DOPSIT - Domestic origin SIT pickup**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DOSHUT - Domestic origin shuttle service**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDFSIT - Domestic destination 1st day SIT**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDASIT - Domestic destination add'l SIT** *(SITPaymentRequestStart \u0026 SITPaymentRequestEnd are **REQUIRED**)*\n*To create a paymentRequest for this service item, the ` + "`" + `SITPaymentRequestStart` + "`" + ` and ` + "`" + `SITPaymentRequestEnd` + "`" + ` dates must not overlap previously requested SIT dates.*\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n },\n {\n \"key\": \"SITPaymentRequestStart\",\n \"value\": \"date\"\n },\n {\n \"key\": \"SITPaymentRequestEnd\",\n \"value\": \"date\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDDSIT - Domestic destination SIT delivery**\n*To create a paymentRequest for this service item, it must first have a final address set via [updateMTOServiceItem](#operation/updateMTOServiceItem).*\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDSHUT - Domestic destination shuttle service**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n---\n\nInternational Basic Service Items \u0026 Accepted Payment Request Parameters:\n---\nJust like domestic shipments \u0026 service items, if ` + "`" + `WeightBilled` + "`" + ` is not provided then the full shipment weight (` + "`" + `PrimeActualWeight` + "`" + `) will be considered in the calculation.\n**NOTE**: ` + "`" + `POEFSC` + "`" + ` \u0026 ` + "`" + `PODFSC` + "`" + ` service items must have a port associated on the service item in order to successfully add it to a payment request. To update the port of a service item, you must use the (#operation/updateMTOServiceItem) endpoint.\n\n**ISLH - International Shipping \u0026 Linehaul**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**IHPK - International HHG Pack**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**IHUPK - International HHG Unpack**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**POEFSC - International Port of Embarkation Fuel Surcharge**\n **NOTE**: POEFSC requires ` + "`" + `ActualPickupDate` + "`" + ` to be updated on the shipment \u0026 ` + "`" + `POELocation` + "`" + ` on the service item.\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**PODFSC - International Port of Debarkation Fuel Surcharge**\n**NOTE**: PODFSC requires ` + "`" + `ActualPickupDate` + "`" + ` to be updated on the shipment \u0026 ` + "`" + `PODLocation` + "`" + ` on the service item.\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n---\n", "consumes": [ "application/json" ], @@ -5921,7 +5921,7 @@ func init() { }, "/payment-requests": { "post": { - "description": "Creates a new instance of a paymentRequest and is assigned the status ` + "`" + `PENDING` + "`" + `.\nA move task order can have multiple payment requests, and\na final payment request can be marked using boolean ` + "`" + `isFinal` + "`" + `.\n\nIf a ` + "`" + `PENDING` + "`" + ` payment request is recalculated,\na new payment request is created and the original request is\nmarked with the status ` + "`" + `DEPRECATED` + "`" + `.\n\n**NOTE**: In order to create a payment request for most service items, the shipment *must*\nbe updated with the ` + "`" + `PrimeActualWeight` + "`" + ` value via [updateMTOShipment](#operation/updateMTOShipment).\n\n**FSC - Fuel Surcharge** service items require ` + "`" + `ActualPickupDate` + "`" + ` to be updated on the shipment.\n\nA service item can be on several payment requests in the case of partial payment requests and payments.\n\nIn the request, if no params are necessary, then just the ` + "`" + `serviceItem` + "`" + ` ` + "`" + `id` + "`" + ` is required. For example:\n` + "`" + `` + "`" + `` + "`" + `json\n{\n \"isFinal\": false,\n \"moveTaskOrderID\": \"uuid\",\n \"serviceItems\": [\n {\n \"id\": \"uuid\",\n },\n {\n \"id\": \"uuid\",\n \"params\": [\n {\n \"key\": \"Service Item Parameter Name\",\n \"value\": \"Service Item Parameter Value\"\n }\n ]\n }\n ],\n \"pointOfContact\": \"string\"\n}\n` + "`" + `` + "`" + `` + "`" + `\n\nSIT Service Items \u0026 Accepted Payment Request Parameters:\n---\nIf ` + "`" + `WeightBilled` + "`" + ` is not provided then the full shipment weight (` + "`" + `PrimeActualWeight` + "`" + `) will be considered in the calculation.\n\n**NOTE**: Diversions have a unique calcuation for payment requests without a ` + "`" + `WeightBilled` + "`" + ` parameter.\n\nIf you created a payment request for a diversion and ` + "`" + `WeightBilled` + "`" + ` is not provided, then the following will be used in the calculation:\n- The lowest shipment weight (` + "`" + `PrimeActualWeight` + "`" + `) found in the diverted shipment chain.\n- The lowest reweigh weight found in the diverted shipment chain.\n\nThe diverted shipment chain is created by referencing the ` + "`" + `diversion` + "`" + ` boolean, ` + "`" + `divertedFromShipmentId` + "`" + ` UUID, and matching destination to pickup addresses.\nIf the chain cannot be established it will fall back to the ` + "`" + `PrimeActualWeight` + "`" + ` of the current shipment. This is utilized because diverted shipments are all one single shipment, but going to different locations.\nThe lowest weight found is the true shipment weight, and thus we search the chain of shipments for the lowest weight found.\n\n**DOFSIT - Domestic origin 1st day SIT**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DOASIT - Domestic origin add'l SIT** *(SITPaymentRequestStart \u0026 SITPaymentRequestEnd are **REQUIRED**)*\n*To create a paymentRequest for this service item, the ` + "`" + `SITPaymentRequestStart` + "`" + ` and ` + "`" + `SITPaymentRequestEnd` + "`" + ` dates must not overlap previously requested SIT dates.*\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n },\n {\n \"key\": \"SITPaymentRequestStart\",\n \"value\": \"date\"\n },\n {\n \"key\": \"SITPaymentRequestEnd\",\n \"value\": \"date\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DOPSIT - Domestic origin SIT pickup**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DOSHUT - Domestic origin shuttle service**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDFSIT - Domestic destination 1st day SIT**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDASIT - Domestic destination add'l SIT** *(SITPaymentRequestStart \u0026 SITPaymentRequestEnd are **REQUIRED**)*\n*To create a paymentRequest for this service item, the ` + "`" + `SITPaymentRequestStart` + "`" + ` and ` + "`" + `SITPaymentRequestEnd` + "`" + ` dates must not overlap previously requested SIT dates.*\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n },\n {\n \"key\": \"SITPaymentRequestStart\",\n \"value\": \"date\"\n },\n {\n \"key\": \"SITPaymentRequestEnd\",\n \"value\": \"date\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDDSIT - Domestic destination SIT delivery**\n*To create a paymentRequest for this service item, it must first have a final address set via [updateMTOServiceItem](#operation/updateMTOServiceItem).*\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDSHUT - Domestic destination shuttle service**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n---\n", + "description": "Creates a new instance of a paymentRequest and is assigned the status ` + "`" + `PENDING` + "`" + `.\nA move task order can have multiple payment requests, and\na final payment request can be marked using boolean ` + "`" + `isFinal` + "`" + `.\n\nIf a ` + "`" + `PENDING` + "`" + ` payment request is recalculated,\na new payment request is created and the original request is\nmarked with the status ` + "`" + `DEPRECATED` + "`" + `.\n\n**NOTE**: In order to create a payment request for most service items, the shipment *must*\nbe updated with the ` + "`" + `PrimeActualWeight` + "`" + ` value via [updateMTOShipment](#operation/updateMTOShipment).\n\nIf ` + "`" + `WeightBilled` + "`" + ` is not provided then the full shipment weight (` + "`" + `PrimeActualWeight` + "`" + `) will be considered in the calculation.\n\n**NOTE**: Diversions have a unique calcuation for payment requests without a ` + "`" + `WeightBilled` + "`" + ` parameter.\n\nIf you created a payment request for a diversion and ` + "`" + `WeightBilled` + "`" + ` is not provided, then the following will be used in the calculation:\n- The lowest shipment weight (` + "`" + `PrimeActualWeight` + "`" + `) found in the diverted shipment chain.\n- The lowest reweigh weight found in the diverted shipment chain.\n\nThe diverted shipment chain is created by referencing the ` + "`" + `diversion` + "`" + ` boolean, ` + "`" + `divertedFromShipmentId` + "`" + ` UUID, and matching destination to pickup addresses.\nIf the chain cannot be established it will fall back to the ` + "`" + `PrimeActualWeight` + "`" + ` of the current shipment. This is utilized because diverted shipments are all one single shipment, but going to different locations.\nThe lowest weight found is the true shipment weight, and thus we search the chain of shipments for the lowest weight found.\n\nA service item can be on several payment requests in the case of partial payment requests and payments.\n\nIn the request, if no params are necessary, then just the ` + "`" + `serviceItem` + "`" + ` ` + "`" + `id` + "`" + ` is required. For example:\n` + "`" + `` + "`" + `` + "`" + `json\n{\n \"isFinal\": false,\n \"moveTaskOrderID\": \"uuid\",\n \"serviceItems\": [\n {\n \"id\": \"uuid\",\n },\n {\n \"id\": \"uuid\",\n \"params\": [\n {\n \"key\": \"Service Item Parameter Name\",\n \"value\": \"Service Item Parameter Value\"\n }\n ]\n }\n ],\n \"pointOfContact\": \"string\"\n}\n` + "`" + `` + "`" + `` + "`" + `\n\nDomestic Basic Service Items \u0026 Accepted Payment Request Parameters:\n---\n\n**DLH - Domestic Linehaul**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DSH - Domestic Shorthaul**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**FSC - Fuel Surcharge**\n**NOTE**: FSC requires ` + "`" + `ActualPickupDate` + "`" + ` to be updated on the shipment.\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DUPK - Domestic Unpacking**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DPK - Domestic Packing**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DNPK - Domestic NTS Packing**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DPK - Domestic Packing**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DOP - Domestic Origin Price**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDP - Domestic Destination Price**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\nDomestic SIT Service Items \u0026 Accepted Payment Request Parameters:\n---\n\n**DOFSIT - Domestic origin 1st day SIT**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DOASIT - Domestic origin add'l SIT** *(SITPaymentRequestStart \u0026 SITPaymentRequestEnd are **REQUIRED**)*\n*To create a paymentRequest for this service item, the ` + "`" + `SITPaymentRequestStart` + "`" + ` and ` + "`" + `SITPaymentRequestEnd` + "`" + ` dates must not overlap previously requested SIT dates.*\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n },\n {\n \"key\": \"SITPaymentRequestStart\",\n \"value\": \"date\"\n },\n {\n \"key\": \"SITPaymentRequestEnd\",\n \"value\": \"date\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DOPSIT - Domestic origin SIT pickup**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DOSHUT - Domestic origin shuttle service**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDFSIT - Domestic destination 1st day SIT**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDASIT - Domestic destination add'l SIT** *(SITPaymentRequestStart \u0026 SITPaymentRequestEnd are **REQUIRED**)*\n*To create a paymentRequest for this service item, the ` + "`" + `SITPaymentRequestStart` + "`" + ` and ` + "`" + `SITPaymentRequestEnd` + "`" + ` dates must not overlap previously requested SIT dates.*\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n },\n {\n \"key\": \"SITPaymentRequestStart\",\n \"value\": \"date\"\n },\n {\n \"key\": \"SITPaymentRequestEnd\",\n \"value\": \"date\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDDSIT - Domestic destination SIT delivery**\n*To create a paymentRequest for this service item, it must first have a final address set via [updateMTOServiceItem](#operation/updateMTOServiceItem).*\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**DDSHUT - Domestic destination shuttle service**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n---\n\nInternational Basic Service Items \u0026 Accepted Payment Request Parameters:\n---\nJust like domestic shipments \u0026 service items, if ` + "`" + `WeightBilled` + "`" + ` is not provided then the full shipment weight (` + "`" + `PrimeActualWeight` + "`" + `) will be considered in the calculation.\n**NOTE**: ` + "`" + `POEFSC` + "`" + ` \u0026 ` + "`" + `PODFSC` + "`" + ` service items must have a port associated on the service item in order to successfully add it to a payment request. To update the port of a service item, you must use the (#operation/updateMTOServiceItem) endpoint.\n\n**ISLH - International Shipping \u0026 Linehaul**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**IHPK - International HHG Pack**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**IHUPK - International HHG Unpack**\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**POEFSC - International Port of Embarkation Fuel Surcharge**\n **NOTE**: POEFSC requires ` + "`" + `ActualPickupDate` + "`" + ` to be updated on the shipment \u0026 ` + "`" + `POELocation` + "`" + ` on the service item.\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n\n**PODFSC - International Port of Debarkation Fuel Surcharge**\n**NOTE**: PODFSC requires ` + "`" + `ActualPickupDate` + "`" + ` to be updated on the shipment \u0026 ` + "`" + `PODLocation` + "`" + ` on the service item.\n` + "`" + `` + "`" + `` + "`" + `json\n \"params\": [\n {\n \"key\": \"WeightBilled\",\n \"value\": \"integer\"\n }\n ]\n` + "`" + `` + "`" + `` + "`" + `\n---\n", "consumes": [ "application/json" ], diff --git a/pkg/gen/primeapi/primeoperations/payment_request/create_payment_request.go b/pkg/gen/primeapi/primeoperations/payment_request/create_payment_request.go index 1e98d93abe7..d1ca6a38ab8 100644 --- a/pkg/gen/primeapi/primeoperations/payment_request/create_payment_request.go +++ b/pkg/gen/primeapi/primeoperations/payment_request/create_payment_request.go @@ -45,7 +45,17 @@ marked with the status `DEPRECATED`. **NOTE**: In order to create a payment request for most service items, the shipment *must* be updated with the `PrimeActualWeight` value via [updateMTOShipment](#operation/updateMTOShipment). -**FSC - Fuel Surcharge** service items require `ActualPickupDate` to be updated on the shipment. +If `WeightBilled` is not provided then the full shipment weight (`PrimeActualWeight`) will be considered in the calculation. + +**NOTE**: Diversions have a unique calcuation for payment requests without a `WeightBilled` parameter. + +If you created a payment request for a diversion and `WeightBilled` is not provided, then the following will be used in the calculation: +- The lowest shipment weight (`PrimeActualWeight`) found in the diverted shipment chain. +- The lowest reweigh weight found in the diverted shipment chain. + +The diverted shipment chain is created by referencing the `diversion` boolean, `divertedFromShipmentId` UUID, and matching destination to pickup addresses. +If the chain cannot be established it will fall back to the `PrimeActualWeight` of the current shipment. This is utilized because diverted shipments are all one single shipment, but going to different locations. +The lowest weight found is the true shipment weight, and thus we search the chain of shipments for the lowest weight found. A service item can be on several payment requests in the case of partial payment requests and payments. @@ -74,19 +84,120 @@ In the request, if no params are necessary, then just the `serviceItem` `id` is ``` -SIT Service Items & Accepted Payment Request Parameters: +Domestic Basic Service Items & Accepted Payment Request Parameters: --- -If `WeightBilled` is not provided then the full shipment weight (`PrimeActualWeight`) will be considered in the calculation. -**NOTE**: Diversions have a unique calcuation for payment requests without a `WeightBilled` parameter. +**DLH - Domestic Linehaul** +```json -If you created a payment request for a diversion and `WeightBilled` is not provided, then the following will be used in the calculation: -- The lowest shipment weight (`PrimeActualWeight`) found in the diverted shipment chain. -- The lowest reweigh weight found in the diverted shipment chain. + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] -The diverted shipment chain is created by referencing the `diversion` boolean, `divertedFromShipmentId` UUID, and matching destination to pickup addresses. -If the chain cannot be established it will fall back to the `PrimeActualWeight` of the current shipment. This is utilized because diverted shipments are all one single shipment, but going to different locations. -The lowest weight found is the true shipment weight, and thus we search the chain of shipments for the lowest weight found. +``` + +**DSH - Domestic Shorthaul** +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + +``` + +**FSC - Fuel Surcharge** +**NOTE**: FSC requires `ActualPickupDate` to be updated on the shipment. +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + +``` + +**DUPK - Domestic Unpacking** +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + +``` + +**DPK - Domestic Packing** +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + +``` + +**DNPK - Domestic NTS Packing** +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + +``` + +**DPK - Domestic Packing** +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + +``` + +**DOP - Domestic Origin Price** +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + +``` + +**DDP - Domestic Destination Price** +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + +``` + +Domestic SIT Service Items & Accepted Payment Request Parameters: +--- **DOFSIT - Domestic origin 1st day SIT** ```json @@ -201,6 +312,76 @@ The lowest weight found is the true shipment weight, and thus we search the chai } ] +``` +--- + +International Basic Service Items & Accepted Payment Request Parameters: +--- +Just like domestic shipments & service items, if `WeightBilled` is not provided then the full shipment weight (`PrimeActualWeight`) will be considered in the calculation. +**NOTE**: `POEFSC` & `PODFSC` service items must have a port associated on the service item in order to successfully add it to a payment request. To update the port of a service item, you must use the (#operation/updateMTOServiceItem) endpoint. + +**ISLH - International Shipping & Linehaul** +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + +``` + +**IHPK - International HHG Pack** +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + +``` + +**IHUPK - International HHG Unpack** +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + +``` + +**POEFSC - International Port of Embarkation Fuel Surcharge** + + **NOTE**: POEFSC requires `ActualPickupDate` to be updated on the shipment & `POELocation` on the service item. + +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + +``` + +**PODFSC - International Port of Debarkation Fuel Surcharge** +**NOTE**: PODFSC requires `ActualPickupDate` to be updated on the shipment & `PODLocation` on the service item. +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` --- */ diff --git a/pkg/gen/primeclient/payment_request/payment_request_client.go b/pkg/gen/primeclient/payment_request/payment_request_client.go index 82eae72610e..b10b46d87cf 100644 --- a/pkg/gen/primeclient/payment_request/payment_request_client.go +++ b/pkg/gen/primeclient/payment_request/payment_request_client.go @@ -52,7 +52,17 @@ marked with the status `DEPRECATED`. **NOTE**: In order to create a payment request for most service items, the shipment *must* be updated with the `PrimeActualWeight` value via [updateMTOShipment](#operation/updateMTOShipment). -**FSC - Fuel Surcharge** service items require `ActualPickupDate` to be updated on the shipment. +If `WeightBilled` is not provided then the full shipment weight (`PrimeActualWeight`) will be considered in the calculation. + +**NOTE**: Diversions have a unique calcuation for payment requests without a `WeightBilled` parameter. + +If you created a payment request for a diversion and `WeightBilled` is not provided, then the following will be used in the calculation: +- The lowest shipment weight (`PrimeActualWeight`) found in the diverted shipment chain. +- The lowest reweigh weight found in the diverted shipment chain. + +The diverted shipment chain is created by referencing the `diversion` boolean, `divertedFromShipmentId` UUID, and matching destination to pickup addresses. +If the chain cannot be established it will fall back to the `PrimeActualWeight` of the current shipment. This is utilized because diverted shipments are all one single shipment, but going to different locations. +The lowest weight found is the true shipment weight, and thus we search the chain of shipments for the lowest weight found. A service item can be on several payment requests in the case of partial payment requests and payments. @@ -81,19 +91,120 @@ In the request, if no params are necessary, then just the `serviceItem` `id` is ``` -SIT Service Items & Accepted Payment Request Parameters: +Domestic Basic Service Items & Accepted Payment Request Parameters: --- -If `WeightBilled` is not provided then the full shipment weight (`PrimeActualWeight`) will be considered in the calculation. -**NOTE**: Diversions have a unique calcuation for payment requests without a `WeightBilled` parameter. +**DLH - Domestic Linehaul** +```json -If you created a payment request for a diversion and `WeightBilled` is not provided, then the following will be used in the calculation: -- The lowest shipment weight (`PrimeActualWeight`) found in the diverted shipment chain. -- The lowest reweigh weight found in the diverted shipment chain. + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] -The diverted shipment chain is created by referencing the `diversion` boolean, `divertedFromShipmentId` UUID, and matching destination to pickup addresses. -If the chain cannot be established it will fall back to the `PrimeActualWeight` of the current shipment. This is utilized because diverted shipments are all one single shipment, but going to different locations. -The lowest weight found is the true shipment weight, and thus we search the chain of shipments for the lowest weight found. +``` + +**DSH - Domestic Shorthaul** +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + +``` + +**FSC - Fuel Surcharge** +**NOTE**: FSC requires `ActualPickupDate` to be updated on the shipment. +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + +``` + +**DUPK - Domestic Unpacking** +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + +``` + +**DPK - Domestic Packing** +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + +``` + +**DNPK - Domestic NTS Packing** +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + +``` + +**DPK - Domestic Packing** +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + +``` + +**DOP - Domestic Origin Price** +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + +``` + +**DDP - Domestic Destination Price** +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + +``` + +Domestic SIT Service Items & Accepted Payment Request Parameters: +--- **DOFSIT - Domestic origin 1st day SIT** ```json @@ -208,6 +319,76 @@ The lowest weight found is the true shipment weight, and thus we search the chai } ] +``` +--- + +International Basic Service Items & Accepted Payment Request Parameters: +--- +Just like domestic shipments & service items, if `WeightBilled` is not provided then the full shipment weight (`PrimeActualWeight`) will be considered in the calculation. +**NOTE**: `POEFSC` & `PODFSC` service items must have a port associated on the service item in order to successfully add it to a payment request. To update the port of a service item, you must use the (#operation/updateMTOServiceItem) endpoint. + +**ISLH - International Shipping & Linehaul** +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + +``` + +**IHPK - International HHG Pack** +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + +``` + +**IHUPK - International HHG Unpack** +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + +``` + +**POEFSC - International Port of Embarkation Fuel Surcharge** + + **NOTE**: POEFSC requires `ActualPickupDate` to be updated on the shipment & `POELocation` on the service item. + +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + +``` + +**PODFSC - International Port of Debarkation Fuel Surcharge** +**NOTE**: PODFSC requires `ActualPickupDate` to be updated on the shipment & `PODLocation` on the service item. +```json + + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` --- */ diff --git a/swagger-def/prime.yaml b/swagger-def/prime.yaml index 5fcb6cdd5e3..61f19f25a93 100644 --- a/swagger-def/prime.yaml +++ b/swagger-def/prime.yaml @@ -952,7 +952,17 @@ paths: **NOTE**: In order to create a payment request for most service items, the shipment *must* be updated with the `PrimeActualWeight` value via [updateMTOShipment](#operation/updateMTOShipment). - **FSC - Fuel Surcharge** service items require `ActualPickupDate` to be updated on the shipment. + If `WeightBilled` is not provided then the full shipment weight (`PrimeActualWeight`) will be considered in the calculation. + + **NOTE**: Diversions have a unique calcuation for payment requests without a `WeightBilled` parameter. + + If you created a payment request for a diversion and `WeightBilled` is not provided, then the following will be used in the calculation: + - The lowest shipment weight (`PrimeActualWeight`) found in the diverted shipment chain. + - The lowest reweigh weight found in the diverted shipment chain. + + The diverted shipment chain is created by referencing the `diversion` boolean, `divertedFromShipmentId` UUID, and matching destination to pickup addresses. + If the chain cannot be established it will fall back to the `PrimeActualWeight` of the current shipment. This is utilized because diverted shipments are all one single shipment, but going to different locations. + The lowest weight found is the true shipment weight, and thus we search the chain of shipments for the lowest weight found. A service item can be on several payment requests in the case of partial payment requests and payments. @@ -979,19 +989,102 @@ paths: } ``` - SIT Service Items & Accepted Payment Request Parameters: + Domestic Basic Service Items & Accepted Payment Request Parameters: --- - If `WeightBilled` is not provided then the full shipment weight (`PrimeActualWeight`) will be considered in the calculation. - **NOTE**: Diversions have a unique calcuation for payment requests without a `WeightBilled` parameter. + **DLH - Domestic Linehaul** + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` - If you created a payment request for a diversion and `WeightBilled` is not provided, then the following will be used in the calculation: - - The lowest shipment weight (`PrimeActualWeight`) found in the diverted shipment chain. - - The lowest reweigh weight found in the diverted shipment chain. + **DSH - Domestic Shorthaul** + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` - The diverted shipment chain is created by referencing the `diversion` boolean, `divertedFromShipmentId` UUID, and matching destination to pickup addresses. - If the chain cannot be established it will fall back to the `PrimeActualWeight` of the current shipment. This is utilized because diverted shipments are all one single shipment, but going to different locations. - The lowest weight found is the true shipment weight, and thus we search the chain of shipments for the lowest weight found. + **FSC - Fuel Surcharge** + **NOTE**: FSC requires `ActualPickupDate` to be updated on the shipment. + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` + + **DUPK - Domestic Unpacking** + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` + + **DPK - Domestic Packing** + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` + + **DNPK - Domestic NTS Packing** + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` + + **DPK - Domestic Packing** + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` + + **DOP - Domestic Origin Price** + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` + + **DDP - Domestic Destination Price** + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` + + Domestic SIT Service Items & Accepted Payment Request Parameters: + --- **DOFSIT - Domestic origin 1st day SIT** ```json @@ -1092,6 +1185,64 @@ paths: ] ``` --- + + International Basic Service Items & Accepted Payment Request Parameters: + --- + Just like domestic shipments & service items, if `WeightBilled` is not provided then the full shipment weight (`PrimeActualWeight`) will be considered in the calculation. + **NOTE**: `POEFSC` & `PODFSC` service items must have a port associated on the service item in order to successfully add it to a payment request. To update the port of a service item, you must use the (#operation/updateMTOServiceItem) endpoint. + + **ISLH - International Shipping & Linehaul** + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` + + **IHPK - International HHG Pack** + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` + + **IHUPK - International HHG Unpack** + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` + + **POEFSC - International Port of Embarkation Fuel Surcharge** + **NOTE**: POEFSC requires `ActualPickupDate` to be updated on the shipment & `POELocation` on the service item. + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` + + **PODFSC - International Port of Debarkation Fuel Surcharge** + **NOTE**: PODFSC requires `ActualPickupDate` to be updated on the shipment & `PODLocation` on the service item. + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` + --- operationId: createPaymentRequest tags: - paymentRequest diff --git a/swagger/prime.yaml b/swagger/prime.yaml index 83ef52b06c7..2f7082e307b 100644 --- a/swagger/prime.yaml +++ b/swagger/prime.yaml @@ -1214,8 +1214,34 @@ paths: [updateMTOShipment](#operation/updateMTOShipment). - **FSC - Fuel Surcharge** service items require `ActualPickupDate` to be - updated on the shipment. + If `WeightBilled` is not provided then the full shipment weight + (`PrimeActualWeight`) will be considered in the calculation. + + + **NOTE**: Diversions have a unique calcuation for payment requests + without a `WeightBilled` parameter. + + + If you created a payment request for a diversion and `WeightBilled` is + not provided, then the following will be used in the calculation: + + - The lowest shipment weight (`PrimeActualWeight`) found in the diverted + shipment chain. + + - The lowest reweigh weight found in the diverted shipment chain. + + + The diverted shipment chain is created by referencing the `diversion` + boolean, `divertedFromShipmentId` UUID, and matching destination to + pickup addresses. + + If the chain cannot be established it will fall back to the + `PrimeActualWeight` of the current shipment. This is utilized because + diverted shipments are all one single shipment, but going to different + locations. + + The lowest weight found is the true shipment weight, and thus we search + the chain of shipments for the lowest weight found. A service item can be on several payment requests in the case of partial @@ -1250,38 +1276,124 @@ paths: ``` - SIT Service Items & Accepted Payment Request Parameters: + Domestic Basic Service Items & Accepted Payment Request Parameters: --- - If `WeightBilled` is not provided then the full shipment weight - (`PrimeActualWeight`) will be considered in the calculation. + **DLH - Domestic Linehaul** - **NOTE**: Diversions have a unique calcuation for payment requests - without a `WeightBilled` parameter. + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` - If you created a payment request for a diversion and `WeightBilled` is - not provided, then the following will be used in the calculation: + **DSH - Domestic Shorthaul** - - The lowest shipment weight (`PrimeActualWeight`) found in the diverted - shipment chain. + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` - - The lowest reweigh weight found in the diverted shipment chain. + **FSC - Fuel Surcharge** - The diverted shipment chain is created by referencing the `diversion` - boolean, `divertedFromShipmentId` UUID, and matching destination to - pickup addresses. + **NOTE**: FSC requires `ActualPickupDate` to be updated on the shipment. - If the chain cannot be established it will fall back to the - `PrimeActualWeight` of the current shipment. This is utilized because - diverted shipments are all one single shipment, but going to different - locations. + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` + + + **DUPK - Domestic Unpacking** + + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` + + + **DPK - Domestic Packing** + + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` + + + **DNPK - Domestic NTS Packing** + + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` + + + **DPK - Domestic Packing** + + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` - The lowest weight found is the true shipment weight, and thus we search - the chain of shipments for the lowest weight found. + + **DOP - Domestic Origin Price** + + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` + + + **DDP - Domestic Destination Price** + + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` + + + Domestic SIT Service Items & Accepted Payment Request Parameters: + + --- **DOFSIT - Domestic origin 1st day SIT** @@ -1410,6 +1522,85 @@ paths: ``` --- + + + International Basic Service Items & Accepted Payment Request Parameters: + + --- + + Just like domestic shipments & service items, if `WeightBilled` is not + provided then the full shipment weight (`PrimeActualWeight`) will be + considered in the calculation. + + **NOTE**: `POEFSC` & `PODFSC` service items must have a port associated + on the service item in order to successfully add it to a payment + request. To update the port of a service item, you must use the + (#operation/updateMTOServiceItem) endpoint. + + + **ISLH - International Shipping & Linehaul** + + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` + + + **IHPK - International HHG Pack** + + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` + + + **IHUPK - International HHG Unpack** + + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` + + + **POEFSC - International Port of Embarkation Fuel Surcharge** + **NOTE**: POEFSC requires `ActualPickupDate` to be updated on the shipment & `POELocation` on the service item. + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` + + + **PODFSC - International Port of Debarkation Fuel Surcharge** + + **NOTE**: PODFSC requires `ActualPickupDate` to be updated on the + shipment & `PODLocation` on the service item. + + ```json + "params": [ + { + "key": "WeightBilled", + "value": "integer" + } + ] + ``` + + --- operationId: createPaymentRequest tags: - paymentRequest From 4890576da4e3179d2e4073491ef8ca81e2869374 Mon Sep 17 00:00:00 2001 From: Daniel Jordan Date: Mon, 6 Jan 2025 16:56:13 +0000 Subject: [PATCH 46/54] adding logic to use port ZIP for international shipments and ZIP lookups --- ...aram_values_to_service_params_table.up.sql | 2 +- pkg/models/service_item_param_key.go | 8 +-- .../distance_zip_lookup.go | 24 ++++++- .../distance_zip_lookup_test.go | 62 +++++++++++++++++++ ...port_name_lookup.go => port_zip_lookup.go} | 15 ++--- ...lookup_test.go => port_zip_lookup_test.go} | 18 +++--- .../service_param_value_lookups.go | 4 +- .../service_param_value_lookups_test.go | 8 +++ .../intl_port_fuel_surcharge_pricer.go | 2 +- .../intl_port_fuel_surcharge_pricer_test.go | 6 +- 10 files changed, 120 insertions(+), 29 deletions(-) rename pkg/payment_request/service_param_value_lookups/{port_name_lookup.go => port_zip_lookup.go} (50%) rename pkg/payment_request/service_param_value_lookups/{port_name_lookup_test.go => port_zip_lookup_test.go} (83%) diff --git a/migrations/app/schema/20241226173330_add_intl_param_values_to_service_params_table.up.sql b/migrations/app/schema/20241226173330_add_intl_param_values_to_service_params_table.up.sql index 859bbcb47b6..1186b75a7b2 100644 --- a/migrations/app/schema/20241226173330_add_intl_param_values_to_service_params_table.up.sql +++ b/migrations/app/schema/20241226173330_add_intl_param_values_to_service_params_table.up.sql @@ -3,7 +3,7 @@ DROP FUNCTION IF EXISTS get_rate_area_id(UUID, UUID); -- need to add in param keys for international shipments, this will be used to show breakdowns to the TIO INSERT INTO service_item_param_keys (id, key,description,type,origin,created_at,updated_at) VALUES - ('d9ad3878-4b94-4722-bbaf-d4b8080f339d','PortName','Name of the port for an international shipment','STRING','SYSTEM','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957'), + ('d9ad3878-4b94-4722-bbaf-d4b8080f339d','PortZip','ZIP of the port for an international shipment pickup or destination port','STRING','SYSTEM','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957'), ('597bb77e-0ce7-4ba2-9624-24300962625f','PerUnitCents','Per unit cents for a service item','INTEGER','SYSTEM','2024-12-26 15:55:50.041957','2024-12-26 15:55:50.041957'); -- inserting params for PODFSC diff --git a/pkg/models/service_item_param_key.go b/pkg/models/service_item_param_key.go index 868c6785b55..0c637cc7d92 100644 --- a/pkg/models/service_item_param_key.go +++ b/pkg/models/service_item_param_key.go @@ -63,8 +63,8 @@ const ( ServiceItemParamNameNumberDaysSIT ServiceItemParamName = "NumberDaysSIT" // ServiceItemParamNamePerUnitCents is the param key name PerUnitCents ServiceItemParamNamePerUnitCents ServiceItemParamName = "PerUnitCents" - // ServiceItemParamNamePortName is the param key name PortName - ServiceItemParamNamePortName ServiceItemParamName = "PortName" + // ServiceItemParamNamePortZip is the param key name PortZip + ServiceItemParamNamePortZip ServiceItemParamName = "PortZip" // ServiceItemParamNamePriceAreaDest is the param key name PriceAreaDest ServiceItemParamNamePriceAreaDest ServiceItemParamName = "PriceAreaDest" // ServiceItemParamNamePriceAreaIntlDest is the param key name PriceAreaIntlDest @@ -280,7 +280,7 @@ var ValidServiceItemParamNames = []ServiceItemParamName{ ServiceItemParamNameUncappedRequestTotal, ServiceItemParamNameLockedPriceCents, ServiceItemParamNamePerUnitCents, - ServiceItemParamNamePortName, + ServiceItemParamNamePortZip, } // ValidServiceItemParamNameStrings lists all valid service item param key names @@ -356,7 +356,7 @@ var ValidServiceItemParamNameStrings = []string{ string(ServiceItemParamNameUncappedRequestTotal), string(ServiceItemParamNameLockedPriceCents), string(ServiceItemParamNamePerUnitCents), - string(ServiceItemParamNamePortName), + string(ServiceItemParamNamePortZip), } // ValidServiceItemParamTypes lists all valid service item param types diff --git a/pkg/payment_request/service_param_value_lookups/distance_zip_lookup.go b/pkg/payment_request/service_param_value_lookups/distance_zip_lookup.go index 7b70c559d1d..7f05aa59ee9 100644 --- a/pkg/payment_request/service_param_value_lookups/distance_zip_lookup.go +++ b/pkg/payment_request/service_param_value_lookups/distance_zip_lookup.go @@ -49,6 +49,25 @@ func (r DistanceZipLookup) lookup(appCtx appcontext.AppContext, keyData *Service // Now calculate the distance between zips pickupZip := r.PickupAddress.PostalCode destinationZip := r.DestinationAddress.PostalCode + + // if the shipment is international, we need to change the respective ZIP to use the port ZIP and not the address ZIP + if mtoShipment.MarketCode == models.MarketCodeInternational { + portZip, portType, err := models.GetPortLocationInfoForShipment(appCtx.DB(), *mtoShipmentID) + if err != nil { + return "", err + } + if portZip != nil && portType != nil { + // if the port type is POEFSC this means the shipment is CONUS -> OCONUS (pickup -> port) + // if the port type is PODFSC this means the shipment is OCONUS -> CONUS (port -> destination) + if *portType == models.ReServiceCodePOEFSC.String() { + destinationZip = *portZip + } else if *portType == models.ReServiceCodePODFSC.String() { + pickupZip = *portZip + } + } else { + return "", apperror.NewNotFoundError(*mtoShipmentID, "looking for port ZIP for shipment") + } + } errorMsgForPickupZip := fmt.Sprintf("Shipment must have valid pickup zipcode. Received: %s", pickupZip) errorMsgForDestinationZip := fmt.Sprintf("Shipment must have valid destination zipcode. Received: %s", destinationZip) if len(pickupZip) < 5 { @@ -91,14 +110,15 @@ func (r DistanceZipLookup) lookup(appCtx appcontext.AppContext, keyData *Service } } - if mtoShipment.Distance != nil && mtoShipment.ShipmentType != models.MTOShipmentTypePPM { + internationalShipment := mtoShipment.MarketCode == models.MarketCodeInternational + if mtoShipment.Distance != nil && mtoShipment.ShipmentType != models.MTOShipmentTypePPM && !internationalShipment { return strconv.Itoa(mtoShipment.Distance.Int()), nil } if pickupZip == destinationZip { distanceMiles = 1 } else { - distanceMiles, err = planner.ZipTransitDistance(appCtx, pickupZip, destinationZip, false, false) + distanceMiles, err = planner.ZipTransitDistance(appCtx, pickupZip, destinationZip, false, internationalShipment) if err != nil { return "", err } diff --git a/pkg/payment_request/service_param_value_lookups/distance_zip_lookup_test.go b/pkg/payment_request/service_param_value_lookups/distance_zip_lookup_test.go index f40692f2f1c..f4fa18c8790 100644 --- a/pkg/payment_request/service_param_value_lookups/distance_zip_lookup_test.go +++ b/pkg/payment_request/service_param_value_lookups/distance_zip_lookup_test.go @@ -63,6 +63,68 @@ func (suite *ServiceParamValueLookupsSuite) TestDistanceLookup() { suite.Equal(unit.Miles(defaultZipDistance), *mtoShipment.Distance) }) + suite.Run("Calculate transit zip distance for international shipment with port data", func() { + testdatagen.MakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + StartDate: time.Now().Add(-24 * time.Hour), + EndDate: time.Now().Add(24 * time.Hour), + }, + }) + portLocation := factory.FetchPortLocation(suite.DB(), []factory.Customization{ + { + Model: models.Port{ + PortCode: "SEA", + }, + }, + }, nil) + mtoServiceItem := factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: models.ReService{ + Code: models.ReServiceCodePOEFSC, + }, + }, + { + Model: models.Address{ + PostalCode: "74133", + }, + Type: &factory.Addresses.PickupAddress, + }, + { + Model: models.MTOServiceItem{ + POELocationID: &portLocation.ID, + }, + }, + { + Model: models.MTOShipment{ + MarketCode: models.MarketCodeInternational, + }, + }, + }, []factory.Trait{ + factory.GetTraitAvailableToPrimeMove, + }) + + paymentRequest := factory.BuildPaymentRequest(suite.DB(), []factory.Customization{ + { + Model: mtoServiceItem.MoveTaskOrder, + LinkOnly: true, + }, + }, nil) + + paramLookup, err := ServiceParamLookupInitialize(suite.AppContextForTest(), suite.planner, mtoServiceItem, paymentRequest.ID, paymentRequest.MoveTaskOrderID, nil) + suite.FatalNoError(err) + + distanceStr, err := paramLookup.ServiceParamValue(suite.AppContextForTest(), key) + suite.FatalNoError(err) + expected := strconv.Itoa(defaultInternationalZipDistance) + suite.Equal(expected, distanceStr) + + var mtoShipment models.MTOShipment + err = suite.DB().Find(&mtoShipment, mtoServiceItem.MTOShipmentID) + suite.NoError(err) + + suite.Equal(unit.Miles(defaultInternationalZipDistance), *mtoShipment.Distance) + }) + suite.Run("Calculate zip distance lookup without a saved service item", func() { ppmShipment := factory.BuildPPMShipment(suite.DB(), nil, nil) diff --git a/pkg/payment_request/service_param_value_lookups/port_name_lookup.go b/pkg/payment_request/service_param_value_lookups/port_zip_lookup.go similarity index 50% rename from pkg/payment_request/service_param_value_lookups/port_name_lookup.go rename to pkg/payment_request/service_param_value_lookups/port_zip_lookup.go index a925ceaa099..3ea8be94315 100644 --- a/pkg/payment_request/service_param_value_lookups/port_name_lookup.go +++ b/pkg/payment_request/service_param_value_lookups/port_zip_lookup.go @@ -9,26 +9,27 @@ import ( "github.com/transcom/mymove/pkg/models" ) -// PortNameLookup does lookup on the shipment and finds the port name -type PortNameLookup struct { +// PortZipLookup does lookup on the shipment and finds the port zip +// The mileage calculated is from port <-> pickup/destination so this value is important +type PortZipLookup struct { ServiceItem models.MTOServiceItem } -func (p PortNameLookup) lookup(appCtx appcontext.AppContext, _ *ServiceItemParamKeyData) (string, error) { +func (p PortZipLookup) lookup(appCtx appcontext.AppContext, _ *ServiceItemParamKeyData) (string, error) { var portLocationID *uuid.UUID if p.ServiceItem.PODLocationID != nil { portLocationID = p.ServiceItem.PODLocationID } else if p.ServiceItem.POELocationID != nil { portLocationID = p.ServiceItem.POELocationID } else { - return "", fmt.Errorf("unable to find port location for service item id: %s", p.ServiceItem.ID) + return "", fmt.Errorf("unable to find port zip for service item id: %s", p.ServiceItem.ID) } var portLocation models.PortLocation err := appCtx.DB().Q(). - EagerPreload("Port"). + EagerPreload("UsPostRegionCity"). Where("id = $1", portLocationID).First(&portLocation) if err != nil { - return "", fmt.Errorf("unable to find port location with id %s", portLocationID) + return "", fmt.Errorf("unable to find port zip with id %s", portLocationID) } - return portLocation.Port.PortName, nil + return portLocation.UsPostRegionCity.UsprZipID, nil } diff --git a/pkg/payment_request/service_param_value_lookups/port_name_lookup_test.go b/pkg/payment_request/service_param_value_lookups/port_zip_lookup_test.go similarity index 83% rename from pkg/payment_request/service_param_value_lookups/port_name_lookup_test.go rename to pkg/payment_request/service_param_value_lookups/port_zip_lookup_test.go index a16b174dc1c..4410ba8e198 100644 --- a/pkg/payment_request/service_param_value_lookups/port_name_lookup_test.go +++ b/pkg/payment_request/service_param_value_lookups/port_zip_lookup_test.go @@ -10,8 +10,8 @@ import ( "github.com/transcom/mymove/pkg/testdatagen" ) -func (suite *ServiceParamValueLookupsSuite) TestPortNameLookup() { - key := models.ServiceItemParamNamePortName +func (suite *ServiceParamValueLookupsSuite) TestPortZipLookup() { + key := models.ServiceItemParamNamePortZip var mtoServiceItem models.MTOServiceItem setupTestData := func(serviceCode models.ReServiceCode, portID uuid.UUID) { testdatagen.MakeReContractYear(suite.DB(), testdatagen.Assertions{ @@ -49,7 +49,7 @@ func (suite *ServiceParamValueLookupsSuite) TestPortNameLookup() { } } - suite.Run("success - returns PortName value for POEFSC", func() { + suite.Run("success - returns PortZip value for POEFSC", func() { port := factory.FetchPortLocation(suite.DB(), []factory.Customization{ { Model: models.Port{ @@ -62,12 +62,12 @@ func (suite *ServiceParamValueLookupsSuite) TestPortNameLookup() { paramLookup, err := ServiceParamLookupInitialize(suite.AppContextForTest(), suite.planner, mtoServiceItem, uuid.Must(uuid.NewV4()), mtoServiceItem.MoveTaskOrderID, nil) suite.FatalNoError(err) - portName, err := paramLookup.ServiceParamValue(suite.AppContextForTest(), key) + portZip, err := paramLookup.ServiceParamValue(suite.AppContextForTest(), key) suite.FatalNoError(err) - suite.Equal(portName, port.Port.PortName) + suite.Equal(portZip, port.UsPostRegionCity.UsprZipID) }) - suite.Run("success - returns PortName value for PODFSC", func() { + suite.Run("success - returns PortZip value for PODFSC", func() { port := factory.FetchPortLocation(suite.DB(), []factory.Customization{ { Model: models.Port{ @@ -80,12 +80,12 @@ func (suite *ServiceParamValueLookupsSuite) TestPortNameLookup() { paramLookup, err := ServiceParamLookupInitialize(suite.AppContextForTest(), suite.planner, mtoServiceItem, uuid.Must(uuid.NewV4()), mtoServiceItem.MoveTaskOrderID, nil) suite.FatalNoError(err) - portName, err := paramLookup.ServiceParamValue(suite.AppContextForTest(), key) + portZip, err := paramLookup.ServiceParamValue(suite.AppContextForTest(), key) suite.FatalNoError(err) - suite.Equal(portName, port.Port.PortName) + suite.Equal(portZip, port.UsPostRegionCity.UsprZipID) }) - suite.Run("failure - no port value on service item", func() { + suite.Run("failure - no port zip on service item", func() { testdatagen.MakeReContractYear(suite.DB(), testdatagen.Assertions{ ReContractYear: models.ReContractYear{ StartDate: time.Now().Add(-24 * time.Hour), diff --git a/pkg/payment_request/service_param_value_lookups/service_param_value_lookups.go b/pkg/payment_request/service_param_value_lookups/service_param_value_lookups.go index c3669e5cb41..33775af842b 100644 --- a/pkg/payment_request/service_param_value_lookups/service_param_value_lookups.go +++ b/pkg/payment_request/service_param_value_lookups/service_param_value_lookups.go @@ -87,7 +87,7 @@ var ServiceItemParamsWithLookups = []models.ServiceItemParamName{ models.ServiceItemParamNameStandaloneCrateCap, models.ServiceItemParamNameLockedPriceCents, models.ServiceItemParamNamePerUnitCents, - models.ServiceItemParamNamePortName, + models.ServiceItemParamNamePortZip, } // ServiceParamLookupInitialize initializes service parameter lookup @@ -439,7 +439,7 @@ func InitializeLookups(appCtx appcontext.AppContext, shipment models.MTOShipment MTOShipment: shipment, } - lookups[models.ServiceItemParamNamePortName] = PortNameLookup{ + lookups[models.ServiceItemParamNamePortZip] = PortZipLookup{ ServiceItem: serviceItem, } diff --git a/pkg/payment_request/service_param_value_lookups/service_param_value_lookups_test.go b/pkg/payment_request/service_param_value_lookups/service_param_value_lookups_test.go index 1c9138e51ee..7b8307f147c 100644 --- a/pkg/payment_request/service_param_value_lookups/service_param_value_lookups_test.go +++ b/pkg/payment_request/service_param_value_lookups/service_param_value_lookups_test.go @@ -29,6 +29,7 @@ import ( ) const defaultZipDistance = 1234 +const defaultInternationalZipDistance = 1800 type ServiceParamValueLookupsSuite struct { *testingsuite.PopTestSuite @@ -49,6 +50,13 @@ func TestServiceParamValueLookupsSuite(t *testing.T) { false, false, ).Return(defaultZipDistance, nil) + planner.On("ZipTransitDistance", + mock.AnythingOfType("*appcontext.appContext"), + mock.Anything, + mock.Anything, + false, + true, + ).Return(defaultInternationalZipDistance, nil) ts := &ServiceParamValueLookupsSuite{ PopTestSuite: testingsuite.NewPopTestSuite(testingsuite.CurrentPackage(), testingsuite.WithPerTestTransaction()), diff --git a/pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer.go b/pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer.go index 3e6dcfe1bdd..e970f29a1c0 100644 --- a/pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer.go +++ b/pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer.go @@ -96,7 +96,7 @@ func (p portFuelSurchargePricer) PriceUsingParams(appCtx appcontext.AppContext, return unit.Cents(0), nil, err } - _, err = getParamString(params, models.ServiceItemParamNamePortName) + _, err = getParamString(params, models.ServiceItemParamNamePortZip) if err != nil { return unit.Cents(0), nil, err } diff --git a/pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer_test.go b/pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer_test.go index fa660bd796d..ce64f248c22 100644 --- a/pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer_test.go +++ b/pkg/services/ghcrateengine/intl_port_fuel_surcharge_pricer_test.go @@ -20,7 +20,7 @@ const ( intlPortFscWeightDistanceMultiplier = float64(0.000417) intlPortFscFuelPrice = unit.Millicents(281400) intlPortFscPriceCents = unit.Cents(2980) - intlPortFscPortName = "PORTLAND INTL" + intlPortFscPortZip = "99505" ) var intlPortFscActualPickupDate = time.Date(testdatagen.TestYear, time.June, 5, 7, 33, 11, 456, time.UTC) @@ -186,9 +186,9 @@ func (suite *GHCRateEngineServiceSuite) setupPortFuelSurchargeServiceItem() mode Value: fmt.Sprintf("%d", int(intlPortFscFuelPrice)), }, { - Key: models.ServiceItemParamNamePortName, + Key: models.ServiceItemParamNamePortZip, KeyType: models.ServiceItemParamTypeString, - Value: intlPortFscPortName, + Value: intlPortFscPortZip, }, }, nil, nil, ) From 24cf97188d8e910911fa109cc57712a1a2a3657e Mon Sep 17 00:00:00 2001 From: Daniel Jordan Date: Fri, 20 Dec 2024 22:39:44 +0000 Subject: [PATCH 47/54] initial commit, functonality added, tests added --- migrations/app/migrations_manifest.txt | 1 + ...4_add_destination_gbloc_db_function.up.sql | 85 ++++++++++++++++ pkg/gen/ghcapi/embedded_spec.go | 10 ++ pkg/gen/ghcmessages/address.go | 20 ++++ pkg/gen/internalapi/embedded_spec.go | 10 ++ pkg/gen/internalmessages/address.go | 20 ++++ pkg/gen/pptasapi/embedded_spec.go | 10 ++ pkg/gen/pptasmessages/address.go | 20 ++++ pkg/gen/primeapi/embedded_spec.go | 10 ++ pkg/gen/primemessages/address.go | 20 ++++ pkg/gen/primev2api/embedded_spec.go | 10 ++ pkg/gen/primev2messages/address.go | 20 ++++ pkg/gen/primev3api/embedded_spec.go | 10 ++ pkg/gen/primev3messages/address.go | 20 ++++ .../primeapiv3/payloads/model_to_payload.go | 21 ++-- .../payloads/model_to_payload_test.go | 53 +++++----- pkg/models/address.go | 1 + pkg/models/mto_shipments.go | 20 ++++ pkg/models/mto_shipments_test.go | 97 +++++++++++++++++++ .../move_task_order_fetcher.go | 12 +++ .../move_task_order_fetcher_test.go | 57 +++++++++++ swagger-def/definitions/Address.yaml | 4 + swagger/ghc.yaml | 4 + swagger/internal.yaml | 4 + swagger/pptas.yaml | 4 + swagger/prime.yaml | 4 + swagger/prime_v2.yaml | 4 + swagger/prime_v3.yaml | 4 + 28 files changed, 521 insertions(+), 34 deletions(-) create mode 100644 migrations/app/schema/20241220213134_add_destination_gbloc_db_function.up.sql diff --git a/migrations/app/migrations_manifest.txt b/migrations/app/migrations_manifest.txt index 32afaa072da..222544bddb1 100644 --- a/migrations/app/migrations_manifest.txt +++ b/migrations/app/migrations_manifest.txt @@ -1058,6 +1058,7 @@ 20241217180136_add_AK_zips_to_zip3_distances.up.sql 20241218201833_add_PPPO_BASE_ELIZABETH.up.sql 20241220171035_add_additional_AK_zips_to_zip3_distances.up.sql +20241220213134_add_destination_gbloc_db_function.up.sql 20241227153723_remove_empty_string_emplid_values.up.sql 20241227202424_insert_transportation_offices_camp_pendelton.up.sql 20241230190638_remove_AK_zips_from_zip3.up.sql diff --git a/migrations/app/schema/20241220213134_add_destination_gbloc_db_function.up.sql b/migrations/app/schema/20241220213134_add_destination_gbloc_db_function.up.sql new file mode 100644 index 00000000000..bd7d9b1db5d --- /dev/null +++ b/migrations/app/schema/20241220213134_add_destination_gbloc_db_function.up.sql @@ -0,0 +1,85 @@ +-- this function will handle getting the destination GBLOC associated with a shipment's destination address +-- this only applies to OCONUS destination addresses on a shipment, but this also checks domestic shipments +CREATE OR REPLACE FUNCTION get_destination_gbloc_for_shipment(shipment_id UUID) +RETURNS TEXT AS $$ +DECLARE + service_member_affiliation TEXT; + zip TEXT; + gbloc_result TEXT; + alaska_zone_ii BOOLEAN; + market_code TEXT; +BEGIN + -- get the shipment's market_code + SELECT ms.market_code + INTO market_code + FROM mto_shipments ms + WHERE ms.id = shipment_id; + + -- if it's a domestic shipment, use postal_code_to_gblocs + IF market_code = 'd' THEN + SELECT upc.uspr_zip_id + INTO zip + FROM addresses a + JOIN us_post_region_cities upc ON a.us_post_region_cities_id = upc.id + WHERE a.id = (SELECT destination_address_id FROM mto_shipments WHERE id = shipment_id); + + SELECT gbloc + INTO gbloc_result + FROM postal_code_to_gblocs + WHERE postal_code = zip + LIMIT 1; + + IF gbloc_result IS NULL THEN + RETURN NULL; + END IF; + + RETURN gbloc_result; + + ELSEIF market_code = 'i' THEN + -- if it's 'i' then we need to check for some exceptions + SELECT sm.affiliation + INTO service_member_affiliation + FROM service_members sm + JOIN orders o ON o.service_member_id = sm.id + JOIN moves m ON m.orders_id = o.id + JOIN mto_shipments ms ON ms.move_id = m.id + WHERE ms.id = shipment_id; + + SELECT upc.uspr_zip_id + INTO zip + FROM addresses a + JOIN us_post_region_cities upc ON a.us_post_region_cities_id = upc.id + WHERE a.id = (SELECT destination_address_id FROM mto_shipments WHERE id = shipment_id); + + -- check if the postal code (uspr_zip_id) is in Alaska Zone II + SELECT EXISTS ( + SELECT 1 + FROM re_oconus_rate_areas ro + JOIN re_rate_areas ra ON ro.rate_area_id = ra.id + JOIN us_post_region_cities upc ON upc.id = ro.us_post_region_cities_id + WHERE upc.uspr_zip_id = zip + AND ra.code = 'US8190100' -- Alaska Zone II Code + ) + INTO alaska_zone_ii; + + -- if the service member is USAF or USSF and the address is in Alaska Zone II, return 'MBFL' + IF (service_member_affiliation = 'AIR_FORCE' OR service_member_affiliation = 'SPACE_FORCE') AND alaska_zone_ii THEN + RETURN 'MBFL'; + END IF; + + -- for all other branches except USMC, return the gbloc from the postal_code_to_gbloc table based on the zip + SELECT gbloc + INTO gbloc_result + FROM postal_code_to_gblocs + WHERE postal_code = zip + LIMIT 1; + + IF gbloc_result IS NULL THEN + RETURN NULL; + END IF; + + RETURN gbloc_result; + END IF; + +END; +$$ LANGUAGE plpgsql; diff --git a/pkg/gen/ghcapi/embedded_spec.go b/pkg/gen/ghcapi/embedded_spec.go index 361f666c222..3b8e14d3e66 100644 --- a/pkg/gen/ghcapi/embedded_spec.go +++ b/pkg/gen/ghcapi/embedded_spec.go @@ -6403,6 +6403,11 @@ func init() { "x-nullable": true, "example": "LOS ANGELES" }, + "destinationGbloc": { + "type": "string", + "pattern": "^[A-Z]{4}$", + "x-nullable": true + }, "eTag": { "type": "string", "readOnly": true @@ -23143,6 +23148,11 @@ func init() { "x-nullable": true, "example": "LOS ANGELES" }, + "destinationGbloc": { + "type": "string", + "pattern": "^[A-Z]{4}$", + "x-nullable": true + }, "eTag": { "type": "string", "readOnly": true diff --git a/pkg/gen/ghcmessages/address.go b/pkg/gen/ghcmessages/address.go index 47148e32cf7..42bd1d8d69e 100644 --- a/pkg/gen/ghcmessages/address.go +++ b/pkg/gen/ghcmessages/address.go @@ -36,6 +36,10 @@ type Address struct { // Example: LOS ANGELES County *string `json:"county,omitempty"` + // destination gbloc + // Pattern: ^[A-Z]{4}$ + DestinationGbloc *string `json:"destinationGbloc,omitempty"` + // e tag // Read Only: true ETag string `json:"eTag,omitempty"` @@ -91,6 +95,10 @@ func (m *Address) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validateDestinationGbloc(formats); err != nil { + res = append(res, err) + } + if err := m.validateID(formats); err != nil { res = append(res, err) } @@ -138,6 +146,18 @@ func (m *Address) validateCountry(formats strfmt.Registry) error { return nil } +func (m *Address) validateDestinationGbloc(formats strfmt.Registry) error { + if swag.IsZero(m.DestinationGbloc) { // not required + return nil + } + + if err := validate.Pattern("destinationGbloc", "body", *m.DestinationGbloc, `^[A-Z]{4}$`); err != nil { + return err + } + + return nil +} + func (m *Address) validateID(formats strfmt.Registry) error { if swag.IsZero(m.ID) { // not required return nil diff --git a/pkg/gen/internalapi/embedded_spec.go b/pkg/gen/internalapi/embedded_spec.go index c1351734062..53aee4aa8cf 100644 --- a/pkg/gen/internalapi/embedded_spec.go +++ b/pkg/gen/internalapi/embedded_spec.go @@ -3363,6 +3363,11 @@ func init() { "x-nullable": true, "example": "LOS ANGELES" }, + "destinationGbloc": { + "type": "string", + "pattern": "^[A-Z]{4}$", + "x-nullable": true + }, "eTag": { "type": "string", "readOnly": true @@ -12482,6 +12487,11 @@ func init() { "x-nullable": true, "example": "LOS ANGELES" }, + "destinationGbloc": { + "type": "string", + "pattern": "^[A-Z]{4}$", + "x-nullable": true + }, "eTag": { "type": "string", "readOnly": true diff --git a/pkg/gen/internalmessages/address.go b/pkg/gen/internalmessages/address.go index 529cc0d7110..733df1c0680 100644 --- a/pkg/gen/internalmessages/address.go +++ b/pkg/gen/internalmessages/address.go @@ -36,6 +36,10 @@ type Address struct { // Example: LOS ANGELES County *string `json:"county,omitempty"` + // destination gbloc + // Pattern: ^[A-Z]{4}$ + DestinationGbloc *string `json:"destinationGbloc,omitempty"` + // e tag // Read Only: true ETag string `json:"eTag,omitempty"` @@ -91,6 +95,10 @@ func (m *Address) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validateDestinationGbloc(formats); err != nil { + res = append(res, err) + } + if err := m.validateID(formats); err != nil { res = append(res, err) } @@ -138,6 +146,18 @@ func (m *Address) validateCountry(formats strfmt.Registry) error { return nil } +func (m *Address) validateDestinationGbloc(formats strfmt.Registry) error { + if swag.IsZero(m.DestinationGbloc) { // not required + return nil + } + + if err := validate.Pattern("destinationGbloc", "body", *m.DestinationGbloc, `^[A-Z]{4}$`); err != nil { + return err + } + + return nil +} + func (m *Address) validateID(formats strfmt.Registry) error { if swag.IsZero(m.ID) { // not required return nil diff --git a/pkg/gen/pptasapi/embedded_spec.go b/pkg/gen/pptasapi/embedded_spec.go index 1757ac556cc..fc54f37df09 100644 --- a/pkg/gen/pptasapi/embedded_spec.go +++ b/pkg/gen/pptasapi/embedded_spec.go @@ -114,6 +114,11 @@ func init() { "x-nullable": true, "example": "LOS ANGELES" }, + "destinationGbloc": { + "type": "string", + "pattern": "^[A-Z]{4}$", + "x-nullable": true + }, "eTag": { "type": "string", "readOnly": true @@ -1008,6 +1013,11 @@ func init() { "x-nullable": true, "example": "LOS ANGELES" }, + "destinationGbloc": { + "type": "string", + "pattern": "^[A-Z]{4}$", + "x-nullable": true + }, "eTag": { "type": "string", "readOnly": true diff --git a/pkg/gen/pptasmessages/address.go b/pkg/gen/pptasmessages/address.go index 1e53ba6d230..0e5a9af985a 100644 --- a/pkg/gen/pptasmessages/address.go +++ b/pkg/gen/pptasmessages/address.go @@ -36,6 +36,10 @@ type Address struct { // Example: LOS ANGELES County *string `json:"county,omitempty"` + // destination gbloc + // Pattern: ^[A-Z]{4}$ + DestinationGbloc *string `json:"destinationGbloc,omitempty"` + // e tag // Read Only: true ETag string `json:"eTag,omitempty"` @@ -91,6 +95,10 @@ func (m *Address) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validateDestinationGbloc(formats); err != nil { + res = append(res, err) + } + if err := m.validateID(formats); err != nil { res = append(res, err) } @@ -138,6 +146,18 @@ func (m *Address) validateCountry(formats strfmt.Registry) error { return nil } +func (m *Address) validateDestinationGbloc(formats strfmt.Registry) error { + if swag.IsZero(m.DestinationGbloc) { // not required + return nil + } + + if err := validate.Pattern("destinationGbloc", "body", *m.DestinationGbloc, `^[A-Z]{4}$`); err != nil { + return err + } + + return nil +} + func (m *Address) validateID(formats strfmt.Registry) error { if swag.IsZero(m.ID) { // not required return nil diff --git a/pkg/gen/primeapi/embedded_spec.go b/pkg/gen/primeapi/embedded_spec.go index 52039d783cc..9076c994d00 100644 --- a/pkg/gen/primeapi/embedded_spec.go +++ b/pkg/gen/primeapi/embedded_spec.go @@ -1214,6 +1214,11 @@ func init() { "x-nullable": true, "example": "LOS ANGELES" }, + "destinationGbloc": { + "type": "string", + "pattern": "^[A-Z]{4}$", + "x-nullable": true + }, "eTag": { "type": "string", "readOnly": true @@ -6108,6 +6113,11 @@ func init() { "x-nullable": true, "example": "LOS ANGELES" }, + "destinationGbloc": { + "type": "string", + "pattern": "^[A-Z]{4}$", + "x-nullable": true + }, "eTag": { "type": "string", "readOnly": true diff --git a/pkg/gen/primemessages/address.go b/pkg/gen/primemessages/address.go index 2fe5ba87adb..4ff5b6f7932 100644 --- a/pkg/gen/primemessages/address.go +++ b/pkg/gen/primemessages/address.go @@ -36,6 +36,10 @@ type Address struct { // Example: LOS ANGELES County *string `json:"county,omitempty"` + // destination gbloc + // Pattern: ^[A-Z]{4}$ + DestinationGbloc *string `json:"destinationGbloc,omitempty"` + // e tag // Read Only: true ETag string `json:"eTag,omitempty"` @@ -91,6 +95,10 @@ func (m *Address) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validateDestinationGbloc(formats); err != nil { + res = append(res, err) + } + if err := m.validateID(formats); err != nil { res = append(res, err) } @@ -138,6 +146,18 @@ func (m *Address) validateCountry(formats strfmt.Registry) error { return nil } +func (m *Address) validateDestinationGbloc(formats strfmt.Registry) error { + if swag.IsZero(m.DestinationGbloc) { // not required + return nil + } + + if err := validate.Pattern("destinationGbloc", "body", *m.DestinationGbloc, `^[A-Z]{4}$`); err != nil { + return err + } + + return nil +} + func (m *Address) validateID(formats strfmt.Registry) error { if swag.IsZero(m.ID) { // not required return nil diff --git a/pkg/gen/primev2api/embedded_spec.go b/pkg/gen/primev2api/embedded_spec.go index f0468e10884..f6ffd9298ba 100644 --- a/pkg/gen/primev2api/embedded_spec.go +++ b/pkg/gen/primev2api/embedded_spec.go @@ -399,6 +399,11 @@ func init() { "x-nullable": true, "example": "LOS ANGELES" }, + "destinationGbloc": { + "type": "string", + "pattern": "^[A-Z]{4}$", + "x-nullable": true + }, "eTag": { "type": "string", "readOnly": true @@ -4006,6 +4011,11 @@ func init() { "x-nullable": true, "example": "LOS ANGELES" }, + "destinationGbloc": { + "type": "string", + "pattern": "^[A-Z]{4}$", + "x-nullable": true + }, "eTag": { "type": "string", "readOnly": true diff --git a/pkg/gen/primev2messages/address.go b/pkg/gen/primev2messages/address.go index 631419ea719..2f1631a297c 100644 --- a/pkg/gen/primev2messages/address.go +++ b/pkg/gen/primev2messages/address.go @@ -36,6 +36,10 @@ type Address struct { // Example: LOS ANGELES County *string `json:"county,omitempty"` + // destination gbloc + // Pattern: ^[A-Z]{4}$ + DestinationGbloc *string `json:"destinationGbloc,omitempty"` + // e tag // Read Only: true ETag string `json:"eTag,omitempty"` @@ -91,6 +95,10 @@ func (m *Address) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validateDestinationGbloc(formats); err != nil { + res = append(res, err) + } + if err := m.validateID(formats); err != nil { res = append(res, err) } @@ -138,6 +146,18 @@ func (m *Address) validateCountry(formats strfmt.Registry) error { return nil } +func (m *Address) validateDestinationGbloc(formats strfmt.Registry) error { + if swag.IsZero(m.DestinationGbloc) { // not required + return nil + } + + if err := validate.Pattern("destinationGbloc", "body", *m.DestinationGbloc, `^[A-Z]{4}$`); err != nil { + return err + } + + return nil +} + func (m *Address) validateID(formats strfmt.Registry) error { if swag.IsZero(m.ID) { // not required return nil diff --git a/pkg/gen/primev3api/embedded_spec.go b/pkg/gen/primev3api/embedded_spec.go index 22f6c879b42..e788d625932 100644 --- a/pkg/gen/primev3api/embedded_spec.go +++ b/pkg/gen/primev3api/embedded_spec.go @@ -405,6 +405,11 @@ func init() { "x-nullable": true, "example": "LOS ANGELES" }, + "destinationGbloc": { + "type": "string", + "pattern": "^[A-Z]{4}$", + "x-nullable": true + }, "eTag": { "type": "string", "readOnly": true @@ -4697,6 +4702,11 @@ func init() { "x-nullable": true, "example": "LOS ANGELES" }, + "destinationGbloc": { + "type": "string", + "pattern": "^[A-Z]{4}$", + "x-nullable": true + }, "eTag": { "type": "string", "readOnly": true diff --git a/pkg/gen/primev3messages/address.go b/pkg/gen/primev3messages/address.go index edffd06b01c..43fbf3bc550 100644 --- a/pkg/gen/primev3messages/address.go +++ b/pkg/gen/primev3messages/address.go @@ -36,6 +36,10 @@ type Address struct { // Example: LOS ANGELES County *string `json:"county,omitempty"` + // destination gbloc + // Pattern: ^[A-Z]{4}$ + DestinationGbloc *string `json:"destinationGbloc,omitempty"` + // e tag // Read Only: true ETag string `json:"eTag,omitempty"` @@ -91,6 +95,10 @@ func (m *Address) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validateDestinationGbloc(formats); err != nil { + res = append(res, err) + } + if err := m.validateID(formats); err != nil { res = append(res, err) } @@ -138,6 +146,18 @@ func (m *Address) validateCountry(formats strfmt.Registry) error { return nil } +func (m *Address) validateDestinationGbloc(formats strfmt.Registry) error { + if swag.IsZero(m.DestinationGbloc) { // not required + return nil + } + + if err := validate.Pattern("destinationGbloc", "body", *m.DestinationGbloc, `^[A-Z]{4}$`); err != nil { + return err + } + + return nil +} + func (m *Address) validateID(formats strfmt.Registry) error { if swag.IsZero(m.ID) { // not required return nil diff --git a/pkg/handlers/primeapiv3/payloads/model_to_payload.go b/pkg/handlers/primeapiv3/payloads/model_to_payload.go index 4676e9b3d47..2b93dd3480f 100644 --- a/pkg/handlers/primeapiv3/payloads/model_to_payload.go +++ b/pkg/handlers/primeapiv3/payloads/model_to_payload.go @@ -231,16 +231,17 @@ func Address(address *models.Address) *primev3messages.Address { return nil } return &primev3messages.Address{ - ID: strfmt.UUID(address.ID.String()), - StreetAddress1: &address.StreetAddress1, - StreetAddress2: address.StreetAddress2, - StreetAddress3: address.StreetAddress3, - City: &address.City, - State: &address.State, - PostalCode: &address.PostalCode, - Country: Country(address.Country), - ETag: etag.GenerateEtag(address.UpdatedAt), - County: address.County, + ID: strfmt.UUID(address.ID.String()), + StreetAddress1: &address.StreetAddress1, + StreetAddress2: address.StreetAddress2, + StreetAddress3: address.StreetAddress3, + City: &address.City, + State: &address.State, + PostalCode: &address.PostalCode, + Country: Country(address.Country), + ETag: etag.GenerateEtag(address.UpdatedAt), + County: address.County, + DestinationGbloc: address.DestinationGbloc, } } diff --git a/pkg/handlers/primeapiv3/payloads/model_to_payload_test.go b/pkg/handlers/primeapiv3/payloads/model_to_payload_test.go index e6de847b908..8ef4396aa1d 100644 --- a/pkg/handlers/primeapiv3/payloads/model_to_payload_test.go +++ b/pkg/handlers/primeapiv3/payloads/model_to_payload_test.go @@ -144,12 +144,13 @@ func (suite *PayloadsSuite) TestMoveTaskOrder() { PostalCode: fairbanksAlaskaPostalCode, }, DestinationAddress: &models.Address{ - StreetAddress1: "123 Main St", - StreetAddress2: &streetAddress2, - StreetAddress3: &streetAddress3, - City: "Anchorage", - State: "AK", - PostalCode: anchorageAlaskaPostalCode, + StreetAddress1: "123 Main St", + StreetAddress2: &streetAddress2, + StreetAddress3: &streetAddress3, + City: "Anchorage", + State: "AK", + PostalCode: anchorageAlaskaPostalCode, + DestinationGbloc: models.StringPointer("JEAT"), }, }) newMove.MTOShipments = append(newMove.MTOShipments, models.MTOShipment{ @@ -162,12 +163,13 @@ func (suite *PayloadsSuite) TestMoveTaskOrder() { PostalCode: wasillaAlaskaPostalCode, }, DestinationAddress: &models.Address{ - StreetAddress1: "123 Main St", - StreetAddress2: &streetAddress2, - StreetAddress3: &streetAddress3, - City: "Wasilla", - State: "AK", - PostalCode: wasillaAlaskaPostalCode, + StreetAddress1: "123 Main St", + StreetAddress2: &streetAddress2, + StreetAddress3: &streetAddress3, + City: "Wasilla", + State: "AK", + PostalCode: wasillaAlaskaPostalCode, + DestinationGbloc: models.StringPointer("JEAT"), }, }) newMove.MTOShipments = append(newMove.MTOShipments, models.MTOShipment{ @@ -231,20 +233,22 @@ func (suite *PayloadsSuite) TestMoveTaskOrder() { }) newMove.MTOShipments = append(newMove.MTOShipments, models.MTOShipment{ PickupAddress: &models.Address{ - StreetAddress1: "123 Main St", - StreetAddress2: &streetAddress2, - StreetAddress3: &streetAddress3, - City: "Beverly Hills", - State: "CA", - PostalCode: "90210", + StreetAddress1: "123 Main St", + StreetAddress2: &streetAddress2, + StreetAddress3: &streetAddress3, + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + DestinationGbloc: models.StringPointer("JEAT"), }, DestinationAddress: &models.Address{ - StreetAddress1: "123 Main St", - StreetAddress2: &streetAddress2, - StreetAddress3: &streetAddress3, - City: "Beverly Hills", - State: "CA", - PostalCode: "90210", + StreetAddress1: "123 Main St", + StreetAddress2: &streetAddress2, + StreetAddress3: &streetAddress3, + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + DestinationGbloc: models.StringPointer("JEAT"), }, }) @@ -351,6 +355,7 @@ func (suite *PayloadsSuite) TestMoveTaskOrder() { } else { suite.NotNil(shipment.PickupAddress) suite.NotNil(shipment.DestinationAddress) + suite.NotNil(shipment.DestinationAddress.DestinationGbloc) if slices.Contains(expectedAlaskaPostalCodes, *shipment.PickupAddress.PostalCode) { ra, contains := shipmentPostalCodeRateAreaLookupMap[*shipment.PickupAddress.PostalCode] suite.True(contains) diff --git a/pkg/models/address.go b/pkg/models/address.go index e683f7771ab..6042ae53960 100644 --- a/pkg/models/address.go +++ b/pkg/models/address.go @@ -34,6 +34,7 @@ type Address struct { IsOconus *bool `json:"is_oconus" db:"is_oconus"` UsPostRegionCityID *uuid.UUID `json:"us_post_region_cities_id" db:"us_post_region_cities_id"` UsPostRegionCity *UsPostRegionCity `belongs_to:"us_post_region_cities" fk_id:"us_post_region_cities_id"` + DestinationGbloc *string `db:"-"` } // TableName overrides the table name used by Pop. diff --git a/pkg/models/mto_shipments.go b/pkg/models/mto_shipments.go index 9b914f98198..783188addaa 100644 --- a/pkg/models/mto_shipments.go +++ b/pkg/models/mto_shipments.go @@ -415,6 +415,26 @@ func UpdateEstimatedPricingForShipmentBasicServiceItems(db *pop.Connection, ship return nil } +// GetDestinationGblocForShipment gets the GBLOC associated with the shipment's destination address +// there are certain exceptions for OCONUS addresses in Alaska Zone II based on affiliation +func GetDestinationGblocForShipment(db *pop.Connection, shipmentID uuid.UUID) (*string, error) { + var gbloc *string + + err := db.RawQuery("SELECT * FROM get_destination_gbloc_for_shipment($1)", shipmentID). + First(&gbloc) + + if err != nil && err != sql.ErrNoRows { + return nil, fmt.Errorf("error fetching destination gbloc for shipment ID: %s with error %w", shipmentID, err) + } + + // return the ZIP code and port type, or nil if not found + if gbloc != nil { + return gbloc, nil + } + + return nil, nil +} + // Returns a Shipment for a given id func FetchShipmentByID(db *pop.Connection, shipmentID uuid.UUID) (*MTOShipment, error) { var mtoShipment MTOShipment diff --git a/pkg/models/mto_shipments_test.go b/pkg/models/mto_shipments_test.go index 0af9121e013..1b92fbb7035 100644 --- a/pkg/models/mto_shipments_test.go +++ b/pkg/models/mto_shipments_test.go @@ -339,3 +339,100 @@ func (suite *ModelSuite) TestFindShipmentByID() { suite.Equal(models.ErrFetchNotFound, err) }) } + +func (suite *ModelSuite) TestGetDestinationGblocForShipment() { + suite.Run("success - get GBLOC for USAF in AK Zone II", func() { + // Create a USAF move in Alaska Zone II + // this is a hard coded uuid that is a us_post_region_cities_id within AK Zone II + // this should always return MBFL + zone2UUID, err := uuid.FromString("66768964-e0de-41f3-b9be-7ef32e4ae2b4") + suite.FatalNoError(err) + airForce := models.AffiliationAIRFORCE + postalCode := "99501" + + destinationAddress := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + PostalCode: postalCode, + UsPostRegionCityID: &zone2UUID, + }, + }, + }, nil) + + move := factory.BuildAvailableToPrimeMove(suite.DB(), []factory.Customization{ + { + Model: models.ServiceMember{ + Affiliation: &airForce, + }, + }, + }, nil) + + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + MarketCode: models.MarketCodeInternational, + }, + }, + { + Model: move, + LinkOnly: true, + }, + { + Model: destinationAddress, + LinkOnly: true, + }, + }, nil) + + gbloc, err := models.GetDestinationGblocForShipment(suite.DB(), shipment.ID) + suite.NoError(err) + suite.NotNil(gbloc) + suite.Equal(*gbloc, "MBFL") + }) + suite.Run("success - get GBLOC for Army in AK Zone II", func() { + // Create an ARMY move in Alaska Zone II + zone2UUID, err := uuid.FromString("66768964-e0de-41f3-b9be-7ef32e4ae2b4") + suite.FatalNoError(err) + army := models.AffiliationARMY + postalCode := "99501" + // since we truncate the test db, we need to add the postal_code_to_gbloc value + factory.FetchOrBuildPostalCodeToGBLOC(suite.DB(), "99744", "JEAT") + + destinationAddress := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + PostalCode: postalCode, + UsPostRegionCityID: &zone2UUID, + }, + }, + }, nil) + + move := factory.BuildAvailableToPrimeMove(suite.DB(), []factory.Customization{ + { + Model: models.ServiceMember{ + Affiliation: &army, + }, + }, + }, nil) + + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + MarketCode: models.MarketCodeInternational, + }, + }, + { + Model: move, + LinkOnly: true, + }, + { + Model: destinationAddress, + LinkOnly: true, + }, + }, nil) + + gbloc, err := models.GetDestinationGblocForShipment(suite.DB(), shipment.ID) + suite.NoError(err) + suite.NotNil(gbloc) + suite.Equal(*gbloc, "JEAT") + }) +} diff --git a/pkg/services/move_task_order/move_task_order_fetcher.go b/pkg/services/move_task_order/move_task_order_fetcher.go index b63f5960290..b41d6bc9176 100644 --- a/pkg/services/move_task_order/move_task_order_fetcher.go +++ b/pkg/services/move_task_order/move_task_order_fetcher.go @@ -278,6 +278,18 @@ func (f moveTaskOrderFetcher) FetchMoveTaskOrder(appCtx appcontext.AppContext, s return &models.Move{}, apperror.NewQueryError("MobileHomeShipment", loadErrMH, "") } } + // we need to get the destination GBLOC associated with a shipment's destination address + // USMC always goes to the USMC GBLOC + if mto.MTOShipments[i].DestinationAddress != nil { + if *mto.Orders.ServiceMember.Affiliation == models.AffiliationMARINES { + *mto.MTOShipments[i].DestinationAddress.DestinationGbloc = "USMC" + } else { + mto.MTOShipments[i].DestinationAddress.DestinationGbloc, err = models.GetDestinationGblocForShipment(appCtx.DB(), mto.MTOShipments[i].ID) + if err != nil { + return &models.Move{}, apperror.NewQueryError("Error getting shipment GBLOC", err, "") + } + } + } filteredShipments = append(filteredShipments, mto.MTOShipments[i]) } mto.MTOShipments = filteredShipments diff --git a/pkg/services/move_task_order/move_task_order_fetcher_test.go b/pkg/services/move_task_order/move_task_order_fetcher_test.go index 9ba9f7a8ba2..fb59f8209f3 100644 --- a/pkg/services/move_task_order/move_task_order_fetcher_test.go +++ b/pkg/services/move_task_order/move_task_order_fetcher_test.go @@ -344,6 +344,63 @@ func (suite *MoveTaskOrderServiceSuite) TestMoveTaskOrderFetcher() { } }) + suite.Run("Success with Prime available move, returns destination GBLOC in shipment dest address", func() { + zone2UUID, err := uuid.FromString("66768964-e0de-41f3-b9be-7ef32e4ae2b4") + suite.FatalNoError(err) + army := models.AffiliationARMY + postalCode := "99501" + // since we truncate the test db, we need to add the postal_code_to_gbloc value + factory.FetchOrBuildPostalCodeToGBLOC(suite.DB(), "99744", "JEAT") + + destinationAddress := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + PostalCode: postalCode, + UsPostRegionCityID: &zone2UUID, + }, + }, + }, nil) + + move := factory.BuildAvailableToPrimeMove(suite.DB(), []factory.Customization{ + { + Model: models.ServiceMember{ + Affiliation: &army, + }, + }, + }, nil) + + factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + MarketCode: models.MarketCodeInternational, + }, + }, + { + Model: move, + LinkOnly: true, + }, + { + Model: destinationAddress, + LinkOnly: true, + }, + }, nil) + searchParams := services.MoveTaskOrderFetcherParams{ + IncludeHidden: false, + Locator: move.Locator, + ExcludeExternalShipments: true, + } + + actualMTO, err := mtoFetcher.FetchMoveTaskOrder(suite.AppContextForTest(), &searchParams) + suite.NoError(err) + suite.NotNil(actualMTO) + + if suite.Len(actualMTO.MTOShipments, 1) { + suite.Equal(move.ID.String(), actualMTO.ID.String()) + // the shipment should have a destination GBLOC value + suite.NotNil(actualMTO.MTOShipments[0].DestinationAddress.DestinationGbloc) + } + }) + suite.Run("Success with move that has only deleted shipments", func() { mtoWithAllShipmentsDeleted := factory.BuildMove(suite.DB(), nil, nil) factory.BuildMTOShipmentMinimal(suite.DB(), []factory.Customization{ diff --git a/swagger-def/definitions/Address.yaml b/swagger-def/definitions/Address.yaml index baa869f59b3..0c018795ee2 100644 --- a/swagger-def/definitions/Address.yaml +++ b/swagger-def/definitions/Address.yaml @@ -161,6 +161,10 @@ properties: type: string format: uuid example: c56a4180-65aa-42ec-a945-5fd21dec0538 + destinationGbloc: + type: string + pattern: '^[A-Z]{4}$' + x-nullable: true required: - streetAddress1 - city diff --git a/swagger/ghc.yaml b/swagger/ghc.yaml index ab568b434c8..2456656b1c6 100644 --- a/swagger/ghc.yaml +++ b/swagger/ghc.yaml @@ -8301,6 +8301,10 @@ definitions: type: string format: uuid example: c56a4180-65aa-42ec-a945-5fd21dec0538 + destinationGbloc: + type: string + pattern: ^[A-Z]{4}$ + x-nullable: true required: - streetAddress1 - city diff --git a/swagger/internal.yaml b/swagger/internal.yaml index 84097cd100a..e6026ff442e 100644 --- a/swagger/internal.yaml +++ b/swagger/internal.yaml @@ -2758,6 +2758,10 @@ definitions: type: string format: uuid example: c56a4180-65aa-42ec-a945-5fd21dec0538 + destinationGbloc: + type: string + pattern: ^[A-Z]{4}$ + x-nullable: true required: - streetAddress1 - city diff --git a/swagger/pptas.yaml b/swagger/pptas.yaml index e2aa9b3c525..6b4223b52f7 100644 --- a/swagger/pptas.yaml +++ b/swagger/pptas.yaml @@ -245,6 +245,10 @@ definitions: type: string format: uuid example: c56a4180-65aa-42ec-a945-5fd21dec0538 + destinationGbloc: + type: string + pattern: ^[A-Z]{4}$ + x-nullable: true required: - streetAddress1 - city diff --git a/swagger/prime.yaml b/swagger/prime.yaml index 83ef52b06c7..c42c5608559 100644 --- a/swagger/prime.yaml +++ b/swagger/prime.yaml @@ -2955,6 +2955,10 @@ definitions: type: string format: uuid example: c56a4180-65aa-42ec-a945-5fd21dec0538 + destinationGbloc: + type: string + pattern: ^[A-Z]{4}$ + x-nullable: true required: - streetAddress1 - city diff --git a/swagger/prime_v2.yaml b/swagger/prime_v2.yaml index 00c4e8d169b..815a6e87ad5 100644 --- a/swagger/prime_v2.yaml +++ b/swagger/prime_v2.yaml @@ -1566,6 +1566,10 @@ definitions: type: string format: uuid example: c56a4180-65aa-42ec-a945-5fd21dec0538 + destinationGbloc: + type: string + pattern: ^[A-Z]{4}$ + x-nullable: true required: - streetAddress1 - city diff --git a/swagger/prime_v3.yaml b/swagger/prime_v3.yaml index c478289f287..fcb9b1a0d40 100644 --- a/swagger/prime_v3.yaml +++ b/swagger/prime_v3.yaml @@ -1654,6 +1654,10 @@ definitions: type: string format: uuid example: c56a4180-65aa-42ec-a945-5fd21dec0538 + destinationGbloc: + type: string + pattern: ^[A-Z]{4}$ + x-nullable: true required: - streetAddress1 - city From 509f3fc29e37aecbe9fbed5372daee29c01406c9 Mon Sep 17 00:00:00 2001 From: Daniel Jordan Date: Fri, 20 Dec 2024 22:55:00 +0000 Subject: [PATCH 48/54] comments --- .../20241220213134_add_destination_gbloc_db_function.up.sql | 4 ++-- pkg/models/address.go | 2 +- pkg/models/mto_shipments.go | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/migrations/app/schema/20241220213134_add_destination_gbloc_db_function.up.sql b/migrations/app/schema/20241220213134_add_destination_gbloc_db_function.up.sql index bd7d9b1db5d..4b679eb8d48 100644 --- a/migrations/app/schema/20241220213134_add_destination_gbloc_db_function.up.sql +++ b/migrations/app/schema/20241220213134_add_destination_gbloc_db_function.up.sql @@ -1,5 +1,5 @@ -- this function will handle getting the destination GBLOC associated with a shipment's destination address --- this only applies to OCONUS destination addresses on a shipment, but this also checks domestic shipments +-- this only applies to OCONUS destination addresses on a shipment, but this can also checks domestic shipments CREATE OR REPLACE FUNCTION get_destination_gbloc_for_shipment(shipment_id UUID) RETURNS TEXT AS $$ DECLARE @@ -9,7 +9,7 @@ DECLARE alaska_zone_ii BOOLEAN; market_code TEXT; BEGIN - -- get the shipment's market_code + -- get the shipment's market code to determine conditionals SELECT ms.market_code INTO market_code FROM mto_shipments ms diff --git a/pkg/models/address.go b/pkg/models/address.go index 6042ae53960..d89a163c9aa 100644 --- a/pkg/models/address.go +++ b/pkg/models/address.go @@ -34,7 +34,7 @@ type Address struct { IsOconus *bool `json:"is_oconus" db:"is_oconus"` UsPostRegionCityID *uuid.UUID `json:"us_post_region_cities_id" db:"us_post_region_cities_id"` UsPostRegionCity *UsPostRegionCity `belongs_to:"us_post_region_cities" fk_id:"us_post_region_cities_id"` - DestinationGbloc *string `db:"-"` + DestinationGbloc *string `db:"-"` // this tells Pop not to look in the db for this value } // TableName overrides the table name used by Pop. diff --git a/pkg/models/mto_shipments.go b/pkg/models/mto_shipments.go index 783188addaa..9c2506e3292 100644 --- a/pkg/models/mto_shipments.go +++ b/pkg/models/mto_shipments.go @@ -427,7 +427,6 @@ func GetDestinationGblocForShipment(db *pop.Connection, shipmentID uuid.UUID) (* return nil, fmt.Errorf("error fetching destination gbloc for shipment ID: %s with error %w", shipmentID, err) } - // return the ZIP code and port type, or nil if not found if gbloc != nil { return gbloc, nil } From f6408b9868670883466e64a1238b5973088a338e Mon Sep 17 00:00:00 2001 From: Daniel Jordan Date: Fri, 20 Dec 2024 23:05:01 +0000 Subject: [PATCH 49/54] added usmc test --- .../move_task_order_fetcher.go | 2 +- .../move_task_order_fetcher_test.go | 39 +++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/pkg/services/move_task_order/move_task_order_fetcher.go b/pkg/services/move_task_order/move_task_order_fetcher.go index b41d6bc9176..1d30aef5ceb 100644 --- a/pkg/services/move_task_order/move_task_order_fetcher.go +++ b/pkg/services/move_task_order/move_task_order_fetcher.go @@ -282,7 +282,7 @@ func (f moveTaskOrderFetcher) FetchMoveTaskOrder(appCtx appcontext.AppContext, s // USMC always goes to the USMC GBLOC if mto.MTOShipments[i].DestinationAddress != nil { if *mto.Orders.ServiceMember.Affiliation == models.AffiliationMARINES { - *mto.MTOShipments[i].DestinationAddress.DestinationGbloc = "USMC" + mto.MTOShipments[i].DestinationAddress.DestinationGbloc = models.StringPointer("USMC") } else { mto.MTOShipments[i].DestinationAddress.DestinationGbloc, err = models.GetDestinationGblocForShipment(appCtx.DB(), mto.MTOShipments[i].ID) if err != nil { diff --git a/pkg/services/move_task_order/move_task_order_fetcher_test.go b/pkg/services/move_task_order/move_task_order_fetcher_test.go index fb59f8209f3..a80be4aa524 100644 --- a/pkg/services/move_task_order/move_task_order_fetcher_test.go +++ b/pkg/services/move_task_order/move_task_order_fetcher_test.go @@ -401,6 +401,45 @@ func (suite *MoveTaskOrderServiceSuite) TestMoveTaskOrderFetcher() { } }) + suite.Run("Success with Prime available move, returns USMC destination GBLOC for USMC move", func() { + usmc := models.AffiliationMARINES + + destinationAddress := factory.BuildAddress(suite.DB(), nil, nil) + move := factory.BuildAvailableToPrimeMove(suite.DB(), []factory.Customization{ + { + Model: models.ServiceMember{ + Affiliation: &usmc, + }, + }, + }, nil) + + factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + { + Model: destinationAddress, + LinkOnly: true, + }, + }, nil) + searchParams := services.MoveTaskOrderFetcherParams{ + IncludeHidden: false, + Locator: move.Locator, + ExcludeExternalShipments: true, + } + + actualMTO, err := mtoFetcher.FetchMoveTaskOrder(suite.AppContextForTest(), &searchParams) + suite.NoError(err) + suite.NotNil(actualMTO) + + if suite.Len(actualMTO.MTOShipments, 1) { + suite.Equal(move.ID.String(), actualMTO.ID.String()) + suite.NotNil(actualMTO.MTOShipments[0].DestinationAddress.DestinationGbloc) + suite.Equal(*actualMTO.MTOShipments[0].DestinationAddress.DestinationGbloc, "USMC") + } + }) + suite.Run("Success with move that has only deleted shipments", func() { mtoWithAllShipmentsDeleted := factory.BuildMove(suite.DB(), nil, nil) factory.BuildMTOShipmentMinimal(suite.DB(), []factory.Customization{ From 55c862950a184157476aa5296504e16d80afa02c Mon Sep 17 00:00:00 2001 From: Daniel Jordan Date: Thu, 26 Dec 2024 13:47:23 +0000 Subject: [PATCH 50/54] updated db func to include USMC check, added test --- ...4_add_destination_gbloc_db_function.up.sql | 5 ++ pkg/models/mto_shipments_test.go | 49 +++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/migrations/app/schema/20241220213134_add_destination_gbloc_db_function.up.sql b/migrations/app/schema/20241220213134_add_destination_gbloc_db_function.up.sql index 4b679eb8d48..0375f64d044 100644 --- a/migrations/app/schema/20241220213134_add_destination_gbloc_db_function.up.sql +++ b/migrations/app/schema/20241220213134_add_destination_gbloc_db_function.up.sql @@ -45,6 +45,11 @@ BEGIN JOIN mto_shipments ms ON ms.move_id = m.id WHERE ms.id = shipment_id; + -- if the service member is USMC, return 'USMC' + IF service_member_affiliation = 'MARINES' THEN + RETURN 'USMC'; + END IF; + SELECT upc.uspr_zip_id INTO zip FROM addresses a diff --git a/pkg/models/mto_shipments_test.go b/pkg/models/mto_shipments_test.go index 1b92fbb7035..4014aa90d12 100644 --- a/pkg/models/mto_shipments_test.go +++ b/pkg/models/mto_shipments_test.go @@ -435,4 +435,53 @@ func (suite *ModelSuite) TestGetDestinationGblocForShipment() { suite.NotNil(gbloc) suite.Equal(*gbloc, "JEAT") }) + suite.Run("success - get GBLOC for USMC in AK Zone II", func() { + // Create a USMC move in Alaska Zone II + // this should always return USMC + zone2UUID, err := uuid.FromString("66768964-e0de-41f3-b9be-7ef32e4ae2b4") + suite.FatalNoError(err) + usmc := models.AffiliationMARINES + postalCode := "99501" + // since we truncate the test db, we need to add the postal_code_to_gbloc value + // this doesn't matter to the db function because it will check for USMC but we are just verifying it won't be JEAT despite the zip matching + factory.FetchOrBuildPostalCodeToGBLOC(suite.DB(), "99744", "JEAT") + + destinationAddress := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + PostalCode: postalCode, + UsPostRegionCityID: &zone2UUID, + }, + }, + }, nil) + + move := factory.BuildAvailableToPrimeMove(suite.DB(), []factory.Customization{ + { + Model: models.ServiceMember{ + Affiliation: &usmc, + }, + }, + }, nil) + + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + MarketCode: models.MarketCodeInternational, + }, + }, + { + Model: move, + LinkOnly: true, + }, + { + Model: destinationAddress, + LinkOnly: true, + }, + }, nil) + + gbloc, err := models.GetDestinationGblocForShipment(suite.DB(), shipment.ID) + suite.NoError(err) + suite.NotNil(gbloc) + suite.Equal(*gbloc, "USMC") + }) } From 1ac32cdfad43652c60b0da90184b1d256a137ebf Mon Sep 17 00:00:00 2001 From: Daniel Jordan Date: Fri, 20 Dec 2024 23:10:03 +0000 Subject: [PATCH 51/54] if it broke, ya gotta fix From 93b4fbb74ea0bf76e8f81620d3265ea5890cc4e2 Mon Sep 17 00:00:00 2001 From: AaronW Date: Thu, 9 Jan 2025 20:37:04 +0000 Subject: [PATCH 52/54] brought in changes again since an auto-merge issue happened --- .../payloads/model_to_payload_test.go | 88 +++++++++++++ pkg/handlers/internalapi/orders.go | 6 + pkg/handlers/internalapi/orders_test.go | 124 ++++++++++++++++++ pkg/models/order.go | 43 ++++++ pkg/testdatagen/scenario/shared.go | 54 ++++++++ pkg/testdatagen/testharness/dispatch.go | 3 + pkg/testdatagen/testharness/make_move.go | 16 +++ .../servicesCounselingFlows.spec.js | 29 ++++ playwright/tests/utils/testharness.js | 11 ++ 9 files changed, 374 insertions(+) diff --git a/pkg/handlers/ghcapi/internal/payloads/model_to_payload_test.go b/pkg/handlers/ghcapi/internal/payloads/model_to_payload_test.go index ec3072d2ca0..40fb6e67ebf 100644 --- a/pkg/handlers/ghcapi/internal/payloads/model_to_payload_test.go +++ b/pkg/handlers/ghcapi/internal/payloads/model_to_payload_test.go @@ -9,6 +9,7 @@ import ( "github.com/transcom/mymove/pkg/factory" "github.com/transcom/mymove/pkg/gen/ghcmessages" + "github.com/transcom/mymove/pkg/gen/internalmessages" "github.com/transcom/mymove/pkg/handlers" "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/models/roles" @@ -315,6 +316,93 @@ func (suite *PayloadsSuite) TestShipmentAddressUpdate() { }) } +func (suite *PayloadsSuite) TestMoveWithGBLOC() { + defaultOrdersNumber := "ORDER3" + defaultTACNumber := "F8E1" + defaultDepartmentIndicator := "AIR_AND_SPACE_FORCE" + defaultGrade := "E_1" + defaultHasDependents := false + defaultSpouseHasProGear := false + defaultOrdersType := internalmessages.OrdersTypePERMANENTCHANGEOFSTATION + defaultOrdersTypeDetail := internalmessages.OrdersTypeDetail("HHG_PERMITTED") + defaultStatus := models.OrderStatusDRAFT + testYear := 2018 + defaultIssueDate := time.Date(testYear, time.March, 15, 0, 0, 0, 0, time.UTC) + defaultReportByDate := time.Date(testYear, time.August, 1, 0, 0, 0, 0, time.UTC) + defaultGBLOC := "KKFA" + + originDutyLocation := models.DutyLocation{ + Name: "Custom Origin", + } + originDutyLocationTOName := "origin duty location transportation office" + firstName := "customFirst" + lastName := "customLast" + serviceMember := models.ServiceMember{ + FirstName: &firstName, + LastName: &lastName, + } + uploadedOrders := models.Document{ + ID: uuid.Must(uuid.NewV4()), + } + dependents := 7 + entitlement := models.Entitlement{ + TotalDependents: &dependents, + } + amendedOrders := models.Document{ + ID: uuid.Must(uuid.NewV4()), + } + // Create order + order := factory.BuildOrder(suite.DB(), []factory.Customization{ + { + Model: originDutyLocation, + Type: &factory.DutyLocations.OriginDutyLocation, + }, + { + Model: models.TransportationOffice{ + Name: originDutyLocationTOName, + }, + Type: &factory.TransportationOffices.OriginDutyLocation, + }, + { + Model: serviceMember, + }, + { + Model: uploadedOrders, + Type: &factory.Documents.UploadedOrders, + }, + { + Model: entitlement, + }, + { + Model: amendedOrders, + Type: &factory.Documents.UploadedAmendedOrders, + }, + }, nil) + + suite.Equal(defaultOrdersNumber, *order.OrdersNumber) + suite.Equal(defaultTACNumber, *order.TAC) + suite.Equal(defaultDepartmentIndicator, *order.DepartmentIndicator) + suite.Equal(defaultGrade, string(*order.Grade)) + suite.Equal(defaultHasDependents, order.HasDependents) + suite.Equal(defaultSpouseHasProGear, order.SpouseHasProGear) + suite.Equal(defaultOrdersType, order.OrdersType) + suite.Equal(defaultOrdersTypeDetail, *order.OrdersTypeDetail) + suite.Equal(defaultStatus, order.Status) + suite.Equal(defaultIssueDate, order.IssueDate) + suite.Equal(defaultReportByDate, order.ReportByDate) + suite.Equal(defaultGBLOC, *order.OriginDutyLocationGBLOC) + + suite.Equal(originDutyLocation.Name, order.OriginDutyLocation.Name) + suite.Equal(originDutyLocationTOName, order.OriginDutyLocation.TransportationOffice.Name) + suite.Equal(*serviceMember.FirstName, *order.ServiceMember.FirstName) + suite.Equal(*serviceMember.LastName, *order.ServiceMember.LastName) + suite.Equal(uploadedOrders.ID, order.UploadedOrdersID) + suite.Equal(uploadedOrders.ID, order.UploadedOrders.ID) + suite.Equal(*entitlement.TotalDependents, *order.Entitlement.TotalDependents) + suite.Equal(amendedOrders.ID, *order.UploadedAmendedOrdersID) + suite.Equal(amendedOrders.ID, order.UploadedAmendedOrders.ID) +} + func (suite *PayloadsSuite) TestWeightTicketUpload() { uploadID, _ := uuid.NewV4() testURL := "https://testurl.com" diff --git a/pkg/handlers/internalapi/orders.go b/pkg/handlers/internalapi/orders.go index c9b5125c827..3936dcb39e2 100644 --- a/pkg/handlers/internalapi/orders.go +++ b/pkg/handlers/internalapi/orders.go @@ -382,6 +382,12 @@ func (h UpdateOrdersHandler) Handle(params ordersop.UpdateOrdersParams) middlewa order.OriginDutyLocation = &originDutyLocation order.OriginDutyLocationID = &originDutyLocationID + originGBLOC, originGBLOCerr := models.FetchGBLOCForPostalCode(appCtx.DB(), originDutyLocation.Address.PostalCode) + if originGBLOCerr != nil { + return handlers.ResponseForError(appCtx.Logger(), originGBLOCerr), originGBLOCerr + } + order.OriginDutyLocationGBLOC = &originGBLOC.GBLOC + if payload.MoveID != "" { moveID, err := uuid.FromString(payload.MoveID.String()) diff --git a/pkg/handlers/internalapi/orders_test.go b/pkg/handlers/internalapi/orders_test.go index b2ad7897f8a..59df8daf475 100644 --- a/pkg/handlers/internalapi/orders_test.go +++ b/pkg/handlers/internalapi/orders_test.go @@ -723,6 +723,130 @@ func (suite *HandlerSuite) TestUpdateOrdersHandler() { }) } +func (suite *HandlerSuite) TestUpdateOrdersHandlerOriginPostalCodeAndGBLOC() { + factory.BuildPostalCodeToGBLOC(suite.DB(), []factory.Customization{ + { + Model: models.PostalCodeToGBLOC{ + PostalCode: "90210", + GBLOC: "KKFA", + }, + }, + }, nil) + factory.BuildPostalCodeToGBLOC(suite.DB(), []factory.Customization{ + { + Model: models.PostalCodeToGBLOC{ + PostalCode: "35023", + GBLOC: "CNNQ", + }, + }, + }, nil) + + firstAddress := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + PostalCode: "90210", + }, + }, + }, nil) + updatedAddress := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + PostalCode: "35023", + }, + }, + }, nil) + dutyLocation := factory.BuildDutyLocation(suite.DB(), []factory.Customization{ + { + Model: models.DutyLocation{ + AddressID: firstAddress.ID, + }, + }, + }, nil) + updatedDutyLocation := factory.BuildDutyLocation(suite.DB(), []factory.Customization{ + { + Model: models.DutyLocation{ + AddressID: updatedAddress.ID, + }, + }, + }, nil) + newDutyLocation := factory.BuildDutyLocation(suite.DB(), nil, nil) + + order := factory.BuildOrder(suite.DB(), []factory.Customization{ + { + Model: models.Order{ + OriginDutyLocationID: &dutyLocation.ID, + NewDutyLocationID: newDutyLocation.ID, + }, + }, + }, nil) + + fetchedOrder, err := models.FetchOrder(suite.DB(), order.ID) + suite.NoError(err) + + var fetchedPostalCode, fetchedGBLOC string + fetchedPostalCode, err = fetchedOrder.GetOriginPostalCode(suite.DB()) + suite.NoError(err) + fetchedGBLOC, err = fetchedOrder.GetOriginGBLOC(suite.DB()) + suite.NoError(err) + + suite.Equal("90210", fetchedPostalCode) + suite.Equal("KKFA", fetchedGBLOC) + + newOrdersType := internalmessages.OrdersTypePERMANENTCHANGEOFSTATION + issueDate := time.Date(2018, time.March, 10, 0, 0, 0, 0, time.UTC) + reportByDate := time.Date(2018, time.August, 1, 0, 0, 0, 0, time.UTC) + deptIndicator := internalmessages.DeptIndicatorAIRANDSPACEFORCE + + payload := &internalmessages.CreateUpdateOrders{ + OrdersType: &order.OrdersType, + NewDutyLocationID: handlers.FmtUUID(order.NewDutyLocationID), + OriginDutyLocationID: *handlers.FmtUUID(updatedDutyLocation.ID), + IssueDate: handlers.FmtDate(issueDate), + ReportByDate: handlers.FmtDate(reportByDate), + DepartmentIndicator: &deptIndicator, + HasDependents: handlers.FmtBool(false), + SpouseHasProGear: handlers.FmtBool(false), + ServiceMemberID: handlers.FmtUUID(order.ServiceMemberID), + Grade: models.ServiceMemberGradeE4.Pointer(), + } + + path := fmt.Sprintf("/orders/%v", order.ID.String()) + req := httptest.NewRequest("PUT", path, nil) + req = suite.AuthenticateRequest(req, order.ServiceMember) + + params := ordersop.UpdateOrdersParams{ + HTTPRequest: req, + OrdersID: *handlers.FmtUUID(order.ID), + UpdateOrders: payload, + } + + fakeS3 := storageTest.NewFakeS3Storage(true) + handlerConfig := suite.HandlerConfig() + handlerConfig.SetFileStorer(fakeS3) + + handler := UpdateOrdersHandler{handlerConfig} + + response := handler.Handle(params) + + suite.IsType(&ordersop.UpdateOrdersOK{}, response) + + okResponse := response.(*ordersop.UpdateOrdersOK) + + suite.NoError(okResponse.Payload.Validate(strfmt.Default)) + suite.Equal(string(newOrdersType), string(*okResponse.Payload.OrdersType)) + + fetchedOrder, err = models.FetchOrder(suite.DB(), order.ID) + suite.NoError(err) + + fetchedPostalCode, err = fetchedOrder.GetOriginPostalCode(suite.DB()) + suite.NoError(err) + fetchedGBLOC = *fetchedOrder.OriginDutyLocationGBLOC + suite.NoError(err) + + suite.Equal("35023", fetchedPostalCode) + suite.Equal("CNNQ", fetchedGBLOC) +} + func (suite *HandlerSuite) TestEntitlementHelperFunc() { orderGrade := internalmessages.OrderPayGrade("O-3") int64Dependents := int64(2) diff --git a/pkg/models/order.go b/pkg/models/order.go index 9898e7f27e3..f5cc022e239 100644 --- a/pkg/models/order.go +++ b/pkg/models/order.go @@ -309,6 +309,49 @@ func (o *Order) CreateNewMove(db *pop.Connection, moveOptions MoveOptions) (*Mov return createNewMove(db, *o, moveOptions) } +/* + * GetOriginPostalCode returns the GBLOC for the postal code of the the origin duty location of the order. + */ +func (o Order) GetOriginPostalCode(db *pop.Connection) (string, error) { + // Since this requires looking up the order in the DB, the order must have an ID. This means, the order has to have been created first. + if uuid.UUID.IsNil(o.ID) { + return "", errors.WithMessage(ErrInvalidOrderID, "You must create the order in the DB before getting the origin GBLOC.") + } + + err := db.Load(&o, "OriginDutyLocation.Address") + if err != nil { + if err.Error() == RecordNotFoundErrorString { + return "", errors.WithMessage(err, "No Origin Duty Location was found for the order ID "+o.ID.String()) + } + return "", err + } + + return o.OriginDutyLocation.Address.PostalCode, nil +} + +/* + * GetOriginGBLOC returns the GBLOC for the postal code of the the origin duty location of the order. + */ +func (o Order) GetOriginGBLOC(db *pop.Connection) (string, error) { + // Since this requires looking up the order in the DB, the order must have an ID. This means, the order has to have been created first. + if uuid.UUID.IsNil(o.ID) { + return "", errors.WithMessage(ErrInvalidOrderID, "You must created the order in the DB before getting the destination GBLOC.") + } + + originPostalCode, err := o.GetOriginPostalCode(db) + if err != nil { + return "", err + } + + var originGBLOC PostalCodeToGBLOC + originGBLOC, err = FetchGBLOCForPostalCode(db, originPostalCode) + if err != nil { + return "", err + } + + return originGBLOC.GBLOC, nil +} + // IsComplete checks if orders have all fields necessary to approve a move func (o *Order) IsComplete() bool { diff --git a/pkg/testdatagen/scenario/shared.go b/pkg/testdatagen/scenario/shared.go index 15e8836bc1d..2b2990d46bc 100644 --- a/pkg/testdatagen/scenario/shared.go +++ b/pkg/testdatagen/scenario/shared.go @@ -10519,6 +10519,60 @@ func CreateNeedsServicesCounseling(appCtx appcontext.AppContext, ordersType inte return move } +/* +Create Needs Service Counseling in a non-default GBLOC - pass in orders with all required information, shipment type, destination type, locator +*/ +func CreateNeedsServicesCounselingInOtherGBLOC(appCtx appcontext.AppContext, ordersType internalmessages.OrdersType, shipmentType models.MTOShipmentType, destinationType *models.DestinationType, locator string) models.Move { + db := appCtx.DB() + originDutyLocationAddress := factory.BuildAddress(db, []factory.Customization{ + { + Model: models.Address{ + PostalCode: "35023", + }, + }, + }, nil) + originDutyLocation := factory.BuildDutyLocation(db, []factory.Customization{ + { + Model: models.DutyLocation{ + Name: "Test Location", + AddressID: originDutyLocationAddress.ID, + }, + }, + }, nil) + order := factory.BuildOrder(db, []factory.Customization{ + { + Model: originDutyLocation, + }, + }, nil) + move := factory.BuildMove(db, []factory.Customization{ + { + Model: order, + LinkOnly: true, + }, + { + Model: models.Move{ + Locator: locator, + Status: models.MoveStatusNeedsServiceCounseling, + }, + }, + }, nil) + + factory.BuildMTOShipment(db, []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + { + Model: models.MTOShipment{ + ShipmentType: shipmentType, + Status: models.MTOShipmentStatusSubmitted, + }, + }, + }, nil) + + return move +} + func CreateNeedsServicesCounselingWithAmendedOrders(appCtx appcontext.AppContext, userUploader *uploader.UserUploader, ordersType internalmessages.OrdersType, shipmentType models.MTOShipmentType, destinationType *models.DestinationType, locator string) models.Move { db := appCtx.DB() submittedAt := time.Now() diff --git a/pkg/testdatagen/testharness/dispatch.go b/pkg/testdatagen/testharness/dispatch.go index e9cf4c27d5f..dd4b421cc2c 100644 --- a/pkg/testdatagen/testharness/dispatch.go +++ b/pkg/testdatagen/testharness/dispatch.go @@ -50,6 +50,9 @@ var actionDispatcher = map[string]actionFunc{ "HHGMoveNeedsSC": func(appCtx appcontext.AppContext) testHarnessResponse { return MakeHHGMoveNeedsSC(appCtx) }, + "HHGMoveNeedsSCOtherGBLOC": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeHHGMoveNeedsSCOtherGBLOC(appCtx) + }, "HHGMoveAsUSMCNeedsSC": func(appCtx appcontext.AppContext) testHarnessResponse { return MakeHHGMoveNeedsServicesCounselingUSMC(appCtx) }, diff --git a/pkg/testdatagen/testharness/make_move.go b/pkg/testdatagen/testharness/make_move.go index ddd96329c38..aae7c83ecfa 100644 --- a/pkg/testdatagen/testharness/make_move.go +++ b/pkg/testdatagen/testharness/make_move.go @@ -4013,6 +4013,22 @@ func MakeHHGMoveNeedsSC(appCtx appcontext.AppContext) models.Move { return *newmove } +// MakeHHGMoveNeedsSCOtherGBLOC creates an fully ready move needing SC approval in a non-default GBLOC +func MakeHHGMoveNeedsSCOtherGBLOC(appCtx appcontext.AppContext) models.Move { + pcos := internalmessages.OrdersTypePERMANENTCHANGEOFSTATION + hhg := models.MTOShipmentTypeHHG + locator := models.GenerateLocator() + move := scenario.CreateNeedsServicesCounselingInOtherGBLOC(appCtx, pcos, hhg, nil, locator) + + // re-fetch the move so that we ensure we have exactly what is in + // the db + newmove, err := models.FetchMove(appCtx.DB(), &auth.Session{}, move.ID) + if err != nil { + log.Panic(fmt.Errorf("failed to fetch move: %w", err)) + } + return *newmove +} + // MakeBoatHaulAwayMoveNeedsSC creates an fully ready move with a boat haul-away shipment needing SC approval func MakeBoatHaulAwayMoveNeedsSC(appCtx appcontext.AppContext) models.Move { userUploader := newUserUploader(appCtx) diff --git a/playwright/tests/office/servicescounseling/servicesCounselingFlows.spec.js b/playwright/tests/office/servicescounseling/servicesCounselingFlows.spec.js index 6ab07e9026c..5aa4db714c3 100644 --- a/playwright/tests/office/servicescounseling/servicesCounselingFlows.spec.js +++ b/playwright/tests/office/servicescounseling/servicesCounselingFlows.spec.js @@ -13,6 +13,33 @@ const supportingDocsEnabled = process.env.FEATURE_FLAG_MANAGE_SUPPORTING_DOCS; const LocationLookup = 'BEVERLY HILLS, CA 90210 (LOS ANGELES)'; test.describe('Services counselor user', () => { + test.describe('GBLOC tests', () => { + test.describe('Origin Duty Location', () => { + let moveLocatorKKFA = ''; + let moveLocatorCNNQ = ''; + test.beforeEach(async ({ scPage }) => { + const moveKKFA = await scPage.testHarness.buildHHGMoveNeedsSC(); + moveLocatorKKFA = moveKKFA.locator; + const moveCNNQ = await scPage.testHarness.buildHHGMoveNeedsSC(); + moveLocatorCNNQ = moveCNNQ.locator; + }); + + test('when origin duty location GBLOC matches services counselor GBLOC', async ({ page }) => { + const locatorFilter = await page.getByTestId('locator').getByTestId('TextBoxFilter'); + await locatorFilter.fill(moveLocatorKKFA); + await locatorFilter.blur(); + await expect(page.getByTestId('locator-0')).toBeVisible(); + }); + + test('when origin duty location GBLOC does not match services counselor GBLOC', async ({ page }) => { + const locatorFilter = await page.getByTestId('locator').getByTestId('TextBoxFilter'); + await locatorFilter.fill(moveLocatorCNNQ); + await locatorFilter.blur(); + await expect(page.getByTestId('locator-0')).not.toBeVisible(); + }); + }); + }); + test.describe('with basic HHG move', () => { test.beforeEach(async ({ scPage }) => { const move = await scPage.testHarness.buildHHGMoveNeedsSC(); @@ -351,6 +378,8 @@ test.describe('Services counselor user', () => { await expect(page.getByText(LocationLookup, { exact: true })).toBeVisible(); await page.keyboard.press('Enter'); await page.locator('select[name="destinationType"]').selectOption({ label: 'Home of selection (HOS)' }); + await page.getByLabel('Requested pickup date').fill('16 Mar 2022'); + await page.locator('[data-testid="submitForm"]').click(); await scPage.waitForLoading(); diff --git a/playwright/tests/utils/testharness.js b/playwright/tests/utils/testharness.js index a01dac3461e..74feee4ffef 100644 --- a/playwright/tests/utils/testharness.js +++ b/playwright/tests/utils/testharness.js @@ -28,6 +28,9 @@ export class TestHarness { * @property {string} id * @property {string} locator * @property {Object} Orders + * @property {Object} OriginDutyLocation + * @property {Object} OriginDutyLocation.Address + * @property {string} OriginDutyLocation.Address.PostalCode * @property {Object} Orders.NewDutyLocation * @property {string} Orders.NewDutyLocation.name * @property {Object} Orders.ServiceMember @@ -392,6 +395,14 @@ export class TestHarness { return this.buildDefault('HHGMoveNeedsSC'); } + /** + * Use testharness to build hhg move needing SC approval in a non-default GBLOC + * @returns {Promise} + */ + async buildHHGMoveNeedsSCInOtherGBLOC() { + return this.buildDefault('HHGMoveNeedsSCOtherGBLOC'); + } + /** * Use testharness to build hhg move as USMC needing SC approval * @returns {Promise} From cdb5dbeb3da028680a769fc4632d0653ca84f131 Mon Sep 17 00:00:00 2001 From: Alex Lusk Date: Fri, 10 Jan 2025 16:12:29 +0000 Subject: [PATCH 53/54] update gen files --- swagger/ghc.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/swagger/ghc.yaml b/swagger/ghc.yaml index ac64dad4c0e..af454b0b2b0 100644 --- a/swagger/ghc.yaml +++ b/swagger/ghc.yaml @@ -8283,14 +8283,14 @@ definitions: type: array items: $ref: '#/definitions/TransportationOffice' - VLocations: - type: array - items: - $ref: '#/definitions/VLocation' ReServiceItems: type: array items: $ref: '#/definitions/ReServiceItem' + VLocations: + type: array + items: + $ref: '#/definitions/VLocation' GBLOCs: type: array items: From 3123707db761e438f9190ff2cb804c54fab67671 Mon Sep 17 00:00:00 2001 From: Alex Lusk Date: Fri, 10 Jan 2025 22:53:33 +0000 Subject: [PATCH 54/54] peer review suggestions --- pkg/handlers/ghcapi/transportation_offices_test.go | 1 - pkg/handlers/internalapi/transportation_offices_test.go | 1 - src/components/Office/AddOrdersForm/AddOrdersForm.test.jsx | 4 ++-- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/pkg/handlers/ghcapi/transportation_offices_test.go b/pkg/handlers/ghcapi/transportation_offices_test.go index 087c5c2d243..3be2ce21b3d 100644 --- a/pkg/handlers/ghcapi/transportation_offices_test.go +++ b/pkg/handlers/ghcapi/transportation_offices_test.go @@ -180,7 +180,6 @@ func (suite *HandlerSuite) TestShowCounselingOfficesHandler() { }, }, }, nil) - suite.MustSave(&origDutyLocation) path := fmt.Sprintf("/transportation_offices/%v/counseling_offices", origDutyLocation.ID.String()) req := httptest.NewRequest("GET", path, nil) diff --git a/pkg/handlers/internalapi/transportation_offices_test.go b/pkg/handlers/internalapi/transportation_offices_test.go index b41c5aff7bf..83f875335a4 100644 --- a/pkg/handlers/internalapi/transportation_offices_test.go +++ b/pkg/handlers/internalapi/transportation_offices_test.go @@ -153,7 +153,6 @@ func (suite *HandlerSuite) TestShowCounselingOfficesHandler() { }, }, }, nil) - suite.MustSave(&origDutyLocation) path := fmt.Sprintf("/transportation_offices/%v/counseling_offices", origDutyLocation.ID.String()) req := httptest.NewRequest("GET", path, nil) diff --git a/src/components/Office/AddOrdersForm/AddOrdersForm.test.jsx b/src/components/Office/AddOrdersForm/AddOrdersForm.test.jsx index 18495b24036..55813f517fb 100644 --- a/src/components/Office/AddOrdersForm/AddOrdersForm.test.jsx +++ b/src/components/Office/AddOrdersForm/AddOrdersForm.test.jsx @@ -263,8 +263,8 @@ describe('AddOrdersForm - With Counseling Office', () => { ); await userEvent.selectOptions(await screen.findByLabelText(/Orders type/), 'PERMANENT_CHANGE_OF_STATION'); - await userEvent.type(screen.getByLabelText(/Orders date/), '08 Nov 2020'); - await userEvent.type(screen.getByLabelText(/Report by date/), '26 Nov 2020'); + await userEvent.paste(screen.getByLabelText(/Orders date/), '08 Nov 2020'); + await userEvent.paste(screen.getByLabelText(/Report by date/), '26 Nov 2020'); await userEvent.click(screen.getByLabelText('No')); await userEvent.selectOptions(screen.getByLabelText(/Pay grade/), ['E_5']);