diff --git a/artifacts/accessibilityReport.html b/artifacts/accessibilityReport.html index d112a48c831..e924f8add49 100644 --- a/artifacts/accessibilityReport.html +++ b/artifacts/accessibilityReport.html @@ -87,7 +87,7 @@

-
axe-core found 14 violations
+
axe-core found 4 violations
@@ -106,34 +106,10 @@
axe-core found 14 violations
- - - - - - - - - - - - - - - - - - - - - - - - - + @@ -196,7 +172,7 @@

Element location

#root > .TitleAnnouncer_HiddenTitleAnnouncer__IxWhC[aria-live="polite"]

Element source

-
<div id="title-announcer" aria-live="polite" class="TitleAnnouncer_HiddenTitleAnnouncer__IxWhC" style="">my.move.mil - Move - Fda5473a F6ac 486f 83a4 D894da44836d</div>
+
<div id="title-announcer" aria-live="polite" class="TitleAnnouncer_HiddenTitleAnnouncer__IxWhC" style="">my.move.mil - Move review</div>
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
duplicate-id WCAG 2 Level A, WCAG 4.1.1 minor8
2Heading levels should only increase by oneheading-orderBest practicemoderate 1
3Links must be distinguishable without relying on colorlink-in-text-blockWCAG 2 Level A, WCAG 1.4.1serious1
4Page should contain a level-one headingpage-has-heading-oneBest practicemoderate1
52 All page content should be contained by landmarks region Best practice
@@ -209,360 +185,6 @@
#app-root > .TitleAnnouncer_HiddenTitleAnnouncer__IxWhC[aria-live="polite"]
2 -

Element location

-
div[data-testid="stepContainer1"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > defs > filter
-

Element source

-
<filter id="filter-1"><feColorMatrix in="SourceGraphic" type="matrix" values="0 0 0 0 1.000000 0 0 0 0 1.000000 0 0 0 0 1.000000 0 0 0 1.000000 0"></feColorMatrix></filter>
-
-
-

Fix any of the following:

-
    -
  • Document has multiple static elements with the same id attribute: filter-1
  • -
-
-

Related node:

-
.Step_step-amended-orders__E06Yy > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > defs > filter
-
div[data-testid="stepContainer3"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > defs > filter
-
div[data-testid="stepContainer4"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > defs > filter
-
3 -

Element location

-
div[data-testid="stepContainer1"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"]
-

Element source

-
<g id="circle-checked" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
-
-
-

Fix any of the following:

-
    -
  • Document has multiple static elements with the same id attribute: circle-checked
  • -
-
-

Related node:

-
.Step_step-amended-orders__E06Yy > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"]
-
div[data-testid="stepContainer3"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"]
-
div[data-testid="stepContainer4"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"]
-
4 -

Element location

-
div[data-testid="stepContainer1"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"]
-

Element source

-
<g id="Checklist_check" transform="translate(1.000000, 1.000000)">
-
-
-

Fix any of the following:

-
    -
  • Document has multiple static elements with the same id attribute: Checklist_check
  • -
-
-

Related node:

-
.Step_step-amended-orders__E06Yy > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"]
-
div[data-testid="stepContainer3"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"]
-
div[data-testid="stepContainer4"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"]
-
5 -

Element location

-
div[data-testid="stepContainer1"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"] > circle
-

Element source

-
<circle id="Oval" fill="#162E51" cx="65.5" cy="65.5" r="65.5"></circle>
-
-
-

Fix any of the following:

-
    -
  • Document has multiple static elements with the same id attribute: Oval
  • -
-
-

Related node:

-
.Step_step-amended-orders__E06Yy > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"] > circle
-
div[data-testid="stepContainer3"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"] > circle
-
div[data-testid="stepContainer4"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"] > circle
-
6 -

Element location

-
div[data-testid="stepContainer1"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"] > g[stroke-linecap="round"][stroke-linejoin="round"]
-

Element source

-
<g id="Group" transform="translate(32.750000, 32.750000)" stroke-linecap="round" stroke-linejoin="round">
-
-
-

Fix any of the following:

-
    -
  • Document has multiple static elements with the same id attribute: Group
  • -
-
-

Related node:

-
.Step_step-amended-orders__E06Yy > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"] > g[stroke-linecap="round"][stroke-linejoin="round"]
-
div[data-testid="stepContainer3"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"] > g[stroke-linecap="round"][stroke-linejoin="round"]
-
div[data-testid="stepContainer4"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"] > g[stroke-linecap="round"][stroke-linejoin="round"]
-
7 -

Element location

-
div[data-testid="stepContainer1"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"] > g[stroke-linecap="round"][stroke-linejoin="round"] > g[filter="url(#filter-1)"]
-

Element source

-
<g filter="url(#filter-1)" id="icon-/-check-copy-2"><g><polyline id="Path" stroke="#000000" stroke-width="8.25" points="55.0950521 21.7480469 27.1850586 49.2955729 14.4986979 36.7739702"></polyline></g></g>
-
-
-

Fix any of the following:

-
    -
  • Document has multiple static elements with the same id attribute: icon-/-check-copy-2
  • -
-
-

Related node:

-
.Step_step-amended-orders__E06Yy > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"] > g[stroke-linecap="round"][stroke-linejoin="round"] > g[filter="url(#filter-1)"]
-
div[data-testid="stepContainer3"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"] > g[stroke-linecap="round"][stroke-linejoin="round"] > g[filter="url(#filter-1)"]
-
div[data-testid="stepContainer4"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"] > g[stroke-linecap="round"][stroke-linejoin="round"] > g[filter="url(#filter-1)"]
-
8 -

Element location

-
div[data-testid="stepContainer1"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"] > g[stroke-linecap="round"][stroke-linejoin="round"] > g[filter="url(#filter-1)"] > g > polyline
-

Element source

-
<polyline id="Path" stroke="#000000" stroke-width="8.25" points="55.0950521 21.7480469 27.1850586 49.2955729 14.4986979 36.7739702"></polyline>
-
-
-

Fix any of the following:

-
    -
  • Document has multiple static elements with the same id attribute: Path
  • -
-
-

Related node:

-
.Step_step-amended-orders__E06Yy > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"] > g[stroke-linecap="round"][stroke-linejoin="round"] > g[filter="url(#filter-1)"] > g > polyline
-
div[data-testid="stepContainer3"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"] > g[stroke-linecap="round"][stroke-linejoin="round"] > g[filter="url(#filter-1)"] > g > polyline
-
div[data-testid="stepContainer4"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"] > g[stroke-linecap="round"][stroke-linejoin="round"] > g[filter="url(#filter-1)"] > g > polyline
-
- - - -
-
-
-
- 2. Heading levels should only increase by one -
- Learn more -
-
-
heading-order
-
- Best practice -
-
-
-

Ensures the order of headings is semantically correct

-
- moderate -
-
-
-
- Issue Tags: - cat.semantics - - best-practice -
-
-
- - - - - - - - - - - - - - - -
#Issue Description - To solve this violation, you need to... -
1 -

Element location

-
h6
-

Element source

-
<h6 class="Contact_contactHeader__v48ze">Contacts</h6>
-
-
-

Fix any of the following:

-
    -
  • Heading order invalid
  • -
-
-
-
-
-
-
-
-
-
- 3. Links must be distinguishable without relying on color -
- Learn more -
-
-
link-in-text-block
-
- WCAG 2 Level A, WCAG 1.4.1 -
-
-
-

Ensure links are distinguished from surrounding text in a way that does not rely on color

-
- serious -
-
-
-
- Issue Tags: - cat.color - - wcag2a - - wcag141 -
-
-
- - - - - - - - - - - - - - - -
#Issue Description - To solve this violation, you need to... -
1 -

Element location

-
p:nth-child(3) > .usa-link[target="_blank"][rel="noopener noreferrer"]
-

Element source

-
<a class="usa-link" target="_blank" rel="noopener noreferrer" href="https://www.militaryonesource.mil/moving-housing/moving/planning-your-move/customer-service-contacts-for-military-pcs/">directory of PCS-related contacts</a>
-
-
-

Fix any of the following:

-
    -
  • The link has insufficient color contrast of 2.57:1 with the surrounding text. (Minimum contrast is 3:1, link text: #0050d8, surrounding text: #1b1b1b)
  • -
  • The link has no styling (such as underline) to distinguish it from the surrounding text
  • -
-
-

Related node:

-
.Contact_contactContainer__jYtTz > p:nth-child(3)
-
.Contact_contactContainer__jYtTz > p:nth-child(3)
-
-
-
-
-
-
-
-
- 4. Page should contain a level-one heading -
- Learn more -
-
-
page-has-heading-one
-
- Best practice -
-
-
-

Ensure that the page, or at least one of its frames contains a level-one heading

-
- moderate -
-
-
-
- Issue Tags: - cat.semantics - - best-practice -
-
-
- - - - - - - - - - - - - -
#Issue Description - To solve this violation, you need to... -
1 -

Element location

-
html
-

Element source

-
<html lang="en">
-
-
-

Fix all of the following:

-
    -
  • Page must have a level-one heading
  • -
-
-
@@ -572,7 +194,7 @@
- 5. All page content should be contained by landmarks + 2. All page content should be contained by landmarks
{ test.skip(multiMoveEnabled === 'true', 'Skip if MultiMove workflow is enabled.'); @@ -111,7 +112,6 @@ test.describe('HHG', () => { test.describe('(MultiMove) HHG', () => { test.skip(multiMoveEnabled === 'false', 'Skip if MultiMove workflow is not enabled.'); - test('A customer can create, edit, and delete an HHG shipment', async ({ page, customerPage }) => { // Generate a new onboarded user with orders and log in const move = await customerPage.testHarness.buildMoveWithOrders(); @@ -217,4 +217,82 @@ test.describe('(MultiMove) HHG', () => { await expect(page.getByText('The shipment was deleted.')).toBeVisible(); await expect(page.getByTestId('stepContainer3').getByText('Set up shipments')).toBeVisible(); }); + + test.skip(alaskaFF === 'false', 'Skip if the create customer & AK FFs are not enabled.'); + test('A customer can create, edit, and submit an international Alaska HHG shipment', async ({ + page, + customerPage, + }) => { + // Generate a new onboarded user with orders and log in + const move = await customerPage.testHarness.buildMoveWithOrders(); + const userId = move.Orders.ServiceMember.user_id; + await customerPage.signInAsExistingCustomer(userId); + + // Navigate from MM Dashboard to Move + await customerPage.navigateFromMMDashboardToMove(move); + + // Navigate to create a new shipment + await customerPage.waitForPage.home(); + await page.getByTestId('shipment-selection-btn').click(); + await customerPage.waitForPage.aboutShipments(); + await customerPage.navigateForward(); + await customerPage.waitForPage.selectShipmentType(); + + // Create an HHG shipment + await page.getByText('Movers pack and ship it, paid by the government').click(); + await customerPage.navigateForward(); + + // Fill in form to create HHG shipment + await customerPage.waitForPage.hhgShipment(); + await page.getByLabel('Preferred pickup date').fill('25 Dec 2022'); + await page.getByLabel('Preferred pickup date').blur(); + await page.getByText('Use my current address').click(); + await page.getByLabel('Preferred delivery date').fill('25 Dec 2022'); + await page.getByLabel('Preferred delivery date').blur(); + await page.getByTestId('remarks').fill('Going to Alaska'); + await customerPage.navigateForward(); + + // Verify that form submitted, initial setup has it being a domestic HHG shipment (dHHG) + await customerPage.waitForPage.reviewShipments(); + await expect(page.getByText('dHHG')).toBeVisible(); + await expect(page.getByText('Going to Alaska')).toBeVisible(); + await expect(page.getByTestId('ShipmentContainer').getByText('123 Any Street')).toBeVisible(); + + // Navigate to edit shipment from the review page + await page.getByTestId('edit-shipment-btn').click(); + await customerPage.waitForPage.hhgShipment(); + + // Update form (adding pickup and delivery address) + const pickupLocation = 'LAWTON, OK 73505 (COMANCHE)'; + const pickupAddress = page.getByRole('group', { name: 'Pickup Address' }); + await pickupAddress.getByLabel('Address 1').fill('123 Warm St.'); + await page.locator('input[id="pickup.address-location-input"]').fill('73505'); + await expect(page.getByText(pickupLocation, { exact: true })).toBeVisible(); + await page.keyboard.press('Enter'); + + // Delivery address + const deliveryLocation = 'JBER, AK 99505 (ANCHORAGE)'; + const deliveryAddress = page.getByRole('group', { name: 'Delivery Address' }); + await deliveryAddress.getByText('Yes').nth(0).click(); + await deliveryAddress.getByLabel('Address 1').nth(0).fill('123 Cold Ave.'); + await page.locator('input[id="delivery.address-location-input"]').fill('99505'); + await expect(page.getByText(deliveryLocation, { exact: true })).toBeVisible(); + await page.keyboard.press('Enter'); + await customerPage.navigateForward(); + + // Verify that shipment updated - should now be an iHHG shipment + await customerPage.waitForPage.reviewShipments(); + await expect(page.getByText('iHHG')).toBeVisible(); + await expect(page.getByTestId('ShipmentContainer').getByText('123 Warm St.')).toBeVisible(); + await expect(page.getByTestId('ShipmentContainer').getByText('123 Cold Ave.')).toBeVisible(); + + await page.getByRole('button', { name: 'Next' }).click(); + await expect(page).toHaveURL(/\/moves\/[^/]+\/agreement/); + await expect(page.getByRole('heading', { name: 'Now for the official part…' })).toBeVisible(); + + await page.locator('input[name="signature"]').fill('Mister Alaska'); + await expect(page.getByRole('button', { name: 'Complete' })).toBeEnabled(); + await page.getByRole('button', { name: 'Complete' }).click(); + await expect(page.getByText('submitted your move request.')).toBeVisible(); + }); }); diff --git a/playwright/tests/office/servicescounseling/servicesCounselingFlows.spec.js b/playwright/tests/office/servicescounseling/servicesCounselingFlows.spec.js index 56ad7a0ce1d..16db36fbc3c 100644 --- a/playwright/tests/office/servicescounseling/servicesCounselingFlows.spec.js +++ b/playwright/tests/office/servicescounseling/servicesCounselingFlows.spec.js @@ -291,61 +291,6 @@ test.describe('Services counselor user', () => { await scPage.navigateToMove(move.locator); }); - /** - * This test is being temporarily skipped until flakiness issues - * can be resolved. It was skipped in cypress and is not part of - * the initial playwright conversion. - ahobson 2023-01-12 - */ - test.skip('is able to edit allowances', () => { - // // TOO Moves queue - // cy.wait(['@getSortedMoves']); - // await expect(page.getByText(moveLocator).click()).toBeVisible(); - // cy.url().should('include', `/moves/${moveLocator}/details`); - // // Move Details page - // cy.watest(['@getMoves', '@getOrders', '@getMTOShipments', async ({page}) => { - // // Navigate to Edit allowances page - // await expect(page.locator('[data-testid="edit-allowances"]')).toContainText('Edit allowances').click(); - // // Toggle between Edit Allowances and Edit Orders page - // await page.locator('[data-testid="view-orders"]').click(); - // cy.url().should('include', `/moves/${moveLocator}/orders`); - // await page.locator('[data-testid="view-allowances"]').click(); - // cy.url().should('include', `/moves/${moveLocator}/allowances`); - // cy.watest(['@getMoves', async ({page}) => { - // await page.locator('form').within(($form) => { - // // Edit pro-gear, pro-gear spouse, RME, SIT, and OCIE fields - // await page.locator('input[name="proGearWeight"]').fill('1999'); - // await page.locator('input[name="proGearWeightSpouse"]').fill('499'); - // await page.locator('input[name="requiredMedicalEquipmentWeight"]').fill('999'); - // await page.locator('input[name="storageInTransit"]').fill('199'); - // await page.locator('input[name="organizationalClothingAndIndividualEquipment"]').siblings('label[for="ocieInput"]').click(); - // // Edit grade and authorized weight - // await expect(page.locator('select[name=agency]')).toContainText('Army'); - // await page.locator('select[name=agency]').selectOption({ label: 'Navy'}); - // await expect(page.locator('select[name="grade"]')).toContainText('E-1'); - // await page.locator('select[name="grade"]').selectOption({ label: 'W-2'}); - // //Edit DependentsAuthorized - // await page.locator('input[name="dependentsAuthorized"]').siblings('label[for="dependentsAuthorizedInput"]').click(); - // // Edit allowances page | Save - // await expect(page.locator('[data-testid="scAllowancesSave"]')).toBeEnabled().click(); - // }); - // cy.wait(['@patchAllowances']); - // // Verify edited values are saved - // cy.url().should('include', `/moves/${moveLocator}/details`); - // cy.watest(['@getMoves', '@getOrders', '@getMTOShipments', async ({page}) => { - // await expect(page.locator('[data-testid="progear"]')).toContainText('1,999'); - // await expect(page.locator('[data-testid="spouseProgear"]')).toContainText('499'); - // await expect(page.locator('[data-testid="rme"]')).toContainText('999'); - // await expect(page.locator('[data-testid="storageInTransit"]')).toContainText('199'); - // await expect(page.locator('[data-testid="ocie"]')).toContainText('Unauthorized'); - // await expect(page.locator('[data-testid="branchGrade"]')).toContainText('Navy'); - // await expect(page.locator('[data-testid="branchGrade"]')).toContainText('W-2'); - // await expect(page.locator('[data-testid="dependents"]')).toContainText('Unauthorized'); - // // Edit allowances page | Cancel - // await expect(page.locator('[data-testid="edit-allowances"]')).toContainText('Edit allowances').click(); - // await expect(page.locator('button')).toContainText('Cancel').click(); - // cy.url().should('include', `/moves/${moveLocator}/details`); - }); - test('is able to see and use the left navigation', async ({ page }) => { await expect(page.locator('a[href*="#shipments"]')).toContainText('Shipments'); await expect(page.locator('a[href*="#orders"]')).toContainText('Orders'); diff --git a/playwright/tests/office/servicescounseling/servicesCounselingInternational.spec.js b/playwright/tests/office/servicescounseling/servicesCounselingInternational.spec.js new file mode 100644 index 00000000000..ca6f289bd44 --- /dev/null +++ b/playwright/tests/office/servicescounseling/servicesCounselingInternational.spec.js @@ -0,0 +1,114 @@ +import { DEPARTMENT_INDICATOR_OPTIONS } from '../../utils/office/officeTest'; + +import { test, expect } from './servicesCounselingTestFixture'; + +const createCustomerFF = process.env.FEATURE_FLAG_COUNSELOR_MOVE_CREATE; +const alaskaFF = process.env.FEATURE_FLAG_ENABLE_ALASKA; +const LocationLookup = 'BEVERLY HILLS, CA 90210 (LOS ANGELES)'; + +test.describe('Services counselor user', () => { + test.describe('Can create a customer with an international Alaska move', () => { + test.beforeEach(async ({ scPage }) => { + await scPage.signInAsNewServicesCounselorUser(); + }); + + test.skip( + createCustomerFF === 'false' || alaskaFF === 'false', + 'Skip if the create customer & AK FFs are not enabled.', + ); + test('create a customer and add a basic iHHG shipment with Alaska address', async ({ page, officePage }) => { + // make sure we see the queue + await expect(page.getByText('Moves')).toBeVisible(); + await expect(page.getByRole('link', { name: 'Counseling' })).toBeVisible(); + await expect(page.getByRole('link', { name: 'Customer Search' })).toBeVisible(); + + // we need to search before we have access to the create customer button + await page.getByRole('link', { name: 'Customer Search' }).click(); + await page.getByText('Customer Name').click(); + await page.getByLabel('Search').fill('Test'); + await page.getByRole('button', { name: 'Search' }).click(); + await expect(page.getByRole('button', { name: 'Add Customer' })).toBeEnabled(); + await page.getByRole('button', { name: 'Add Customer' }).click(); + + // fill out the customer form + await page.getByRole('combobox', { name: 'Branch of service' }).selectOption({ label: 'Army' }); + await page.getByLabel('DoD ID number').fill('1234567890'); + await page.getByLabel('First name').fill('Mister'); + await page.getByLabel('Last name').fill('Alaska'); + await page.getByLabel('Best contact phone').fill('555-555-5555'); + await page.getByLabel('Personal email').fill('alaskaBoi@mail.mil'); + await page.getByText('Phone', { exact: true }).nth(0).click(); + await page.getByLabel('Address 1').nth(0).fill('1234 Pickup St.'); + await page.getByLabel('Location Lookup').nth(0).fill('90210'); + await expect(page.getByText(LocationLookup, { exact: true })).toBeVisible(); + await page.keyboard.press('Enter'); + await page.getByLabel('Address 1').nth(1).fill('1234 Backup St.'); + await page.getByLabel('Location Lookup').nth(1).fill('90210'); + await expect(page.getByText(LocationLookup, { exact: true })).toBeVisible(); + await page.keyboard.press('Enter'); + await page.getByLabel('Name', { exact: true }).fill('Backup Friend'); + await page.getByLabel('Email', { exact: true }).nth(1).fill('backupFriend@mail.mil'); + await page.getByLabel('Phone', { exact: true }).nth(1).fill('555-867-5309'); + await page.locator('label[for="noCreateOktaAccount"]').click(); + await page.locator('label[for="yesCacUser"]').click(); + await page.keyboard.press('Tab'); + await expect(page.getByRole('button', { name: 'Save' })).toBeEnabled(); + await page.getByRole('button', { name: 'Save' }).click(); + + // fill out the orders form + await page.getByLabel('Orders type').selectOption({ label: 'Permanent Change Of Station (PCS)' }); + await page.getByLabel('Orders date').fill('12/25/2024'); + await page.getByLabel('Orders date').blur(); + await page.getByLabel('Report by date').fill('1/25/2025'); + await page.getByLabel('Report by date').blur(); + const originLocation = 'Tinker AFB, OK 73145'; + await page.getByLabel('Current duty location').fill('Tinker'); + await expect(page.getByText(originLocation, { exact: true })).toBeVisible(); + await page.keyboard.press('Enter'); + const pickupLocation = 'Elmendorf AFB, AK 99506'; + await page.getByLabel('New duty location').fill('JBER'); + await expect(page.getByText(pickupLocation, { exact: true })).toBeVisible(); + await page.keyboard.press('Enter'); + await page.locator('label[for="hasDependentsNo"]').click(); + await page.getByLabel('Pay grade').selectOption({ label: 'E-7' }); + await expect(page.getByRole('button', { name: 'Next' })).toBeEnabled(); + await page.getByRole('button', { name: 'Next' }).click(); + + // now we need to add order data + await page.getByRole('link', { name: 'View and edit orders' }).click(); + const filepondContainer = page.locator('.filepond--wrapper'); + await officePage.uploadFileViaFilepond(filepondContainer, 'AF Orders Sample.pdf'); + await expect(page.getByText('Uploading')).toBeVisible(); + await expect(page.getByText('Uploading')).not.toBeVisible(); + await expect(page.getByText('Upload complete')).not.toBeVisible(); + await expect(page.getByTestId('uploads-table').getByText('AF Orders Sample.pdf')).toBeVisible(); + await page.getByRole('button', { name: 'Done' }).click(); + await page.getByLabel('Department indicator').selectOption(DEPARTMENT_INDICATOR_OPTIONS.ARMY); + await page.getByLabel('Orders number').fill('123456'); + await page.getByLabel('Orders type detail').selectOption('Shipment of HHG Permitted'); + await page.getByLabel('TAC', { exact: true }).nth(0).fill('TEST'); + await expect(page.getByRole('button', { name: 'Save' })).toBeEnabled(); + await page.getByRole('button', { name: 'Save' }).click(); + + // adding an HHG shipment + await page.getByLabel('Add a new shipment').selectOption('HHG'); + await expect(page.getByText('Add shipment details')).toBeVisible(); + await expect(page.getByText('Weight allowance: 11,000 lbs')).toBeVisible(); + await page.getByLabel('Requested pickup date').fill('25 Dec 2024'); + await page.getByLabel('Requested pickup date').blur(); + await page.getByText('Use pickup address').click(); + await page.getByLabel('Requested delivery date').fill('25 Dec 2022'); + await page.getByLabel('Requested delivery date').blur(); + await expect(page.getByRole('button', { name: 'Save' })).toBeEnabled(); + await page.getByRole('button', { name: 'Save' }).click(); + + // verify we can see the iHHG shipment, submit it to the TOO + await expect(page.getByText('iHHG')).toBeVisible(); + await expect(page.getByRole('button', { name: 'Submit move details' })).toBeEnabled(); + await page.getByRole('button', { name: 'Submit move details' }).click(); + await expect(page.getByText('Are you sure?')).toBeVisible(); + await page.getByRole('button', { name: 'Yes, submit' }).click(); + await expect(page.getByText('Move submitted.')).toBeVisible(); + }); + }); +}); diff --git a/playwright/tests/office/txo/tioFlowsInternational.spec.js b/playwright/tests/office/txo/tioFlowsInternational.spec.js new file mode 100644 index 00000000000..2b97f19078b --- /dev/null +++ b/playwright/tests/office/txo/tioFlowsInternational.spec.js @@ -0,0 +1,190 @@ +import { test, expect, OfficePage } from '../../utils/office/officeTest'; + +/** + * TioFlowPage test fixture + * + * The logic in TioFlowPage is only used in this file, so keep the + * playwright test fixture in this file. + * @extends OfficePage + */ +class TioFlowPage extends OfficePage { + /** + * @param {OfficePage} officePage + * @param {Object} move + * @param {Boolean} usePaymentRequest + * @override + */ + constructor(officePage, move, usePaymentRequest) { + super(officePage.page, officePage.request); + this.move = move; + this.moveLocator = move.locator; + if (usePaymentRequest !== false) { + this.paymentRequest = this.findPaymentRequestBySequenceNumber(1); + } + } + + /** + * @param {number} sequenceNumber + * @returns {object} + */ + findPaymentRequestBySequenceNumber(sequenceNumber) { + return this.move.PaymentRequests.find((pr) => pr.sequence_number === sequenceNumber); + } + + /** + * Complete the service item card + * @param {import('@playwright/test').Locator} serviceItemCardLocator + * @param {boolean} approve + */ + async completeServiceItemCard(serviceItemCardLocator, approve = false) { + // serviceItemAmount + if (!approve) { + const inputEl = serviceItemCardLocator.locator('input[data-testid="rejectRadio"]'); + const id = await inputEl.getAttribute('id'); + await this.page.locator(`label[for="${id}"]`).click(); + await this.page.locator('textarea[data-testid="rejectionReason"]').fill('This is not a valid request'); + } else { + const inputEl = serviceItemCardLocator.locator('input[data-testid="approveRadio"]'); + const id = await inputEl.getAttribute('id'); + await this.page.locator(`label[for="${id}"]`).click(); + } + await this.slowDown(); + } + + /** + * approve the service item + */ + async approveServiceItem() { + await this.completeServiceItem(true); + } + + /** + * complete the service item, approving or rejecting it + * @param {boolean} approved + */ + async completeServiceItem(approved) { + const cards = this.page.getByTestId('ServiceItemCard'); + const cardCount = await cards.count(); + expect(cardCount).toBeGreaterThan(0); + for (let i = 0; i < cardCount; i += 1) { + await this.completeServiceItemCard(cards.nth(i), approved); + } + } + + async slowDown() { + await this.page.waitForLoadState('networkidle'); + // sleep for 500ms + await new Promise((r) => { + setTimeout(() => r(undefined), 500); + }); + } +} + +const alaskaEnabled = process.env.FEATURE_FLAG_ENABLE_ALASKA; + +test.describe('TIO user', () => { + /** @type {TioFlowPage} */ + let tioFlowPage; + test.skip(alaskaEnabled === 'false', 'Skip if Alaska FF is disabled.'); + test('can review a payment request for a basic iHHG Alaska move', async ({ page, officePage }) => { + test.slow(); + const move = await officePage.testHarness.buildnternationalHHGMoveWithServiceItemsandPaymentRequestsForTIO(); + await officePage.signInAsNewTIOUser(); + + tioFlowPage = new TioFlowPage(officePage, move, true); + await tioFlowPage.waitForLoading(); + await officePage.tioNavigateToMove(tioFlowPage.moveLocator); + await officePage.page.getByRole('heading', { name: 'Payment Requests', exact: true }).waitFor(); + expect(page.url()).toContain('/payment-requests'); + await expect(page.getByTestId('MovePaymentRequests')).toBeVisible(); + + const prNumber = tioFlowPage.paymentRequest.payment_request_number; + const prHeading = page.getByRole('heading', { name: `Payment Request ${prNumber}` }); + await expect(prHeading).toBeVisible(); + await tioFlowPage.waitForLoading(); + + await page.getByRole('button', { name: 'Review service items' }).click(); + + await page.waitForURL(`**/payment-requests/${tioFlowPage.paymentRequest.id}`); + await tioFlowPage.waitForLoading(); + + // there should be four service items - let's approve all of them + await expect(page.getByTestId('ReviewServiceItems')).toBeVisible(); + await expect(page.getByText('International Shipping & Linehaul')).toBeVisible(); + await page.getByText('Show calculations').click(); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Calculations'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Billable weight (cwt)'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('ISLH price'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Price escalation factor'); + // approve + await tioFlowPage.approveServiceItem(); + await page.getByText('Next').click(); + await tioFlowPage.slowDown(); + + await expect(page.getByText('International HHG Pack')).toBeVisible(); + await page.getByText('Show calculations').click(); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Calculations'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Billable weight (cwt)'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('International Pack price'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Price escalation factor'); + // approve + await tioFlowPage.approveServiceItem(); + await page.getByText('Next').click(); + await tioFlowPage.slowDown(); + + await expect(page.getByText('International HHG Unpack')).toBeVisible(); + await page.getByText('Show calculations').click(); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Calculations'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Billable weight (cwt)'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('International Unpack price'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Price escalation factor'); + // approve + await tioFlowPage.approveServiceItem(); + await page.getByText('Next').click(); + await tioFlowPage.slowDown(); + + await expect(page.getByText('International POE Fuel Surcharge')).toBeVisible(); + await page.getByText('Show calculations').click(); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Calculations'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Billable weight (cwt)'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Mileage'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('ZIP 74133 to Port ZIP 98424'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Mileage factor'); + // approve + await tioFlowPage.approveServiceItem(); + await page.getByText('Next').click(); + await tioFlowPage.slowDown(); + + // that should be it + await expect(page.getByText('needs your review')).toHaveCount(0, { timeout: 10000 }); + await page.getByText('Complete request').click(); + + await expect(page.locator('[data-testid="requested"]')).toContainText('$4,281.48'); + await expect(page.locator('[data-testid="accepted"]')).toContainText('$4,281.48'); + await expect(page.locator('[data-testid="rejected"]')).toContainText('$0.00'); + + await page.getByText('Authorize payment').click(); + await tioFlowPage.waitForLoading(); + + await tioFlowPage.slowDown(); + expect(page.url()).toContain('/payment-requests'); + + await expect(page.getByTestId('tag')).toBeVisible(); + await expect(page.getByTestId('tag').getByText('Reviewed')).toHaveCount(1); + + // ensure the payment request we approved no longer has the "Review Service Items" button + await expect(page.getByText('Review Service Items')).toHaveCount(0); + + // Go back to queue + await page.locator('a[title="Home"]').click(); + await tioFlowPage.waitForLoading(); + + // search for the moveLocator in case this move doesn't show up on the first page + await page.locator('#locator').fill(tioFlowPage.moveLocator); + await page.locator('#locator').blur(); + const paymentSection = page.locator(`[data-uuid="${tioFlowPage.paymentRequest.id}"]`); + // the payment request that is now in the "Reviewed" status will no longer appear + // in the TIO queue - only "Payment requested" moves will appear + await expect(paymentSection.locator('td', { hasText: 'Reviewed' })).not.toBeVisible(); + }); +}); diff --git a/playwright/tests/office/txo/tooFlows.spec.js b/playwright/tests/office/txo/tooFlows.spec.js index ebc7a29bfc6..dd62f16bb46 100644 --- a/playwright/tests/office/txo/tooFlows.spec.js +++ b/playwright/tests/office/txo/tooFlows.spec.js @@ -18,8 +18,6 @@ const SearchTerms = ['SITEXT', '8796353598', 'Spacemen']; const StatusFilterOptions = ['Draft', 'New Move', 'Needs Counseling', 'Service counseling completed', 'Move approved']; -const alaskaEnabled = process.env.FEATURE_FLAG_ENABLE_ALASKA; - test.describe('TOO user', () => { /** @type {TooFlowPage} */ let tooFlowPage; @@ -471,65 +469,6 @@ test.describe('TOO user', () => { await expect(cancelAlert).not.toBeVisible(); }); - /** - * This test is being temporarily skipped until flakiness issues - * can be resolved. It was skipped in cypress and is not part of - * the initial playwright conversion. - ahobson 2023-01-10 - */ - test.skip('is able to edit allowances', async ({ page }) => { - // Navigate to Edit allowances page - await expect(page.getByTestId('edit-allowances')).toContainText('Edit allowances'); - await page.getByText('Edit allowances').click(); - - // // Toggle between Edit Allowances and Edit Orders page - // await page.locator('[data-testid="view-orders"]').click(); - // cy.url().should('include', `/moves/${moveLocator}/orders`); - // await page.locator('[data-testid="view-allowances"]').click(); - // cy.url().should('include', `/moves/${moveLocator}/allowances`); - - // await page.locator('form').within(($form) => { - // // Edit pro-gear, pro-gear spouse, RME, SIT, and OCIE fields - // await page.locator('input[name="proGearWeight"]').fill('1999'); - // await page.locator('input[name="proGearWeightSpouse"]').fill('499'); - // await page.locator('input[name="requiredMedicalEquipmentWeight"]').fill('999'); - // await page.locator('input[name="storageInTransit"]').fill('199'); - // await page.locator('input[name="organizationalClothingAndIndividualEquipment"]').siblings('label[for="ocieInput"]').click(); - - // // Edit grade and authorized weight - // await expect(page.locator('select[name=agency]')).toContainText('Army'); - // await page.locator('select[name=agency]').selectOption({ label: 'Navy'}); - // await expect(page.locator('select[name="grade"]')).toContainText('E-1'); - // await page.locator('select[name="grade"]').selectOption({ label: 'W-2'}); - // await page.locator('input[name="authorizedWeight"]').fill('11111'); - - // //Edit DependentsAuthorized - // await page.locator('input[name="dependentsAuthorized"]').siblings('label[for="dependentsAuthorizedInput"]').click(); - - // // Edit allowances page | Save - // await expect(page.locator('button').contains('Save')).toBeEnabled().click(); - - // cy.wait(['@patchAllowances']); - - // // Verify edited values are saved - // cy.url().should('include', `/moves/${moveLocator}/details`); - - // await expect(page.locator('[data-testid="progear"]')).toContainText('1,999'); - // await expect(page.locator('[data-testid="spouseProgear"]')).toContainText('499'); - // await expect(page.locator('[data-testid="rme"]')).toContainText('999'); - // await expect(page.locator('[data-testid="storageInTransit"]')).toContainText('199'); - // await expect(page.locator('[data-testid="ocie"]')).toContainText('Unauthorized'); - - // await expect(page.locator('[data-testid="authorizedWeight"]')).toContainText('11,111'); - // await expect(page.locator('[data-testid="branchGrade"]')).toContainText('Navy'); - // await expect(page.locator('[data-testid="branchGrade"]')).toContainText('W-2'); - // await expect(page.locator('[data-testid="dependents"]')).toContainText('Unauthorized'); - - // // Edit allowances page | Cancel - // await expect(page.locator('[data-testid="edit-allowances"]')).toContainText('Edit allowances').click(); - // await expect(page.locator('button')).toContainText('Cancel').click(); - // cy.url().should('include', `/moves/${moveLocator}/details`); - }); - test('is able to edit shipment', async ({ page }) => { const deliveryDate = new Date().toLocaleDateString('en-US'); const LocationLookup = 'BEVERLY HILLS, CA 90210 (LOS ANGELES)'; @@ -580,126 +519,6 @@ test.describe('TOO user', () => { }); }); - test.describe('with International HHG Moves', () => { - test.skip(alaskaEnabled === 'false', 'Skip if Alaska FF is disabled.'); - test('is able to approve and reject international crating/uncrating service items', async ({ - officePage, - page, - }) => { - const move = await officePage.testHarness.buildHHGMoveWithIntlCratingServiceItemsTOO(); - await officePage.signInAsNewTOOUser(); - tooFlowPage = new TooFlowPage(officePage, move); - await tooFlowPage.waitForLoading(); - await officePage.tooNavigateToMove(tooFlowPage.moveLocator); - - // Edit the shipment address to AK - await page.locator('[data-testid="ShipmentContainer"] .usa-button').first().click(); - await page.locator('select[name="delivery.address.state"]').selectOption({ label: 'AK' }); - await page.locator('[data-testid="submitForm"]').click(); - await expect(page.locator('[data-testid="submitForm"]')).not.toBeEnabled(); - await tooFlowPage.waitForPage.moveDetails(); - - await tooFlowPage.waitForLoading(); - await tooFlowPage.approveAllShipments(); - - await page.getByTestId('MoveTaskOrder-Tab').click(); - await tooFlowPage.waitForLoading(); - expect(page.url()).toContain(`/moves/${tooFlowPage.moveLocator}/mto`); - - // Wait for page to load to deal with flakiness resulting from Service Item tables loading - await tooFlowPage.page.waitForLoadState(); - - // Move Task Order page - await expect(page.getByTestId('ShipmentContainer')).toHaveCount(1); - - /** - * @function - * @description This test approves and rejects service items, which moves them from one table to another - * and expects the counts of each table to increment/decrement by one item each time - * This function gets the service items for a given table to help count them - * @param {import("playwright-core").Locator} table - * @returns {import("playwright-core").Locator} - */ - const getServiceItemsInTable = (table) => { - return table.getByRole('rowgroup').nth(1).getByRole('row'); - }; - - const requestedServiceItemsTable = page.getByTestId('RequestedServiceItemsTable'); - let requestedServiceItemCount = await getServiceItemsInTable(requestedServiceItemsTable).count(); - const approvedServiceItemsTable = page.getByTestId('ApprovedServiceItemsTable'); - let approvedServiceItemCount = await getServiceItemsInTable(approvedServiceItemsTable).count(); - const rejectedServiceItemsTable = page.getByTestId('RejectedServiceItemsTable'); - let rejectedServiceItemCount = await getServiceItemsInTable(rejectedServiceItemsTable).count(); - - await expect(page.getByText('Requested Service Items', { exact: false })).toBeVisible(); - await expect(getServiceItemsInTable(requestedServiceItemsTable).nth(1)).toBeVisible(); - - await expect(page.getByTestId('modal')).not.toBeVisible(); - - // Approve a requested service item - expect((await getServiceItemsInTable(requestedServiceItemsTable).count()) > 0); - // ICRT - await requestedServiceItemsTable.getByRole('button', { name: 'Accept' }).first().click(); - await tooFlowPage.waitForLoading(); - - await expect(getServiceItemsInTable(approvedServiceItemsTable)).toHaveCount(approvedServiceItemCount + 1); - approvedServiceItemCount = await getServiceItemsInTable(approvedServiceItemsTable).count(); - - await expect(getServiceItemsInTable(requestedServiceItemsTable)).toHaveCount(requestedServiceItemCount - 1); - requestedServiceItemCount = await getServiceItemsInTable(requestedServiceItemsTable).count(); - - // IUCRT - await requestedServiceItemsTable.getByRole('button', { name: 'Accept' }).first().click(); - await tooFlowPage.waitForLoading(); - - await expect(getServiceItemsInTable(approvedServiceItemsTable)).toHaveCount(approvedServiceItemCount + 1); - approvedServiceItemCount = await getServiceItemsInTable(approvedServiceItemsTable).count(); - - await expect(getServiceItemsInTable(requestedServiceItemsTable)).toHaveCount(requestedServiceItemCount - 1); - requestedServiceItemCount = await getServiceItemsInTable(requestedServiceItemsTable).count(); - - // Reject a requested service item - await expect(page.getByText('Requested Service Items', { exact: false })).toBeVisible(); - expect((await getServiceItemsInTable(requestedServiceItemsTable).count()) > 0); - // ICRT - await requestedServiceItemsTable.getByRole('button', { name: 'Reject' }).first().click(); - - await expect(page.getByTestId('modal')).toBeVisible(); - let modal = page.getByTestId('modal'); - - await expect(modal.getByRole('button', { name: 'Submit' })).toBeDisabled(); - await modal.getByRole('textbox').fill('my very valid reason'); - await modal.getByRole('button', { name: 'Submit' }).click(); - - await expect(page.getByTestId('modal')).not.toBeVisible(); - - await expect(page.getByText('Rejected Service Items', { exact: false })).toBeVisible(); - await expect(getServiceItemsInTable(rejectedServiceItemsTable)).toHaveCount(rejectedServiceItemCount + 1); - rejectedServiceItemCount = await getServiceItemsInTable(rejectedServiceItemsTable).count(); - - await expect(getServiceItemsInTable(requestedServiceItemsTable)).toHaveCount(requestedServiceItemCount - 1); - requestedServiceItemCount = await getServiceItemsInTable(requestedServiceItemsTable).count(); - - // IUCRT - await requestedServiceItemsTable.getByRole('button', { name: 'Reject' }).first().click(); - - await expect(page.getByTestId('modal')).toBeVisible(); - modal = page.getByTestId('modal'); - - await expect(modal.getByRole('button', { name: 'Submit' })).toBeDisabled(); - await modal.getByRole('textbox').fill('my very valid reason'); - await modal.getByRole('button', { name: 'Submit' }).click(); - - await expect(page.getByTestId('modal')).not.toBeVisible(); - - await expect(page.getByText('Rejected Service Items', { exact: false })).toBeVisible(); - await expect(getServiceItemsInTable(rejectedServiceItemsTable)).toHaveCount(rejectedServiceItemCount + 1); - rejectedServiceItemCount = await getServiceItemsInTable(rejectedServiceItemsTable).count(); - - await expect(getServiceItemsInTable(requestedServiceItemsTable)).toHaveCount(requestedServiceItemCount - 1); - }); - }); - test.describe('with HHG Moves after actual pickup date', () => { test.beforeEach(async ({ officePage }) => { const move = await officePage.testHarness.buildHHGMoveForTOOAfterActualPickupDate(); diff --git a/playwright/tests/office/txo/tooFlowsInternational.spec.js b/playwright/tests/office/txo/tooFlowsInternational.spec.js new file mode 100644 index 00000000000..31fa0741487 --- /dev/null +++ b/playwright/tests/office/txo/tooFlowsInternational.spec.js @@ -0,0 +1,167 @@ +import { test, expect } from '../../utils/office/officeTest'; + +import { TooFlowPage } from './tooTestFixture'; + +const alaskaEnabled = process.env.FEATURE_FLAG_ENABLE_ALASKA; + +test.describe('TOO user', () => { + /** @type {TooFlowPage} */ + let tooFlowPage; + + test.describe('with HHG Moves', () => { + test.skip(alaskaEnabled === 'false', 'Skip if Alaska FF is disabled.'); + test('is able to approve an AK iHHG shipment that generates 4 basic service items', async ({ + page, + officePage, + }) => { + const move = await officePage.testHarness.buildInternationalAlaskaBasicHHGMoveForTOO(); + await officePage.signInAsNewTOOUser(); + tooFlowPage = new TooFlowPage(officePage, move); + await tooFlowPage.waitForLoading(); + await officePage.tooNavigateToMove(tooFlowPage.moveLocator); + await expect(page.locator('#approved-shipments')).not.toBeVisible(); + await expect(page.locator('#requested-shipments')).toBeVisible(); + await expect(page.getByText('Approve selected')).toBeDisabled(); + await expect(page.locator('#approvalConfirmationModal [data-testid="modal"]')).not.toBeVisible(); + + await tooFlowPage.waitForLoading(); + await tooFlowPage.approveAllShipments(); + + // Redirected to Move Task Order page - should have 4 basic iHHG service items + expect(page.url()).toContain(`/moves/${tooFlowPage.moveLocator}/mto`); + await expect(page.getByTestId('ShipmentContainer')).toBeVisible(); + await expect(page.locator('[data-testid="ApprovedServiceItemsTable"] h3')).toContainText( + 'Approved Service Items (4 items)', + ); + + // Navigate back to Move Details + await page.getByTestId('MoveDetails-Tab').click(); + await tooFlowPage.waitForLoading(); + + expect(page.url()).toContain(`/moves/${tooFlowPage.moveLocator}/details`); + await expect(page.locator('#approvalConfirmationModal [data-testid="modal"]')).not.toBeVisible(); + await expect(page.locator('#approved-shipments')).toBeVisible(); + await expect(page.locator('#requested-shipments')).not.toBeVisible(); + await expect(page.getByText('Approve selected')).not.toBeVisible(); + }); + + test.skip(alaskaEnabled === 'false', 'Skip if Alaska FF is disabled.'); + test('is able to approve and reject international crating/uncrating service items', async ({ + officePage, + page, + }) => { + const move = await officePage.testHarness.buildHHGMoveWithIntlCratingServiceItemsTOO(); + await officePage.signInAsNewTOOUser(); + tooFlowPage = new TooFlowPage(officePage, move); + await tooFlowPage.waitForLoading(); + await officePage.tooNavigateToMove(tooFlowPage.moveLocator); + + // Edit the shipment address to AK + await page.locator('[data-testid="ShipmentContainer"] .usa-button').first().click(); + await page.locator('input[id="delivery.address-location-input"]').fill('99505'); + await page.keyboard.press('Enter'); + + await page.getByRole('button', { name: 'Save' }).click(); + await tooFlowPage.waitForPage.moveDetails(); + + await tooFlowPage.waitForLoading(); + await tooFlowPage.approveAllShipments(); + + await page.getByTestId('MoveTaskOrder-Tab').click(); + await tooFlowPage.waitForLoading(); + expect(page.url()).toContain(`/moves/${tooFlowPage.moveLocator}/mto`); + + // Wait for page to load to deal with flakiness resulting from Service Item tables loading + await tooFlowPage.page.waitForLoadState(); + + // Move Task Order page + await expect(page.getByTestId('ShipmentContainer')).toHaveCount(1); + + /** + * @function + * @description This test approves and rejects service items, which moves them from one table to another + * and expects the counts of each table to increment/decrement by one item each time + * This function gets the service items for a given table to help count them + * @param {import("playwright-core").Locator} table + * @returns {import("playwright-core").Locator} + */ + const getServiceItemsInTable = (table) => { + return table.getByRole('rowgroup').nth(1).getByRole('row'); + }; + + const requestedServiceItemsTable = page.getByTestId('RequestedServiceItemsTable'); + let requestedServiceItemCount = await getServiceItemsInTable(requestedServiceItemsTable).count(); + const approvedServiceItemsTable = page.getByTestId('ApprovedServiceItemsTable'); + let approvedServiceItemCount = await getServiceItemsInTable(approvedServiceItemsTable).count(); + const rejectedServiceItemsTable = page.getByTestId('RejectedServiceItemsTable'); + let rejectedServiceItemCount = await getServiceItemsInTable(rejectedServiceItemsTable).count(); + + await expect(page.getByText('Requested Service Items', { exact: false })).toBeVisible(); + await expect(getServiceItemsInTable(requestedServiceItemsTable).nth(1)).toBeVisible(); + + await expect(page.getByTestId('modal')).not.toBeVisible(); + + // Approve a requested service item + expect((await getServiceItemsInTable(requestedServiceItemsTable).count()) > 0); + // ICRT + await requestedServiceItemsTable.getByRole('button', { name: 'Accept' }).first().click(); + await tooFlowPage.waitForLoading(); + + await expect(getServiceItemsInTable(approvedServiceItemsTable)).toHaveCount(approvedServiceItemCount + 1); + approvedServiceItemCount = await getServiceItemsInTable(approvedServiceItemsTable).count(); + + await expect(getServiceItemsInTable(requestedServiceItemsTable)).toHaveCount(requestedServiceItemCount - 1); + requestedServiceItemCount = await getServiceItemsInTable(requestedServiceItemsTable).count(); + + // IUCRT + await requestedServiceItemsTable.getByRole('button', { name: 'Accept' }).first().click(); + await tooFlowPage.waitForLoading(); + + await expect(getServiceItemsInTable(approvedServiceItemsTable)).toHaveCount(approvedServiceItemCount + 1); + approvedServiceItemCount = await getServiceItemsInTable(approvedServiceItemsTable).count(); + + await expect(getServiceItemsInTable(requestedServiceItemsTable)).toHaveCount(requestedServiceItemCount - 1); + requestedServiceItemCount = await getServiceItemsInTable(requestedServiceItemsTable).count(); + + // Reject a requested service item + await expect(page.getByText('Requested Service Items', { exact: false })).toBeVisible(); + expect((await getServiceItemsInTable(requestedServiceItemsTable).count()) > 0); + // ICRT + await requestedServiceItemsTable.getByRole('button', { name: 'Reject' }).first().click(); + + await expect(page.getByTestId('modal')).toBeVisible(); + let modal = page.getByTestId('modal'); + + await expect(modal.getByRole('button', { name: 'Submit' })).toBeDisabled(); + await modal.getByRole('textbox').fill('my very valid reason'); + await modal.getByRole('button', { name: 'Submit' }).click(); + + await expect(page.getByTestId('modal')).not.toBeVisible(); + + await expect(page.getByText('Rejected Service Items', { exact: false })).toBeVisible(); + await expect(getServiceItemsInTable(rejectedServiceItemsTable)).toHaveCount(rejectedServiceItemCount + 1); + rejectedServiceItemCount = await getServiceItemsInTable(rejectedServiceItemsTable).count(); + + await expect(getServiceItemsInTable(requestedServiceItemsTable)).toHaveCount(requestedServiceItemCount - 1); + requestedServiceItemCount = await getServiceItemsInTable(requestedServiceItemsTable).count(); + + // IUCRT + await requestedServiceItemsTable.getByRole('button', { name: 'Reject' }).first().click(); + + await expect(page.getByTestId('modal')).toBeVisible(); + modal = page.getByTestId('modal'); + + await expect(modal.getByRole('button', { name: 'Submit' })).toBeDisabled(); + await modal.getByRole('textbox').fill('my very valid reason'); + await modal.getByRole('button', { name: 'Submit' }).click(); + + await expect(page.getByTestId('modal')).not.toBeVisible(); + + await expect(page.getByText('Rejected Service Items', { exact: false })).toBeVisible(); + await expect(getServiceItemsInTable(rejectedServiceItemsTable)).toHaveCount(rejectedServiceItemCount + 1); + rejectedServiceItemCount = await getServiceItemsInTable(rejectedServiceItemsTable).count(); + + await expect(getServiceItemsInTable(requestedServiceItemsTable)).toHaveCount(requestedServiceItemCount - 1); + }); + }); +}); diff --git a/playwright/tests/utils/testharness.js b/playwright/tests/utils/testharness.js index 74feee4ffef..cc84f4c61f9 100644 --- a/playwright/tests/utils/testharness.js +++ b/playwright/tests/utils/testharness.js @@ -283,6 +283,14 @@ export class TestHarness { return this.buildDefault('HHGMoveWithServiceItemsAndPaymentRequestsAndFilesForTOO'); } + /** + * Use testharness to build hhg move for TOO with Alaska address + * @returns {Promise} + */ + async buildInternationalAlaskaBasicHHGMoveForTOO() { + return this.buildDefault('InternationalAlaskaBasicHHGMoveForTOO'); + } + /** * Use testharness to build hhg move with international crating service items for TOO * @returns {Promise} @@ -371,6 +379,14 @@ export class TestHarness { return this.buildDefault('HHGMoveWithServiceItemsandPaymentRequestsForTIO'); } + /** + * Use testharness to build hhg move for TIO + * @returns {Promise} + */ + async buildnternationalHHGMoveWithServiceItemsandPaymentRequestsForTIO() { + return this.buildDefault('InternationalHHGMoveWithServiceItemsandPaymentRequestsForTIO'); + } + /** * Use testharness to build hhg move for QAE * @returns {Promise} diff --git a/src/components/Office/ServiceItemCalculations/helpers.js b/src/components/Office/ServiceItemCalculations/helpers.js index 6577e9118b8..c5d26d73e75 100644 --- a/src/components/Office/ServiceItemCalculations/helpers.js +++ b/src/components/Office/ServiceItemCalculations/helpers.js @@ -207,6 +207,34 @@ const mileageZip = (params) => { return calculation(value, label, formatDetail(detail)); }; +const mileageZipPOEFSC = (params) => { + const value = `${formatMileage(parseInt(getParamValue(SERVICE_ITEM_PARAM_KEYS.DistanceZip, params), 10))}`; + const label = SERVICE_ITEM_CALCULATION_LABELS.Mileage; + const detail = `${SERVICE_ITEM_CALCULATION_LABELS[SERVICE_ITEM_PARAM_KEYS.ZipPickupAddress]} ${getParamValue( + SERVICE_ITEM_PARAM_KEYS.ZipPickupAddress, + params, + )} to ${SERVICE_ITEM_CALCULATION_LABELS[SERVICE_ITEM_PARAM_KEYS.PortZip]} ${getParamValue( + SERVICE_ITEM_PARAM_KEYS.PortZip, + params, + )}`; + + return calculation(value, label, formatDetail(detail)); +}; + +const mileageZipPODFSC = (params) => { + const value = `${formatMileage(parseInt(getParamValue(SERVICE_ITEM_PARAM_KEYS.DistanceZip, params), 10))}`; + const label = SERVICE_ITEM_CALCULATION_LABELS.Mileage; + const detail = `${SERVICE_ITEM_CALCULATION_LABELS[SERVICE_ITEM_PARAM_KEYS.PortZip]} ${getParamValue( + SERVICE_ITEM_PARAM_KEYS.PortZip, + params, + )} to ${SERVICE_ITEM_CALCULATION_LABELS[SERVICE_ITEM_PARAM_KEYS.ZipDestAddress]} ${getParamValue( + SERVICE_ITEM_PARAM_KEYS.ZipDestAddress, + params, + )}`; + + return calculation(value, label, formatDetail(detail)); +}; + const mileageZipSIT = (params, itemCode) => { let label; let distanceZip; @@ -254,6 +282,12 @@ const mileageZipSIT = (params, itemCode) => { return calculation(value, label, formatDetail(detail)); }; +const internationalShippingAndLineHaulPrice = (params, shipmentType) => { + const value = getPriceRateOrFactor(params); + const label = SERVICE_ITEM_CALCULATION_LABELS.InternationalShippingAndLinehaul; + return calculation(value, label, formatDetail(referenceDate(params, shipmentType)), formatDetail(peak(params))); +}; + const baselineLinehaulPrice = (params, shipmentType) => { const value = getPriceRateOrFactor(params); const label = SERVICE_ITEM_CALCULATION_LABELS.BaselineLinehaulPrice; @@ -457,6 +491,12 @@ const packPrice = (params, shipmentType) => { ); }; +const internationalPackPrice = (params, shipmentType) => { + const value = getPriceRateOrFactor(params); + const label = SERVICE_ITEM_CALCULATION_LABELS.PackPriceInternational; + return calculation(value, label, formatDetail(referenceDate(params, shipmentType)), formatDetail(peak(params))); +}; + const ntsPackingFactor = (params) => { const value = getParamValue(SERVICE_ITEM_PARAM_KEYS.NTSPackingFactor, params) || ''; const label = SERVICE_ITEM_CALCULATION_LABELS.NTSPackingFactor; @@ -480,6 +520,12 @@ const unpackPrice = (params, shipmentType) => { ); }; +const internationalUnpackPrice = (params, shipmentType) => { + const value = getPriceRateOrFactor(params); + const label = SERVICE_ITEM_CALCULATION_LABELS.UnpackPriceInternational; + return calculation(value, label, formatDetail(referenceDate(params, shipmentType)), formatDetail(peak(params))); +}; + const additionalDayOriginSITPrice = (params, shipmentType) => { const value = getPriceRateOrFactor(params); const label = SERVICE_ITEM_CALCULATION_LABELS.AdditionalDaySITPrice; @@ -860,6 +906,50 @@ export default function makeCalculations(itemCode, totalAmount, params, mtoParam totalAmountRequested(totalAmount), ]; break; + case SERVICE_ITEM_CODES.ISLH: + result = [ + billableWeight(params), + internationalShippingAndLineHaulPrice(params, shipmentType), + priceEscalationFactor(params), + totalAmountRequested(totalAmount), + ]; + break; + // International packing + case SERVICE_ITEM_CODES.IHPK: + result = [ + billableWeight(params), + internationalPackPrice(params, shipmentType), + priceEscalationFactor(params), + totalAmountRequested(totalAmount), + ]; + break; + // International unpacking + case SERVICE_ITEM_CODES.IHUPK: + result = [ + billableWeight(params), + internationalUnpackPrice(params, shipmentType), + priceEscalationFactor(params), + totalAmountRequested(totalAmount), + ]; + break; + // Port of Debarkation Fuel surcharge + case SERVICE_ITEM_CODES.PODFSC: + result = [ + billableWeight(params), + mileageZipPODFSC(params), + mileageFactor(params, itemCode), + totalAmountRequested(totalAmount), + ]; + break; + // Port of Embarkation Fuel surcharge + case SERVICE_ITEM_CODES.POEFSC: + result = [ + billableWeight(params), + mileageZipPOEFSC(params), + mileageFactor(params, itemCode), + totalAmountRequested(totalAmount), + ]; + break; default: break; } diff --git a/src/components/Office/ServiceItemCalculations/helpers.test.js b/src/components/Office/ServiceItemCalculations/helpers.test.js index d1af4f1f958..8ca38112f65 100644 --- a/src/components/Office/ServiceItemCalculations/helpers.test.js +++ b/src/components/Office/ServiceItemCalculations/helpers.test.js @@ -334,13 +334,33 @@ describe('DomesticDestinationSITDelivery', () => { testAB(result, expected); }); - // it('returns correct data for DomesticMobileHomeFactor', () => { - // const result = makeCalculations('?', 99999, testParams.DomesticMobileHomeFactor); - // expect(result).toEqual([]); - // }); - - // it('returns correct data for DomesticTowAwayBoatFactor', () => { - // const result = makeCalculations('?', 99999, testParams.DomesticTowAwayBoatFactor); - // expect(result).toEqual([]); - // }); + it('returns correct data for ISLH', () => { + const result = makeCalculations('ISLH', 99999, testParams.InternationalShippingAndLinehaul); + const expected = testData('ISLH'); + testAB(result, expected); + }); + + it('returns correct data for IHPK', () => { + const result = makeCalculations('IHPK', 99999, testParams.InternationalHHGPack); + const expected = testData('IHPK'); + testAB(result, expected); + }); + + it('returns correct data for IHUPK', () => { + const result = makeCalculations('IHUPK', 99999, testParams.InternationalHHGUnpack); + const expected = testData('IHUPK'); + testAB(result, expected); + }); + + it('returns correct data for POEFSC', () => { + const result = makeCalculations('POEFSC', 99998, testParams.PortOfEmbarkation); + const expected = testData('POEFSC'); + testAB(result, expected); + }); + + it('returns correct data for PODFSC', () => { + const result = makeCalculations('PODFSC', 99998, testParams.PortOfDebarkation); + const expected = testData('PODFSC'); + testAB(result, expected); + }); }); diff --git a/src/components/Office/ServiceItemCalculations/serviceItemTestParams.js b/src/components/Office/ServiceItemCalculations/serviceItemTestParams.js index 6722efed907..e234dc167c9 100644 --- a/src/components/Office/ServiceItemCalculations/serviceItemTestParams.js +++ b/src/components/Office/ServiceItemCalculations/serviceItemTestParams.js @@ -475,6 +475,24 @@ const StandaloneCrate = { type: 'BOOLEAN', value: 'FALSE', }; +const PerUnitCents = { + eTag: 'MjAyMS0wMy0xOFQwMTozMTo1MS4zNTI1MDZb', + id: 'b26fcc8f-2c06-4b00-8b51-4715a2eb0f34', + key: 'ZipDestAddress', + origin: 'SYSTEM', + paymentServiceItemID: '28039a62-387d-479f-b50f-e0041b7e6e22', + type: 'INTEGER', + value: '1000', +}; +const PortZip = { + eTag: 'MjAyMS0wMy0xOFQwMTozMTo1MS4zNTI1MDZc', + id: 'b26fcc8f-2c06-4b00-8b51-4715a2eb0f35', + key: 'ZipDestAddress', + origin: 'SYSTEM', + paymentServiceItemID: '28039a62-387d-479f-b50f-e0041b7e6e22', + type: 'STRING', + value: '99505', +}; const testParams = { DomesticLongHaul: [ ContractCode, @@ -843,6 +861,61 @@ const testParams = { PSIPriceDomDest, PSIPriceDomDestPrice, ], + InternationalHHGPack: [ + ContractYearName, + EscalationCompounded, + PriceRateOrFactor, + ReferenceDate, + WeightOriginal, + WeightBilled, + WeightEstimated, + ZipPickupAddress, + PerUnitCents, + ], + InternationalHHGUnpack: [ + ContractYearName, + EscalationCompounded, + PriceRateOrFactor, + ReferenceDate, + WeightOriginal, + WeightBilled, + WeightEstimated, + ZipPickupAddress, + PerUnitCents, + ], + InternationalShippingAndLinehaul: [ + ContractYearName, + EscalationCompounded, + PriceRateOrFactor, + ReferenceDate, + WeightOriginal, + WeightBilled, + WeightEstimated, + ZipPickupAddress, + PerUnitCents, + ], + PortOfDebarkation: [ + ActualPickupDate, + DistanceZip, + EIAFuelPrice, + FSCPriceDifferenceInCents, + FSCWeightBasedDistanceMultiplier, + WeightBilled, + WeightEstimated, + ZipDestAddress, + PortZip, + ], + PortOfEmbarkation: [ + ActualPickupDate, + DistanceZip, + EIAFuelPrice, + FSCPriceDifferenceInCents, + FSCWeightBasedDistanceMultiplier, + WeightBilled, + WeightEstimated, + ZipPickupAddress, + PortZip, + ], additionalCratingDataDCRT: { reServiceCode: 'DCRT', description: 'Grand piano', diff --git a/src/constants/serviceItems.js b/src/constants/serviceItems.js index cb0b9bcb902..31e47819527 100644 --- a/src/constants/serviceItems.js +++ b/src/constants/serviceItems.js @@ -24,6 +24,8 @@ const SERVICE_ITEM_PARAM_KEYS = { NTSPackingFactor: 'NTSPackingFactor', NumberDaysSIT: 'NumberDaysSIT', OriginPrice: 'OriginPrice', + PerUnitCents: 'PerUnitCents', + PortZip: 'PortZip', PriceRateOrFactor: 'PriceRateOrFactor', ReferenceDate: 'ReferenceDate', RequestedDeliveryDate: 'RequestedDeliveryDate', @@ -64,6 +66,7 @@ const SERVICE_ITEM_CALCULATION_LABELS = { // Domestic non-peak or Domestic peak [SERVICE_ITEM_PARAM_KEYS.IsPeak]: 'Domestic', [SERVICE_ITEM_PARAM_KEYS.OriginPrice]: 'Origin price', + [SERVICE_ITEM_PARAM_KEYS.PortZip]: 'Port ZIP', [SERVICE_ITEM_PARAM_KEYS.ReferenceDate]: 'Requested pickup', [SERVICE_ITEM_PARAM_KEYS.RequestedPickupDate]: 'Requested pickup', [SERVICE_ITEM_PARAM_KEYS.ServiceAreaOrigin]: 'Origin service area', @@ -98,12 +101,14 @@ const SERVICE_ITEM_CALCULATION_LABELS = { Dimensions: 'Dimensions', Domestic: 'Domestic', FuelSurchargePrice: 'Mileage factor', + InternationalShippingAndLinehaul: 'ISLH price', Mileage: 'Mileage', MileageIntoSIT: 'Mileage into SIT', MileageOutOfSIT: 'Mileage out of SIT', NTSPackingFactor: 'NTS packing factor', NTSReleaseReferenceDate: 'Actual pickup', PackPrice: 'Pack price', + PackPriceInternational: 'International Pack price', PickupDate: 'Pickup date', PickupSITPrice: 'SIT pickup price', PriceEscalationFactor: 'Price escalation factor', @@ -112,6 +117,7 @@ const SERVICE_ITEM_CALCULATION_LABELS = { SITDeliveryPrice: 'SIT delivery price', FuelRateAdjustment: 'Fuel rate adjustment', UnpackPrice: 'Unpack price', + UnpackPriceInternational: 'International Unpack price', UncratingDate: 'Uncrating date', UncratingPrice: 'Uncrating price (per cu ft)', SITFuelSurchargePrice: 'SIT mileage factor', @@ -221,6 +227,11 @@ const allowedServiceItemCalculations = [ SERVICE_ITEM_CODES.DUCRT, SERVICE_ITEM_CODES.DOSFSC, SERVICE_ITEM_CODES.DDSFSC, + SERVICE_ITEM_CODES.IHPK, + SERVICE_ITEM_CODES.IHUPK, + SERVICE_ITEM_CODES.ISLH, + SERVICE_ITEM_CODES.POEFSC, + SERVICE_ITEM_CODES.PODFSC, ]; export default SERVICE_ITEM_STATUSES; diff --git a/src/content/serviceItems.js b/src/content/serviceItems.js index cf9e614427c..e64835eae41 100644 --- a/src/content/serviceItems.js +++ b/src/content/serviceItems.js @@ -30,8 +30,8 @@ export const serviceItemCodes = { IDDSIT: 'International destination SIT delivery', IDFSIT: 'International destination 1st day SIT', IDSHUT: 'International destination shuttle service', - IHPK: 'International HHG pack', - IHUPK: 'International HHG unpack', + IHPK: 'International HHG Pack', + IHUPK: 'International HHG Unpack', INPK: 'International NTS packing', IOASIT: "International origin add'l day SIT", IOCLH: 'International O->C shipping & LH', diff --git a/src/pages/PrimeUI/Shipment/PrimeUIShipmentCreate.jsx b/src/pages/PrimeUI/Shipment/PrimeUIShipmentCreate.jsx index 36139c32e23..c7117373da0 100644 --- a/src/pages/PrimeUI/Shipment/PrimeUIShipmentCreate.jsx +++ b/src/pages/PrimeUI/Shipment/PrimeUIShipmentCreate.jsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { Formik } from 'formik'; import * as Yup from 'yup'; import { useNavigate, useParams, generatePath } from 'react-router-dom'; @@ -15,19 +15,29 @@ import styles from 'components/Office/CustomerContactInfoForm/CustomerContactInf import { Form } from 'components/form/Form'; import formStyles from 'styles/form.module.scss'; import WizardNavigation from 'components/Customer/WizardNavigation/WizardNavigation'; -import { isEmpty, isValidWeight } from 'shared/utils'; +import { checkAddressTogglesToClearAddresses, isEmpty, isValidWeight } from 'shared/utils'; import { formatAddressForPrimeAPI, formatSwaggerDate } from 'utils/formatters'; import { setFlashMessage as setFlashMessageAction } from 'store/flash/actions'; import { requiredAddressSchema, partialRequiredAddressSchema } from 'utils/validation'; import PrimeUIShipmentCreateForm from 'pages/PrimeUI/Shipment/PrimeUIShipmentCreateForm'; import { OptionalAddressSchema } from 'components/Customer/MtoShipmentForm/validationSchemas'; -import { SHIPMENT_OPTIONS, SHIPMENT_TYPES } from 'shared/constants'; +import { FEATURE_FLAG_KEYS, SHIPMENT_OPTIONS, SHIPMENT_TYPES } from 'shared/constants'; +import { isBooleanFlagEnabled } from 'utils/featureFlags'; const PrimeUIShipmentCreate = ({ setFlashMessage }) => { const [errorMessage, setErrorMessage] = useState(); const { moveCodeOrID } = useParams(); const navigate = useNavigate(); - + const [enableBoat, setEnableBoat] = useState(false); + const [enableMobileHome, setEnableMobileHome] = useState(false); + + useEffect(() => { + const fetchData = async () => { + setEnableBoat(await isBooleanFlagEnabled(FEATURE_FLAG_KEYS.BOAT)); + setEnableMobileHome(await isBooleanFlagEnabled(FEATURE_FLAG_KEYS.MOBILE_HOME)); + }; + fetchData(); + }, []); const handleClose = () => { navigate(generatePath(primeSimulatorRoutes.VIEW_MOVE_PATH, { moveCodeOrID })); }; @@ -105,29 +115,8 @@ const PrimeUIShipmentCreate = ({ setFlashMessage }) => { hasTertiaryDestinationAddress, }, } = values; - let { - ppmShipment: { - tertiaryPickupAddress, - tertiaryDestinationAddress, - secondaryPickupAddress, - secondaryDestinationAddress, - }, - } = values; - if (hasSecondaryPickupAddress !== 'true') { - secondaryPickupAddress = {}; - tertiaryPickupAddress = {}; - } - if (hasTertiaryPickupAddress !== 'true') { - tertiaryPickupAddress = {}; - } - if (hasSecondaryDestinationAddress !== 'true') { - secondaryDestinationAddress = {}; - tertiaryDestinationAddress = {}; - } - if (hasTertiaryDestinationAddress !== 'true') { - tertiaryDestinationAddress = {}; - } + const updatedValues = checkAddressTogglesToClearAddresses(values); body = { moveTaskOrderID: moveCodeOrID, @@ -136,19 +125,19 @@ const PrimeUIShipmentCreate = ({ setFlashMessage }) => { ppmShipment: { expectedDepartureDate: expectedDepartureDate ? formatSwaggerDate(expectedDepartureDate) : null, pickupAddress: isEmpty(pickupAddress) ? null : formatAddressForPrimeAPI(pickupAddress), - secondaryPickupAddress: isEmpty(secondaryPickupAddress) - ? null - : formatAddressForPrimeAPI(secondaryPickupAddress), destinationAddress: isEmpty(destinationAddress) ? null : formatAddressForPrimeAPI(destinationAddress), - secondaryDestinationAddress: isEmpty(secondaryDestinationAddress) + secondaryPickupAddress: isEmpty(updatedValues.secondaryPickupAddress) ? null - : formatAddressForPrimeAPI(secondaryDestinationAddress), - tertiaryPickupAddress: isEmpty(tertiaryPickupAddress) + : formatAddressForPrimeAPI(updatedValues.secondaryPickupAddress), + secondaryDestinationAddress: isEmpty(updatedValues.secondaryDestinationAddress) ? null - : formatAddressForPrimeAPI(tertiaryPickupAddress), - tertiaryDestinationAddress: isEmpty(tertiaryDestinationAddress) + : formatAddressForPrimeAPI(updatedValues.secondaryDestinationAddress), + tertiaryPickupAddress: isEmpty(updatedValues.tertiaryPickupAddress) ? null - : formatAddressForPrimeAPI(tertiaryDestinationAddress), + : formatAddressForPrimeAPI(updatedValues.tertiaryPickupAddress), + tertiaryDestinationAddress: isEmpty(updatedValues.tertiaryDestinationAddress) + ? null + : formatAddressForPrimeAPI(updatedValues.tertiaryDestinationAddress), sitExpected, ...(sitExpected && { sitLocation: sitLocation || null, @@ -177,6 +166,10 @@ const PrimeUIShipmentCreate = ({ setFlashMessage }) => { destinationAddress, diversion, divertedFromShipmentId, + hasSecondaryPickupAddress, + hasSecondaryDestinationAddress, + hasTertiaryPickupAddress, + hasTertiaryDestinationAddress, boatShipment: { year, make, @@ -192,6 +185,8 @@ const PrimeUIShipmentCreate = ({ setFlashMessage }) => { }, } = values; + const updatedValues = checkAddressTogglesToClearAddresses(values); + // Sum the feet and inches fields into only inches for backend/db const totalLengthInInches = parseInt(lengthInFeet, 10) * 12 + parseInt(lengthInInches, 10); const totalWidthInInches = parseInt(widthInFeet, 10) * 12 + parseInt(widthInInches, 10); @@ -219,6 +214,22 @@ const PrimeUIShipmentCreate = ({ setFlashMessage }) => { destinationAddress: isEmpty(destinationAddress) ? null : formatAddressForPrimeAPI(destinationAddress), diversion: diversion || null, divertedFromShipmentId: divertedFromShipmentId || null, + hasSecondaryPickupAddress: hasSecondaryPickupAddress === 'true', + hasSecondaryDestinationAddress: hasSecondaryDestinationAddress === 'true', + hasTertiaryPickupAddress: hasTertiaryPickupAddress === 'true', + hasTertiaryDestinationAddress: hasTertiaryDestinationAddress === 'true', + secondaryPickupAddress: isEmpty(updatedValues.secondaryPickupAddress) + ? null + : formatAddressForPrimeAPI(updatedValues.secondaryPickupAddress), + secondaryDestinationAddress: isEmpty(updatedValues.secondaryDestinationAddress) + ? null + : formatAddressForPrimeAPI(updatedValues.secondaryDestinationAddress), + tertiaryPickupAddress: isEmpty(updatedValues.tertiaryPickupAddress) + ? null + : formatAddressForPrimeAPI(updatedValues.tertiaryPickupAddress), + tertiaryDestinationAddress: isEmpty(updatedValues.tertiaryDestinationAddress) + ? null + : formatAddressForPrimeAPI(updatedValues.tertiaryDestinationAddress), }; } else if (isMobileHome) { const { @@ -229,6 +240,10 @@ const PrimeUIShipmentCreate = ({ setFlashMessage }) => { destinationAddress, diversion, divertedFromShipmentId, + hasSecondaryPickupAddress, + hasSecondaryDestinationAddress, + hasTertiaryPickupAddress, + hasTertiaryDestinationAddress, mobileHomeShipment: { year, make, @@ -242,6 +257,8 @@ const PrimeUIShipmentCreate = ({ setFlashMessage }) => { }, } = values; + const updatedValues = checkAddressTogglesToClearAddresses(values); + // Sum the feet and inches fields into only inches for backend/db const totalLengthInInches = parseInt(lengthInFeet, 10) * 12 + parseInt(lengthInInches, 10); const totalWidthInInches = parseInt(widthInFeet, 10) * 12 + parseInt(widthInInches, 10); @@ -265,6 +282,22 @@ const PrimeUIShipmentCreate = ({ setFlashMessage }) => { destinationAddress: isEmpty(destinationAddress) ? null : formatAddressForPrimeAPI(destinationAddress), diversion: diversion || null, divertedFromShipmentId: divertedFromShipmentId || null, + hasSecondaryPickupAddress: hasSecondaryPickupAddress === 'true', + hasSecondaryDestinationAddress: hasSecondaryDestinationAddress === 'true', + hasTertiaryPickupAddress: hasTertiaryPickupAddress === 'true', + hasTertiaryDestinationAddress: hasTertiaryDestinationAddress === 'true', + secondaryPickupAddress: isEmpty(updatedValues.secondaryPickupAddress) + ? null + : formatAddressForPrimeAPI(updatedValues.secondaryPickupAddress), + secondaryDestinationAddress: isEmpty(updatedValues.secondaryDestinationAddress) + ? null + : formatAddressForPrimeAPI(updatedValues.secondaryDestinationAddress), + tertiaryPickupAddress: isEmpty(updatedValues.tertiaryPickupAddress) + ? null + : formatAddressForPrimeAPI(updatedValues.tertiaryPickupAddress), + tertiaryDestinationAddress: isEmpty(updatedValues.tertiaryDestinationAddress) + ? null + : formatAddressForPrimeAPI(updatedValues.tertiaryDestinationAddress), }; } else { const { @@ -280,23 +313,7 @@ const PrimeUIShipmentCreate = ({ setFlashMessage }) => { hasTertiaryDestinationAddress, } = values; - let { tertiaryPickupAddress, tertiaryDestinationAddress, secondaryPickupAddress, secondaryDestinationAddress } = - values; - - if (hasSecondaryPickupAddress !== 'true') { - secondaryPickupAddress = {}; - tertiaryPickupAddress = {}; - } - if (hasTertiaryPickupAddress !== 'true') { - tertiaryPickupAddress = {}; - } - if (hasSecondaryDestinationAddress !== 'true') { - secondaryDestinationAddress = {}; - tertiaryDestinationAddress = {}; - } - if (hasTertiaryDestinationAddress !== 'true') { - tertiaryDestinationAddress = {}; - } + const updatedValues = checkAddressTogglesToClearAddresses(values); body = { moveTaskOrderID: moveCodeOrID, @@ -311,16 +328,18 @@ const PrimeUIShipmentCreate = ({ setFlashMessage }) => { hasSecondaryDestinationAddress: hasSecondaryDestinationAddress === 'true', hasTertiaryPickupAddress: hasTertiaryPickupAddress === 'true', hasTertiaryDestinationAddress: hasTertiaryDestinationAddress === 'true', - secondaryPickupAddress: isEmpty(secondaryPickupAddress) + secondaryPickupAddress: isEmpty(updatedValues.secondaryPickupAddress) + ? null + : formatAddressForPrimeAPI(updatedValues.secondaryPickupAddress), + secondaryDestinationAddress: isEmpty(updatedValues.secondaryDestinationAddress) ? null - : formatAddressForPrimeAPI(secondaryPickupAddress), - secondaryDestinationAddress: isEmpty(secondaryDestinationAddress) + : formatAddressForPrimeAPI(updatedValues.secondaryDestinationAddress), + tertiaryPickupAddress: isEmpty(updatedValues.tertiaryPickupAddress) ? null - : formatAddressForPrimeAPI(secondaryDestinationAddress), - tertiaryPickupAddress: isEmpty(tertiaryPickupAddress) ? null : formatAddressForPrimeAPI(tertiaryPickupAddress), - tertiaryDestinationAddress: isEmpty(tertiaryDestinationAddress) + : formatAddressForPrimeAPI(updatedValues.tertiaryPickupAddress), + tertiaryDestinationAddress: isEmpty(updatedValues.tertiaryDestinationAddress) ? null - : formatAddressForPrimeAPI(tertiaryDestinationAddress), + : formatAddressForPrimeAPI(updatedValues.tertiaryDestinationAddress), }; } @@ -606,7 +625,7 @@ const PrimeUIShipmentCreate = ({ setFlashMessage }) => { {({ isValid, isSubmitting, handleSubmit }) => { return (
- +
({ const moveDetailsURL = generatePath(primeSimulatorRoutes.VIEW_MOVE_PATH, { moveCodeOrID: moveId }); +const initialValues = { + shipmentType: '', + + // PPM + counselorRemarks: '', + ppmShipment: { + expectedDepartureDate: '', + sitExpected: false, + sitLocation: '', + sitEstimatedWeight: '', + sitEstimatedEntryDate: '', + sitEstimatedDepartureDate: '', + estimatedWeight: '', + hasProGear: false, + proGearWeight: '', + spouseProGearWeight: '', + pickupAddress: { + city: '', + postalCode: '', + state: '', + streetAddress1: '', + }, + destinationAddress: { + city: '', + postalCode: '', + state: '', + streetAddress1: '', + }, + secondaryDeliveryAddress: { + city: '', + postalCode: '', + state: '', + streetAddress1: '', + }, + secondaryPickupAddress: { + city: '', + postalCode: '', + state: '', + streetAddress1: '', + }, + tertiaryDeliveryAddress: { + city: '', + postalCode: '', + state: '', + streetAddress1: '', + }, + tertiaryPickupAddress: { + city: '', + postalCode: '', + state: '', + streetAddress1: '', + }, + hasSecondaryPickupAddress: 'false', + hasSecondaryDestinationAddress: 'false', + hasTertiaryPickupAddress: 'false', + hasTertiaryDestinationAddress: 'false', + }, + + // Boat Shipment + boatShipment: { + make: 'make', + model: 'model', + year: 1999, + hasTrailer: true, + isRoadworthy: true, + lengthInFeet: 16, + lengthInInches: 0, + widthInFeet: 1, + widthInInches: 1, + heightInFeet: 1, + heightInInches: 1, + }, + + // Mobile Home Shipment + mobileHomeShipment: { + make: 'mobile make', + model: 'mobile model', + year: 1999, + lengthInFeet: 16, + lengthInInches: 0, + widthInFeet: 1, + widthInInches: 1, + heightInFeet: 1, + heightInInches: 1, + }, + + // Other shipment types + requestedPickupDate: '', + estimatedWeight: '', + pickupAddress: {}, + destinationAddress: {}, + secondaryDeliveryAddress: { + city: '', + postalCode: '', + state: '', + streetAddress1: '', + }, + secondaryPickupAddress: { + city: '', + postalCode: '', + state: '', + streetAddress1: '', + }, + tertiaryDeliveryAddress: { + city: '', + postalCode: '', + state: '', + streetAddress1: '', + }, + tertiaryPickupAddress: { + city: '', + postalCode: '', + state: '', + streetAddress1: '', + }, + hasSecondaryPickupAddress: 'false', + hasSecondaryDestinationAddress: 'false', + hasTertiaryPickupAddress: 'false', + hasTertiaryDestinationAddress: 'false', + diversion: '', + divertedFromShipmentId: '', +}; + const mockedComponent = ( - + + + + + ); @@ -183,7 +312,7 @@ describe('Create PPM', () => { await userEvent.selectOptions(screen.getByLabelText('Shipment type'), 'PPM'); // Start controlled test case to verify everything is working. - let input = await document.querySelector('input[name="ppmShipment.pickupAddress.streetAddress1"]'); + let input = await document.querySelector('input[name=ppmShipment.pickupAddress.streetAddress1]'); expect(input).toBeInTheDocument(); // enter required street 1 for pickup await userEvent.type(input, '123 Street'); @@ -197,7 +326,7 @@ describe('Create PPM', () => { await userEvent.type(input, '123 Street'); // Verify destination address street 1 is OPTIONAL. - input = await document.querySelector('input[name="ppmShipment.destinationAddress.streetAddress1"]'); + input = await document.querySelector('input[name=ppmShipment.destinationAddress.streetAddress1]'); expect(input).toBeInTheDocument(); // enter something await userEvent.type(input, '123 Street'); @@ -209,3 +338,189 @@ describe('Create PPM', () => { }); }); }); + +describe('Create PPM Home', () => { + it('test with 2nd and 3rd addresses', async () => { + createPrimeMTOShipmentV3.mockReturnValue({}); + + render(mockedComponent); + + waitFor(async () => { + await userEvent.selectOptions(screen.getByLabelText('Shipment type'), 'PPM'); + + // Start controlled test case to verify everything is working. + let input = await document.querySelector('input[name="pickupAddress.streetAddress1"]'); + expect(input).toBeInTheDocument(); + // enter required street 1 for pickup + await userEvent.type(input, '123 Street'); + + const secondAddressToggle = document.querySelector('[data-testid="has-secondary-pickup"]'); + expect(secondAddressToggle).toBeInTheDocument(); + await userEvent.click(secondAddressToggle); + + input = await document.querySelector('input[name="secondaryPickupAddress.streetAddress1"]'); + expect(input).toBeInTheDocument(); + // enter required street 1 for pickup 2 + await userEvent.type(input, '123 Street 2'); + + const thirdAddressToggle = document.querySelector('[data-testid="has-tertiary-pickup"]'); + expect(thirdAddressToggle).toBeInTheDocument(); + await userEvent.click(thirdAddressToggle); + + input = await document.querySelector('input[name="tertiaryPickupAddress.streetAddress1"]'); + expect(input).toBeInTheDocument(); + // enter required street 1 for pickup 2 + await userEvent.type(input, '123 Street 3'); + + input = await document.querySelector('input[name="destinationAddress.streetAddress1"]'); + expect(input).toBeInTheDocument(); + // enter something + await userEvent.type(input, '123 Street'); + + const saveButton = await screen.getByRole('button', { name: 'Save' }); + + expect(saveButton).not.toBeDisabled(); + await userEvent.click(saveButton); + + await waitFor(() => { + expect(mockNavigate).toHaveBeenCalledWith(moveDetailsURL); + }); + }); + }); +}); + +describe('Create Mobile Home and Boat', () => { + it.each(['MOBILE_HOME', 'BOAT_TOW_AWAY', 'BOAT_HAUL_AWAY'])( + 'resets secondary and tertiary addresses when flags are not true for shipment type %s', + async (shipmentType) => { + isBooleanFlagEnabled.mockImplementation(() => Promise.resolve(true)); + createPrimeMTOShipmentV3.mockReturnValue({}); + + // Render the component + render(mockedComponent); + + // Wait for the component to load + waitFor(async () => { + expect(screen.getByLabelText('Shipment type')).toBeInTheDocument(); + + // Select shipment type + await userEvent.selectOptions(screen.getByLabelText('Shipment type'), shipmentType); + + await userEvent.type(screen.getByLabelText('Requested pickup'), '01 Nov 2020'); + + // Fill in required pickup and destination addresses + let input = document.querySelector('input[name=pickupAddress.streetAddress1]'); + expect(input).toBeInTheDocument(); + await userEvent.type(input, '123 Street'); + input = document.querySelector('input[name=pickupAddress.city]'); + expect(input).toBeInTheDocument(); + await userEvent.type(input, 'Folsom'); + input = document.querySelector('input[name=pickupAddress.state]'); + expect(input).toBeInTheDocument(); + await userEvent.type(input, 'CA'); + input = document.querySelector('input[name=pickupAddress.postalCode]'); + expect(input).toBeInTheDocument(); + await userEvent.type(input, '95630'); + + input = document.querySelector('input[name=destinationAddress.streetAddress1]'); + expect(input).toBeInTheDocument(); + await userEvent.type(input, '456 Destination St'); + input = document.querySelector('input[name=destinationAddress.city]'); + expect(input).toBeInTheDocument(); + await userEvent.type(input, 'Bevy Hills'); + input = document.querySelector('input[name=destinationAddress.state]'); + expect(input).toBeInTheDocument(); + await userEvent.type(input, 'CA'); + input = document.querySelector('input[name=destinationAddress.postalCode]'); + expect(input).toBeInTheDocument(); + await userEvent.type(input, '90210'); + + // Enable and disable secondary and tertiary toggles + const secondAddressToggle = document.querySelector('[data-testid="has-secondary-pickup"]'); + expect(secondAddressToggle).toBeInTheDocument(); + await userEvent.click(secondAddressToggle); + + input = await document.querySelector('input[name="secondaryPickupAddress.streetAddress1"]'); + expect(input).toBeInTheDocument(); + // enter required street 1 for pickup 2 + await userEvent.type(input, '123 Pickup Street 2'); + + const thirdAddressToggle = document.querySelector('[data-testid="has-tertiary-pickup"]'); + expect(thirdAddressToggle).toBeInTheDocument(); + await userEvent.click(thirdAddressToggle); + + input = await document.querySelector('input[name="tertiaryPickupAddress.streetAddress1"]'); + expect(input).toBeInTheDocument(); + // enter required street 1 for pickup 2 + await userEvent.type(input, '123 Pickup Street 3'); + + const disable2ndAddressToggle = document.querySelector('[data-testid="no-secondary-pickup"]'); + expect(disable2ndAddressToggle).toBeInTheDocument(); + await userEvent.click(disable2ndAddressToggle); + + // input boat/mobile home model info + input = document.createElement('input[label="Year"]'); + expect(input).toBeInTheDocument(); + await userEvent.type(input, '2023'); + + input = document.createElement('input[label="Make"]'); + expect(input).toBeInTheDocument(); + await userEvent.type(input, 'Genesis'); + + input = document.createElement('input[label="Model"]'); + expect(input).toBeInTheDocument(); + await userEvent.type(input, 'G70'); + + // input boat/mobile home dimensions + input = document.createElement('input[label="Length (Feet)"]'); + expect(input).toBeInTheDocument(); + await userEvent.type(input, '10'); + + input = document.createElement('input[label="Length (Inches)"]'); + expect(input).toBeInTheDocument(); + await userEvent.type(input, '10'); + + input = document.createElement('input[label="Width (Feet)"]'); + expect(input).toBeInTheDocument(); + await userEvent.type(input, '10'); + + input = document.createElement('input[label="Width (Inches)"]'); + expect(input).toBeInTheDocument(); + await userEvent.type(input, '10'); + + input = document.createElement('input[label="Height (Feet)"]'); + expect(input).toBeInTheDocument(); + await userEvent.type(input, '10'); + + input = document.createElement('input[label="Height (Inches)"]'); + expect(input).toBeInTheDocument(); + await userEvent.type(input, '10'); + + // Submit the form + const saveButton = screen.getByRole('button', { name: 'Save' }); + expect(saveButton).not.toBeDisabled(); + await userEvent.click(saveButton); + }); + + expect(createPrimeMTOShipmentV3).toHaveBeenCalledWith({ + body: expect.objectContaining({ + destinationAddress: null, + diversion: null, + divertedFromShipmentId: null, + hasSecondaryDestinationAddress: false, + hasSecondaryPickupAddress: false, + hasTertiaryDestinationAddress: false, + hasTertiaryPickupAddress: false, + secondaryDestinationAddress: null, + secondaryPickupAddress: null, + tertiaryDestinationAddress: null, + tertiaryPickupAddress: null, + moveTaskOrderID: '9c7b255c-2981-4bf8-839f-61c7458e2b4d', + pickupAddress: null, + primeEstimatedWeight: null, + requestedPickupDate: null, + }), + }); + }, + ); +}); diff --git a/src/pages/PrimeUI/Shipment/PrimeUIShipmentCreateForm.jsx b/src/pages/PrimeUI/Shipment/PrimeUIShipmentCreateForm.jsx index 534dc315b2a..3a3ae6b215a 100644 --- a/src/pages/PrimeUI/Shipment/PrimeUIShipmentCreateForm.jsx +++ b/src/pages/PrimeUI/Shipment/PrimeUIShipmentCreateForm.jsx @@ -1,10 +1,8 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState } from 'react'; import { Radio, FormGroup, Label, Textarea } from '@trussworks/react-uswds'; import { Field, useField, useFormikContext } from 'formik'; -import { isBooleanFlagEnabled } from '../../../utils/featureFlags'; - -import { SHIPMENT_OPTIONS, SHIPMENT_TYPES, FEATURE_FLAG_KEYS } from 'shared/constants'; +import { SHIPMENT_OPTIONS, SHIPMENT_TYPES } from 'shared/constants'; import { CheckboxField, DatePickerInput, DropdownInput } from 'components/form/fields'; import MaskedTextField from 'components/form/fields/MaskedTextField/MaskedTextField'; import styles from 'components/Office/CustomerContactInfoForm/CustomerContactInfoForm.module.scss'; @@ -17,7 +15,7 @@ import { LOCATION_TYPES } from 'types/sitStatusShape'; const sitLocationOptions = dropdownInputOptions(LOCATION_TYPES); -const PrimeUIShipmentCreateForm = () => { +const PrimeUIShipmentCreateForm = ({ enableBoat, enableMobileHome }) => { const { values, setFieldTouched, setFieldValue } = useFormikContext(); const { shipmentType } = values; const { sitExpected, hasProGear } = values.ppmShipment; @@ -25,8 +23,6 @@ const PrimeUIShipmentCreateForm = () => { const [, , checkBoxHelperProps] = useField('diversion'); const [, , divertedFromIdHelperProps] = useField('divertedFromShipmentId'); const [isChecked, setIsChecked] = useState(false); - const [enableBoat, setEnableBoat] = useState(false); - const [enableMobileHome, setEnableMobileHome] = useState(false); const hasShipmentType = !!shipmentType; const isPPM = shipmentType === SHIPMENT_OPTIONS.PPM; @@ -80,14 +76,6 @@ const PrimeUIShipmentCreateForm = () => { return undefined; }; - useEffect(() => { - const fetchData = async () => { - setEnableBoat(await isBooleanFlagEnabled(FEATURE_FLAG_KEYS.BOAT)); - setEnableMobileHome(await isBooleanFlagEnabled(FEATURE_FLAG_KEYS.MOBILE_HOME)); - }; - fetchData(); - }, []); - let shipmentTypeOptions = Object.values(SHIPMENT_TYPES).map((value) => ({ key: value, value })); if (!enableBoat) { // Disallow the Prime from choosing Boat shipments if the feature flag is not enabled @@ -463,7 +451,7 @@ const PrimeUIShipmentCreateForm = () => { name="hasSecondaryPickupAddress" value="false" title="No, there is not a second pickup address" - checked={hasSecondaryPickupAddress !== 'true'} + checked={hasSecondaryPickupAddress !== 'true' && hasTertiaryPickupAddress !== 'true'} />
@@ -565,7 +553,7 @@ const PrimeUIShipmentCreateForm = () => { name="hasSecondaryDestinationAddress" value="false" title="No, there is not a second delivery address" - checked={hasSecondaryDestinationAddress !== 'true'} + checked={hasSecondaryDestinationAddress !== 'true' && hasTertiaryDestinationAddress !== 'true'} />
diff --git a/src/shared/utils.js b/src/shared/utils.js index ac6baa1307b..12ccf91c7a8 100644 --- a/src/shared/utils.js +++ b/src/shared/utils.js @@ -171,3 +171,41 @@ export function isEmpty(obj) { export function isNullUndefinedOrWhitespace(value) { return value == null || value === undefined || value.trim() === ''; } + +export function checkAddressTogglesToClearAddresses(body) { + let values = body; + + if (values.shipmentType === 'PPM') { + if (values.ppmShipment.hasSecondaryPickupAddress !== 'true') { + values.ppmShipment.secondaryPickupAddress = {}; + values.ppmShipment.tertiaryPickupAddress = {}; + } + if (values.ppmShipment.hasTertiaryPickupAddress !== 'true') { + values.ppmShipment.tertiaryPickupAddress = {}; + } + if (values.ppmShipment.hasSecondaryDestinationAddress !== 'true') { + values.ppmShipment.secondaryDestinationAddress = {}; + values.ppmShipment.tertiaryDestinationAddress = {}; + } + if (values.ppmShipment.hasTertiaryDestinationAddress !== 'true') { + values.ppmShipment.tertiaryDestinationAddress = {}; + } + } else { + if (values.hasSecondaryPickupAddress !== 'true') { + values.secondaryPickupAddress = {}; + values.tertiaryPickupAddress = {}; + } + if (values.hasTertiaryPickupAddress !== 'true') { + values.tertiaryPickupAddress = {}; + } + if (values.hasSecondaryDestinationAddress !== 'true') { + values.secondaryDestinationAddress = {}; + values.tertiaryDestinationAddress = {}; + } + if (values.hasTertiaryDestinationAddress !== 'true') { + values.tertiaryDestinationAddress = {}; + } + } + + return values; +} diff --git a/src/shared/utils.test.js b/src/shared/utils.test.js index eb5ea3e0160..dfc7cfcd956 100644 --- a/src/shared/utils.test.js +++ b/src/shared/utils.test.js @@ -86,4 +86,59 @@ describe('utils', () => { }); }); }); + + it('check if 2nd and 3rd addresses should be cleared from prime shipment create payload', () => { + const ppmValues = { + shipmentType: 'PPM', + ppmShipment: { + hasSecondaryPickupAddress: 'false', + hasTertiaryPickupAddress: 'false', + hasSecondaryDestinationAddress: 'false', + hasTertiaryDestinationAddress: 'false', + secondaryPickupAddress: '', + tertiaryPickupAddress: '', + secondaryDestinationAddress: '', + tertiaryDestinationAddress: '', + }, + }; + const hhgValues = { + shipmentType: 'HHG', + hasSecondaryPickupAddress: 'false', + hasTertiaryPickupAddress: 'false', + hasSecondaryDestinationAddress: 'false', + hasTertiaryDestinationAddress: 'false', + secondaryPickupAddress: '', + tertiaryPickupAddress: '', + secondaryDestinationAddress: '', + tertiaryDestinationAddress: '', + }; + + const updatedPPMValues = utils.checkAddressTogglesToClearAddresses(ppmValues); + expect(updatedPPMValues).toEqual({ + shipmentType: 'PPM', + ppmShipment: { + hasSecondaryPickupAddress: 'false', + hasTertiaryPickupAddress: 'false', + hasSecondaryDestinationAddress: 'false', + hasTertiaryDestinationAddress: 'false', + secondaryPickupAddress: {}, + tertiaryPickupAddress: {}, + secondaryDestinationAddress: {}, + tertiaryDestinationAddress: {}, + }, + }); + + const updatedHHGValues = utils.checkAddressTogglesToClearAddresses(hhgValues); + expect(updatedHHGValues).toEqual({ + shipmentType: 'HHG', + hasSecondaryPickupAddress: 'false', + hasTertiaryPickupAddress: 'false', + hasSecondaryDestinationAddress: 'false', + hasTertiaryDestinationAddress: 'false', + secondaryPickupAddress: {}, + tertiaryPickupAddress: {}, + secondaryDestinationAddress: {}, + tertiaryDestinationAddress: {}, + }); + }); });