diff --git a/playwright/tests/office/txo/tioFlows.spec.js b/playwright/tests/office/txo/tioFlows.spec.js index 53d6bdd03df..ea4b8791bc5 100644 --- a/playwright/tests/office/txo/tioFlows.spec.js +++ b/playwright/tests/office/txo/tioFlows.spec.js @@ -19,13 +19,16 @@ class TioFlowPage extends OfficePage { /** * @param {OfficePage} officePage * @param {Object} move + * @param {Boolean} usePaymentRequest * @override */ - constructor(officePage, move) { + constructor(officePage, move, usePaymentRequest) { super(officePage.page, officePage.request); this.move = move; this.moveLocator = move.locator; - this.paymentRequest = this.findPaymentRequestBySequenceNumber(1); + if (usePaymentRequest !== false) { + this.paymentRequest = this.findPaymentRequestBySequenceNumber(1); + } } /** @@ -182,7 +185,7 @@ test.describe('TIO user', () => { testMove = await officePage.testHarness.buildHHGMoveWithServiceItemsandPaymentRequestsForTIO(); await officePage.signInAsNewTIOUser(); - tioFlowPage = new TioFlowPage(officePage, testMove); + tioFlowPage = new TioFlowPage(officePage, testMove, true); const searchTab = officePage.page.getByTitle(TIOTabsTitles[1]); await searchTab.click(); @@ -330,7 +333,7 @@ test.describe('TIO user', () => { const move = await officePage.testHarness.buildHHGMoveWithServiceItemsandPaymentRequestsForTIO(); await officePage.signInAsNewTIOUser(); - tioFlowPage = new TioFlowPage(officePage, move); + 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(); @@ -646,7 +649,7 @@ test.describe('TIO user', () => { const move = await officePage.testHarness.buildNTSRMoveWithPaymentRequest(); await officePage.signInAsNewTIOUser(); - tioFlowPage = new TioFlowPage(officePage, move); + 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(); @@ -778,7 +781,7 @@ test.describe('TIO user', () => { const move = await officePage.testHarness.buildNTSRMoveWithServiceItemsAndPaymentRequest(); await officePage.signInAsNewTIOUser(); - tioFlowPage = new TioFlowPage(officePage, move); + 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(); @@ -864,4 +867,45 @@ test.describe('TIO user', () => { await expect(page.getByRole('heading', { name: 'Payment requests' })).toBeVisible(); }); }); + + test.describe('with PPM moves with weight tickets and documents', () => { + test.beforeEach(async ({ officePage }) => { + testMove = await officePage.testHarness.buildApprovedMoveWithPPMMovingExpenseOffice(); + await officePage.signInAsNewTIOUser(); + tioFlowPage = new TioFlowPage(officePage, testMove, false); + const searchTab = officePage.page.getByTitle(TIOTabsTitles[1]); + await searchTab.click(); + }); + + test('can view PPM review documents', async ({ page }) => { + const locator = `PPM ${testMove.locator}`; + const selectedRadio = page.getByRole('group').locator(`label:text("${SearchRBSelection[0]}")`); + await selectedRadio.click(); + await page.getByTestId('searchText').fill(testMove.locator); + await page.getByTestId('searchTextSubmit').click(); + + await expect(page.getByText('Results')).toBeVisible(); + await expect(page.getByTestId('locator-0')).toContainText(testMove.locator); + await page.getByTestId('locator-0').click(); + await page.getByRole('button', { name: 'Review shipment weights' }).click(); + await page.getByRole('button', { name: 'Review shipment weights' }).click(); + await expect(page.getByText(locator)).toBeVisible(); + await expect(page.getByText('Shipment Info')).toBeVisible(); + + await expect(page.getByText('Planned Move Start Date')).toBeVisible(); + await expect(page.getByText('Actual Move Start Date')).toBeVisible(); + await expect(page.getByText('Starting Address')).toBeVisible(); + await expect(page.getByText('Ending Address')).toBeVisible(); + await expect(page.getByText('Miles')).toBeVisible(); + await expect(page.getByText('Estimated Net Weight')).toBeVisible(); + await expect(page.getByText('Actual Net Weight')).toBeVisible(); + await expect(page.getByText('Allowable Weight')).toBeVisible(); + await expect(page.getByText('SENT TO CUSTOMER')).toBeVisible(); + await expect(page.getByText('TRIP 1')).toBeVisible(); + await expect(page.getByText('RECEIPT 1')).toBeVisible(); + await expect(page.getByText('RECEIPT 2')).toBeVisible(); + + await page.getByRole('button', { name: 'Done' }).click(); + }); + }); }); diff --git a/src/components/Office/PPM/PPMHeaderSummary/PPMHeaderSummary.jsx b/src/components/Office/PPM/PPMHeaderSummary/PPMHeaderSummary.jsx index 67d1d6f931d..955389774cd 100644 --- a/src/components/Office/PPM/PPMHeaderSummary/PPMHeaderSummary.jsx +++ b/src/components/Office/PPM/PPMHeaderSummary/PPMHeaderSummary.jsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { number, bool } from 'prop-types'; +import { string, bool } from 'prop-types'; import classnames from 'classnames'; import { Tag } from '@trussworks/react-uswds'; @@ -64,7 +64,6 @@ const GCCAndIncentiveInfo = ({ ppmShipmentInfo, updatedItemName, setUpdatedItemN }; export default function PPMHeaderSummary({ ppmShipmentInfo, order, ppmNumber, showAllFields, readOnly }) { const [updatedItemName, setUpdatedItemName] = useState(''); - const shipmentInfo = { plannedMoveDate: ppmShipmentInfo.expectedDepartureDate, actualMoveDate: ppmShipmentInfo.actualMoveDate, @@ -119,7 +118,7 @@ export default function PPMHeaderSummary({ ppmShipmentInfo, order, ppmNumber, sh } PPMHeaderSummary.propTypes = { - ppmNumber: number.isRequired, + ppmNumber: string.isRequired, showAllFields: bool.isRequired, }; diff --git a/src/components/Office/PPM/PPMHeaderSummary/PPMHeaderSummary.test.jsx b/src/components/Office/PPM/PPMHeaderSummary/PPMHeaderSummary.test.jsx index 5ac996937c3..89c94406ccc 100644 --- a/src/components/Office/PPM/PPMHeaderSummary/PPMHeaderSummary.test.jsx +++ b/src/components/Office/PPM/PPMHeaderSummary/PPMHeaderSummary.test.jsx @@ -160,8 +160,9 @@ const defaultProps = { actualWeight: 3500, isActualExpenseReimbursement: true, }, - ppmNumber: 1, + ppmNumber: '1', showAllFields: false, + readOnly: false, }; describe('PPMHeaderSummary component', () => { diff --git a/src/components/Office/PPM/ReviewDocumentsSidePanel/ReviewDocumentsSidePanel.jsx b/src/components/Office/PPM/ReviewDocumentsSidePanel/ReviewDocumentsSidePanel.jsx index d14954781e0..dbf7583d636 100644 --- a/src/components/Office/PPM/ReviewDocumentsSidePanel/ReviewDocumentsSidePanel.jsx +++ b/src/components/Office/PPM/ReviewDocumentsSidePanel/ReviewDocumentsSidePanel.jsx @@ -4,7 +4,7 @@ import { Form } from '@trussworks/react-uswds'; import { Formik } from 'formik'; import classnames from 'classnames'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { arrayOf, func, number, object } from 'prop-types'; +import { arrayOf, bool, func, string, object } from 'prop-types'; import moment from 'moment'; import PPMHeaderSummary from '../PPMHeaderSummary/PPMHeaderSummary'; @@ -31,10 +31,12 @@ export default function ReviewDocumentsSidePanel({ proGearTickets, weightTickets, readOnly, + showAllFields, order, }) { let status; let showReason; + const showAllFieldsBool = showAllFields; const { mutate: patchDocumentsSetStatusMutation } = useMutation(patchPPMDocumentsSetStatus, { onSuccess, @@ -68,7 +70,7 @@ export default function ReviewDocumentsSidePanel({ ); showReason = true; - } else { + } else if (ticket.status === PPMDocumentsStatus.REJECTED) { status = (
@@ -76,6 +78,14 @@ export default function ReviewDocumentsSidePanel({
); showReason = true; + } else { + status = ( +
+ + Pending +
+ ); + showReason = true; } return status; }; @@ -107,11 +117,11 @@ export default function ReviewDocumentsSidePanel({ ppmShipmentInfo={ppmShipmentInfo} order={order} ppmNumber={ppmNumber} - showAllFields + showAllFields={showAllFieldsBool} readOnly={readOnly} /> -
+

{readOnly ? 'Sent to customer' : 'Send to customer?'}

@@ -264,23 +274,25 @@ export default function ReviewDocumentsSidePanel({ ReviewDocumentsSidePanel.propTypes = { ppmShipment: PPMShipmentShape, - ppmNumber: number, + ppmNumber: string, formRef: object, onSuccess: func, onError: func, expenseTickets: arrayOf(ExpenseShape), proGearTickets: arrayOf(ProGearTicketShape), weightTickets: arrayOf(WeightTicketShape), + showAllFields: bool, order: OrderShape.isRequired, }; ReviewDocumentsSidePanel.defaultProps = { ppmShipment: undefined, - ppmNumber: 1, + ppmNumber: '1', formRef: null, onSuccess: () => {}, onError: () => {}, expenseTickets: [], proGearTickets: [], weightTickets: [], + showAllFields: true, }; diff --git a/src/components/Office/PPM/ReviewDocumentsSidePanel/ReviewDocumentsSidePanel.module.scss b/src/components/Office/PPM/ReviewDocumentsSidePanel/ReviewDocumentsSidePanel.module.scss index 069561c21ef..748c5acb080 100644 --- a/src/components/Office/PPM/ReviewDocumentsSidePanel/ReviewDocumentsSidePanel.module.scss +++ b/src/components/Office/PPM/ReviewDocumentsSidePanel/ReviewDocumentsSidePanel.module.scss @@ -126,6 +126,12 @@ margin-left: 17px; margin-right: 9px; } + + [data-icon='rotate-right'] { + color: $warning; + margin-left: 17px; + margin-right: 9px; + } } .container .ReviewDocumentsSidePanel main { diff --git a/src/components/Office/PPM/ReviewExpense/ReviewExpense.jsx b/src/components/Office/PPM/ReviewExpense/ReviewExpense.jsx index 364ddd3f0fa..8b290e33d5d 100644 --- a/src/components/Office/PPM/ReviewExpense/ReviewExpense.jsx +++ b/src/components/Office/PPM/ReviewExpense/ReviewExpense.jsx @@ -1,6 +1,6 @@ import React, { useEffect, useCallback } from 'react'; import { useMutation } from '@tanstack/react-query'; -import { func, number, object } from 'prop-types'; +import { func, number, string, object } from 'prop-types'; import { Formik, Field } from 'formik'; import classnames from 'classnames'; import { FormGroup, Label, Radio, Textarea } from '@trussworks/react-uswds'; @@ -560,7 +560,7 @@ export default function ReviewExpense({ ReviewExpense.propTypes = { expense: ExpenseShape, tripNumber: number.isRequired, - ppmNumber: number.isRequired, + ppmNumber: string.isRequired, onSuccess: func, formRef: object, order: OrderShape.isRequired, diff --git a/src/components/Office/PPM/ReviewExpense/ReviewExpense.stories.jsx b/src/components/Office/PPM/ReviewExpense/ReviewExpense.stories.jsx index 7db33106fbb..fe4a954eb98 100644 --- a/src/components/Office/PPM/ReviewExpense/ReviewExpense.stories.jsx +++ b/src/components/Office/PPM/ReviewExpense/ReviewExpense.stories.jsx @@ -146,7 +146,7 @@ Blank.args = { documentSets: documentSetsProps, documentSetIndex, tripNumber: 1, - ppmNumber: 1, + ppmNumber: '1', }; export const NonStorage = Template.bind({}); @@ -155,7 +155,7 @@ NonStorage.args = { documentSets: documentSetsProps, documentSetIndex, tripNumber: 1, - ppmNumber: 1, + ppmNumber: '1', categoryIndex: 1, expense: { movingExpenseType: expenseTypes.PACKING_MATERIALS, @@ -170,7 +170,7 @@ Storage.args = { documentSetIndex, ppmShipmentInfo: PPMShipmentInfo, tripNumber: 1, - ppmNumber: 1, + ppmNumber: '1', categoryIndex: 1, expense: { movingExpenseType: expenseTypes.STORAGE, diff --git a/src/components/Office/PPM/ReviewProGear/ReviewProGear.jsx b/src/components/Office/PPM/ReviewProGear/ReviewProGear.jsx index 0169683797a..d41e6e22e07 100644 --- a/src/components/Office/PPM/ReviewProGear/ReviewProGear.jsx +++ b/src/components/Office/PPM/ReviewProGear/ReviewProGear.jsx @@ -1,6 +1,6 @@ import React, { useState, useEffect } from 'react'; import { useMutation } from '@tanstack/react-query'; -import { func, number, object } from 'prop-types'; +import { func, number, string, object } from 'prop-types'; import { Field, Formik } from 'formik'; import classnames from 'classnames'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; @@ -295,7 +295,7 @@ export default function ReviewProGear({ ReviewProGear.propTypes = { proGear: ProGearTicketShape, tripNumber: number.isRequired, - ppmNumber: number.isRequired, + ppmNumber: string.isRequired, onSuccess: func, formRef: object, order: OrderShape.isRequired, diff --git a/src/components/Office/PPM/ReviewProGear/ReviewProGear.stories.jsx b/src/components/Office/PPM/ReviewProGear/ReviewProGear.stories.jsx index c375eb92f04..76428dc5a2b 100644 --- a/src/components/Office/PPM/ReviewProGear/ReviewProGear.stories.jsx +++ b/src/components/Office/PPM/ReviewProGear/ReviewProGear.stories.jsx @@ -33,14 +33,14 @@ export const Blank = Template.bind({}); Blank.args = { ppmShipmentInfo: PPMShipmentInfo, tripNumber: 1, - ppmNumber: 1, + ppmNumber: '1', }; export const FilledIn = Template.bind({}); FilledIn.args = { ppmShipmentInfo: PPMShipmentInfo, tripNumber: 1, - ppmNumber: 1, + ppmNumber: '1', proGear: { belongsToSelf: true, proGearDocument: [], diff --git a/src/components/Office/PPM/ReviewProGear/ReviewProGear.test.jsx b/src/components/Office/PPM/ReviewProGear/ReviewProGear.test.jsx index 5a147bdfa8c..88192433bb4 100644 --- a/src/components/Office/PPM/ReviewProGear/ReviewProGear.test.jsx +++ b/src/components/Office/PPM/ReviewProGear/ReviewProGear.test.jsx @@ -22,7 +22,7 @@ const defaultProps = { actualWeight: 3500, }, tripNumber: 1, - ppmNumber: 1, + ppmNumber: '1', showAllFields: false, }; diff --git a/src/components/Office/PPM/ReviewWeightTicket/ReviewWeightTicket.jsx b/src/components/Office/PPM/ReviewWeightTicket/ReviewWeightTicket.jsx index ada4308ea4a..10f2ddfc51b 100644 --- a/src/components/Office/PPM/ReviewWeightTicket/ReviewWeightTicket.jsx +++ b/src/components/Office/PPM/ReviewWeightTicket/ReviewWeightTicket.jsx @@ -1,6 +1,6 @@ import React, { useEffect, useState, useRef } from 'react'; import { useMutation } from '@tanstack/react-query'; -import { func, number, object, PropTypes } from 'prop-types'; +import { func, number, string, object, PropTypes } from 'prop-types'; import { Field, Formik } from 'formik'; import classnames from 'classnames'; import { Alert, FormGroup, Label, Radio, Textarea } from '@trussworks/react-uswds'; @@ -434,7 +434,7 @@ ReviewWeightTicket.propTypes = { weightTicket: WeightTicketShape, mtoShipment: ShipmentShape, tripNumber: number.isRequired, - ppmNumber: number.isRequired, + ppmNumber: string.isRequired, onSuccess: func, formRef: object, currentMtoShipments: PropTypes.arrayOf(ShipmentShape), diff --git a/src/components/Office/PPM/ReviewWeightTicket/ReviewWeightTicket.stories.jsx b/src/components/Office/PPM/ReviewWeightTicket/ReviewWeightTicket.stories.jsx index 75399b9a977..6f714a6920c 100644 --- a/src/components/Office/PPM/ReviewWeightTicket/ReviewWeightTicket.stories.jsx +++ b/src/components/Office/PPM/ReviewWeightTicket/ReviewWeightTicket.stories.jsx @@ -33,7 +33,7 @@ export const Blank = Template.bind({}); Blank.args = { ppmShipmentInfo: PPMShipmentInfo, tripNumber: 1, - ppmNumber: 1, + ppmNumber: '1', weightTicket: { vehicleDescription: 'Kia Forte', emptyWeight: 600, @@ -65,7 +65,7 @@ export const FilledIn = Template.bind({}); FilledIn.args = { ppmShipmentInfo: PPMShipmentInfo, tripNumber: 1, - ppmNumber: 1, + ppmNumber: '1', weightTicket: { vehicleDescription: 'Kia Forte', emptyWeight: 600, @@ -97,7 +97,7 @@ export const MissingWeightTickets = Template.bind({}); MissingWeightTickets.args = { ppmShipmentInfo: PPMShipmentInfo, tripNumber: 1, - ppmNumber: 1, + ppmNumber: '1', weightTicket: { vehicleDescription: 'Kia Forte', emptyWeight: 6000, diff --git a/src/components/Office/PPM/ReviewWeightTicket/ReviewWeightTicket.test.jsx b/src/components/Office/PPM/ReviewWeightTicket/ReviewWeightTicket.test.jsx index c061d5bd92b..67654fa3792 100644 --- a/src/components/Office/PPM/ReviewWeightTicket/ReviewWeightTicket.test.jsx +++ b/src/components/Office/PPM/ReviewWeightTicket/ReviewWeightTicket.test.jsx @@ -33,7 +33,7 @@ const defaultProps = { }, tripNumber: 1, showAllFields: false, - ppmNumber: 1, + ppmNumber: '1', setCurrentMtoShipments: mockCallback, }; diff --git a/src/components/Office/WeightSummary/WeightSummary.jsx b/src/components/Office/WeightSummary/WeightSummary.jsx index 6637c6567e2..7ecdab8b97b 100644 --- a/src/components/Office/WeightSummary/WeightSummary.jsx +++ b/src/components/Office/WeightSummary/WeightSummary.jsx @@ -26,6 +26,7 @@ const WeightSummary = ({ maxBillableWeight, weightRequested, weightAllowance, to if (shipment.shipmentType === SHIPMENT_OPTIONS.HHG && countHHG <= 1) return 'HHG'; if (shipment.shipmentType === SHIPMENT_OPTIONS.NTS) return 'NTS'; if (shipment.shipmentType === SHIPMENT_OPTIONS.NTSR) return 'NTSR'; + if (shipment.shipmentType === SHIPMENT_OPTIONS.PPM) return 'PPM'; return ''; }; @@ -87,7 +88,7 @@ WeightSummary.propTypes = { totalBillableWeight: number.isRequired, shipments: arrayOf( shape({ - calculatedBillableWeight: number.isRequired, + calculatedBillableWeight: number, primeEstimatedWeight: number, }), ).isRequired, diff --git a/src/hooks/queries.js b/src/hooks/queries.js index 6167cf459fd..e46b708e669 100644 --- a/src/hooks/queries.js +++ b/src/hooks/queries.js @@ -81,7 +81,7 @@ import { PAGINATION_PAGE_DEFAULT, PAGINATION_PAGE_SIZE_DEFAULT } from 'constants * @param {string} moveCode The move locator * @return {QueriesResults} ppmDocsQueriesResults: an array of the documents queries for each PPM shipment in the mtoShipments array. */ -const useAddWeightTicketsToPPMShipments = (mtoShipments, moveCode) => { +const useAddExpensesToPPMShipments = (mtoShipments, moveCode) => { // Filter for ppm shipments to get their documents(including weight tickets) const shipmentIDs = mtoShipments?.filter((shipment) => shipment.ppmShipment).map((shipment) => shipment.id) ?? []; @@ -93,8 +93,10 @@ const useAddWeightTicketsToPPMShipments = (mtoShipments, moveCode) => { queryFn: ({ queryKey }) => getPPMDocuments(...queryKey), enabled: !!shipmentID, select: (data) => { - // Shove the weight tickets into the corresponding ppmShipment object + // Shove the weight tickets and other expenses into the corresponding ppmShipment object const shipment = mtoShipments.find((s) => s.id === shipmentID); + shipment.ppmShipment.movingExpenses = data.MovingExpenses; + shipment.ppmShipment.proGearWeightTickets = data.ProGearWeightTickets; shipment.ppmShipment.weightTickets = data.WeightTickets; // Attach the review url to each ppm shipment shipment.ppmShipment.reviewShipmentWeightsURL = generatePath( @@ -364,7 +366,7 @@ export const useReviewShipmentWeightsQuery = (moveCode) => { }); // attach ppm documents to their respective ppm shipments - const ppmDocsQueriesResults = useAddWeightTicketsToPPMShipments(mtoShipments, moveCode); + const ppmDocsQueriesResults = useAddExpensesToPPMShipments(mtoShipments, moveCode); const { isLoading, isError, isSuccess } = getQueriesStatus([ moveQuery, @@ -415,7 +417,7 @@ export const useMoveTaskOrderQueries = (moveCode) => { ); // attach ppm documents to their respective ppm shipments - const ppmDocsQueriesResults = useAddWeightTicketsToPPMShipments(mtoShipments, moveCode); + const ppmDocsQueriesResults = useAddExpensesToPPMShipments(mtoShipments, moveCode); const { isLoading, isError, isSuccess } = getQueriesStatus([ moveQuery, @@ -662,8 +664,8 @@ export const useMovePaymentRequestsQueries = (moveCode) => { }, ); - // attach ppm documents to their respective ppm shipments - const ppmDocsQueriesResults = useAddWeightTicketsToPPMShipments(mtoShipments, moveCode); + // attach all ppm documents to their respective ppm shipments + const ppmDocsQueriesResults = useAddExpensesToPPMShipments(mtoShipments, moveCode); const orderId = move?.ordersId; const { data: { orders } = {}, ...orderQuery } = useQuery( @@ -857,7 +859,7 @@ export const useMoveDetailsQueries = (moveCode) => { }); // attach ppm documents to their respective ppm shipments - const ppmDocsQueriesResults = useAddWeightTicketsToPPMShipments(mtoShipments, moveCode); + const ppmDocsQueriesResults = useAddExpensesToPPMShipments(mtoShipments, moveCode); const customerId = order?.customerID; const { data: { customer } = {}, ...customerQuery } = useQuery({ diff --git a/src/hooks/queries.test.jsx b/src/hooks/queries.test.jsx index 8564d749703..38bf1cbd08b 100644 --- a/src/hooks/queries.test.jsx +++ b/src/hooks/queries.test.jsx @@ -561,6 +561,8 @@ describe('useMoveDetailsQueries', () => { shipmentType: 'PPM', ppmShipment: { id: 'p1', + movingExpenses: [], + proGearWeightTickets: [], shipmentId: 'c3', estimatedWeight: 100, weightTickets: [ @@ -677,6 +679,8 @@ describe('useMoveTaskOrderQueries', () => { shipmentType: 'PPM', ppmShipment: { id: 'p1', + movingExpenses: [], + proGearWeightTickets: [], shipmentId: 'c3', estimatedWeight: 100, weightTickets: [ @@ -1076,6 +1080,8 @@ describe('useReviewShipmentWeightsQuery', () => { shipmentType: 'PPM', ppmShipment: { id: 'p1', + movingExpenses: [], + proGearWeightTickets: [], shipmentId: 'c3', estimatedWeight: 100, weightTickets: [ diff --git a/src/pages/Office/DocumentViewerSidebar/DocumentViewerSidebar.module.scss b/src/pages/Office/DocumentViewerSidebar/DocumentViewerSidebar.module.scss index ba53df410de..acc0f978f53 100644 --- a/src/pages/Office/DocumentViewerSidebar/DocumentViewerSidebar.module.scss +++ b/src/pages/Office/DocumentViewerSidebar/DocumentViewerSidebar.module.scss @@ -88,7 +88,7 @@ .container:not(.defaultH3) h3 { font-size: 15px; - color: $base; + color: #3d4551; font-weight: normal; text-transform: uppercase; @include u-margin-top(0.5); diff --git a/src/pages/Office/PPM/ReviewDocuments/ReviewDocuments.jsx b/src/pages/Office/PPM/ReviewDocuments/ReviewDocuments.jsx index c365ab588ee..a348d4f4b56 100644 --- a/src/pages/Office/PPM/ReviewDocuments/ReviewDocuments.jsx +++ b/src/pages/Office/PPM/ReviewDocuments/ReviewDocuments.jsx @@ -292,7 +292,7 @@ export const ReviewDocuments = ({ readOnly }) => { key={documentSetIndex} weightTicket={currentDocumentSet.documentSet} ppmShipmentInfo={ppmShipmentInfo} - ppmNumber={1} + ppmNumber="1" tripNumber={currentTripNumber} mtoShipment={mtoShipment} order={order} @@ -311,7 +311,7 @@ export const ReviewDocuments = ({ readOnly }) => { { documentSets={documentSets} documentSetIndex={documentSetIndex} categoryIndex={currentDocumentCategoryIndex} - ppmNumber={1} + ppmNumber="1" tripNumber={currentTripNumber} mtoShipment={mtoShipment} onError={onErrorMessage} diff --git a/src/pages/Office/ReviewBillableWeight/ReviewBillableWeight.jsx b/src/pages/Office/ReviewBillableWeight/ReviewBillableWeight.jsx index 2d88c616cad..3d39a5d2c48 100644 --- a/src/pages/Office/ReviewBillableWeight/ReviewBillableWeight.jsx +++ b/src/pages/Office/ReviewBillableWeight/ReviewBillableWeight.jsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import { Button, Alert } from '@trussworks/react-uswds'; import { useNavigate, useParams } from 'react-router-dom'; import { useQueryClient, useMutation } from '@tanstack/react-query'; @@ -7,6 +7,7 @@ import DocumentViewerSidebar from '../DocumentViewerSidebar/DocumentViewerSideba import reviewBillableWeightStyles from './ReviewBillableWeight.module.scss'; +import ReviewDocumentsSidePanel from 'components/Office/PPM/ReviewDocumentsSidePanel/ReviewDocumentsSidePanel'; import ShipmentModificationTag from 'components/ShipmentModificationTag/ShipmentModificationTag'; import { WEIGHT_ADJUSTMENT, shipmentModificationTypes } from 'constants/shipments'; import { MOVES, MTO_SHIPMENTS, ORDERS } from 'constants/queryKeys'; @@ -32,7 +33,9 @@ import { SHIPMENT_OPTIONS } from 'shared/constants'; export default function ReviewBillableWeight() { const [selectedShipmentIndex, setSelectedShipmentIndex] = React.useState(0); + const [selectedShipment, setSelectedShipment] = React.useState({}); const [sidebarType, setSidebarType] = React.useState('MAX'); + const [ppmShipmentInfo, setPpmShipmentInfo] = React.useState({}); const { moveCode } = useParams(); const handleClickNextButton = () => { @@ -67,12 +70,47 @@ export default function ReviewBillableWeight() { return uploads; }; - // filter out PPMs, as they're not including in TIO review - const excludePPMShipments = mtoShipments?.filter((shipment) => shipment.shipmentType !== 'PPM'); + const getAllPPMShipmentFiles = (ppmShipment) => { + const weightTicketDocs = []; + if (ppmShipment !== undefined) { + if (ppmShipment.weightTickets !== undefined) { + weightTicketDocs.push( + ppmShipment.weightTickets.map((weightTicket) => { + return weightTicket.emptyDocument ?? []; + }), + ppmShipment.weightTickets.map((weightTicket) => { + return weightTicket.fullDocument ?? []; + }), + ); + } + if (ppmShipment.proGearWeightTickets !== undefined) { + weightTicketDocs.push( + ppmShipment.proGearWeightTickets.map((proGearWeightTicket) => { + return proGearWeightTicket.document ?? []; + }), + ); + } + if (ppmShipment.movingExpenses !== undefined) { + weightTicketDocs.push( + ppmShipment.movingExpenses.map((movingExpense) => { + return movingExpense.document ?? []; + }), + ); + } + } + const uploadedWeightDocs = weightTicketDocs.flat(); + const uploadedWeightFiles = uploadedWeightDocs.flatMap((doc) => { + return doc.uploads; + }); + + let uploads = []; + uploads = uploads.concat(uploadedWeightFiles); + return uploads; + }; + /* Only show shipments in statuses of approved, diversion requested, or cancellation requested */ - const filteredShipments = excludePPMShipments?.filter((shipment) => - includedStatusesForCalculatingWeights(shipment.status), - ); + const filteredShipments = mtoShipments?.filter((shipment) => includedStatusesForCalculatingWeights(shipment.status)); + const readOnly = true; const isLastShipment = filteredShipments && selectedShipmentIndex === filteredShipments.length - 1; const totalBillableWeight = useCalculatedTotalBillableWeight(filteredShipments, WEIGHT_ADJUSTMENT); @@ -94,8 +132,26 @@ export default function ReviewBillableWeight() { }); }; - const selectedShipment = - filteredShipments && filteredShipments.length > 0 ? filteredShipments[selectedShipmentIndex] : {}; + useEffect(() => { + setSelectedShipment( + filteredShipments && filteredShipments.length > 0 ? filteredShipments[selectedShipmentIndex] : {}, + ); + }, [filteredShipments, selectedShipmentIndex]); + + useEffect(() => { + if (!isLoading && selectedShipment.shipmentType === 'PPM') { + let currentTotalWeight = 0; + selectedShipment.ppmShipment.weightTickets.forEach((weight) => { + currentTotalWeight += weight.fullWeight - weight.emptyWeight; + }); + const updatedPpmShipmentInfo = { + ...selectedShipment.ppmShipment, + miles: selectedShipment.distance, + actualWeight: currentTotalWeight, + }; + setPpmShipmentInfo(updatedPpmShipmentInfo); + } + }, [isLoading, selectedShipment]); const queryClient = useQueryClient(); const { mutate: mutateMTOShipment } = useMutation(updateMTOShipment, { @@ -203,12 +259,12 @@ export default function ReviewBillableWeight() { if (isLoading) return ; if (isError) return ; - const fileList = getAllFiles(); - + const fileList = + selectedShipment.shipmentType !== 'PPM' ? getAllFiles() : getAllPPMShipmentFiles(selectedShipment.ppmShipment); return (
- {fileList.length > 0 ? :

No documents provided

} +
{sidebarType === 'MAX' ? ( @@ -243,14 +299,16 @@ export default function ReviewBillableWeight() { shipments={filteredShipments} />
- + {selectedShipment.shipmentType !== 'PPM' && ( + + )}
-
- + +
+ ) : ( + - + )}
diff --git a/src/pages/Office/ReviewBillableWeight/ReviewBillableWeight.test.jsx b/src/pages/Office/ReviewBillableWeight/ReviewBillableWeight.test.jsx index 14fc3ac2259..21bfd2ba795 100644 --- a/src/pages/Office/ReviewBillableWeight/ReviewBillableWeight.test.jsx +++ b/src/pages/Office/ReviewBillableWeight/ReviewBillableWeight.test.jsx @@ -6,9 +6,11 @@ import ReviewBillableWeight from './ReviewBillableWeight'; import { formatWeight, formatDateFromIso } from 'utils/formatters'; import { useMovePaymentRequestsQueries } from 'hooks/queries'; -import { shipmentStatuses } from 'constants/shipments'; +import { shipmentStatuses, ppmShipmentStatuses } from 'constants/shipments'; import { tioRoutes } from 'constants/routes'; import { MockProviders, ReactQueryWrapper } from 'testUtils'; +import { createPPMShipmentWithFinalIncentive } from 'utils/test/factories/ppmShipment'; +import createUpload from 'utils/test/factories/upload'; // Mock the document viewer since we're not really testing that aspect here. // Document Viewer tests should be covered in the component itself. @@ -244,6 +246,44 @@ const mockDivertedMtoShipments = [ }, ]; +const mtoShipment = createPPMShipmentWithFinalIncentive({ + ppmShipment: { status: ppmShipmentStatuses.NEEDS_CLOSEOUT }, + id: 'shipment123', +}); + +const weightTicketEmptyDocumentCreatedDate = new Date(); +// The factory used above doesn't handle overrides for uploads correctly, so we need to do it manually. +const weightTicketEmptyDocumentUpload = createUpload({ + fileName: 'emptyWeightTicket.pdf', + createdAtDate: weightTicketEmptyDocumentCreatedDate, +}); + +const weightTicketFullDocumentCreatedDate = new Date(weightTicketEmptyDocumentCreatedDate); +weightTicketFullDocumentCreatedDate.setDate(weightTicketFullDocumentCreatedDate.getDate() + 1); +const weightTicketFullDocumentUpload = createUpload( + { fileName: 'fullWeightTicket.xls', createdAtDate: weightTicketFullDocumentCreatedDate }, + { contentType: 'application/vnd.ms-excel' }, +); + +const progearWeightTicketDocumentCreatedDate = new Date(weightTicketFullDocumentCreatedDate); +progearWeightTicketDocumentCreatedDate.setDate(progearWeightTicketDocumentCreatedDate.getDate() + 1); +const progearWeightTicketDocumentUpload = createUpload({ + fileName: 'progearWeightTicket.pdf', + createdAtDate: progearWeightTicketDocumentCreatedDate, +}); + +const movingExpenseDocumentCreatedDate = new Date(progearWeightTicketDocumentCreatedDate); +movingExpenseDocumentCreatedDate.setDate(movingExpenseDocumentCreatedDate.getDate() + 1); +const movingExpenseDocumentUpload = createUpload( + { fileName: 'movingExpense.jpg', createdAtDate: movingExpenseDocumentCreatedDate }, + { contentType: 'image/jpeg' }, +); + +mtoShipment.ppmShipment.weightTickets[0].emptyDocument.uploads = [weightTicketEmptyDocumentUpload]; +mtoShipment.ppmShipment.weightTickets[0].fullDocument.uploads = [weightTicketFullDocumentUpload]; +mtoShipment.ppmShipment.proGearWeightTickets[0].document.uploads = [progearWeightTicketDocumentUpload]; +mtoShipment.ppmShipment.movingExpenses[0].document.uploads = [movingExpenseDocumentUpload]; + const mockPaymentRequest = [ { proofOfServiceDocs: [ @@ -314,6 +354,15 @@ const useDivertedMovePaymentRequestsReturnValue = { paymentRequests: mockPaymentRequest, }; +const useMovePaymentRequestQueriesReturnValueAllDocs = { + paymentRequests: [], + order: mockOrders['1'], + mtoShipments: [mtoShipment], + move, + isLoading: false, + isError: false, +}; + const loadingReturnValue = { isLoading: true, isError: false, @@ -720,4 +769,25 @@ describe('ReviewBillableWeight', () => { expect(screen.queryByTestId('tag', { name: 'DIVERSION' })).not.toBeInTheDocument(); }); }); + + describe('handles PPM shipments', () => { + it('displays PPM information', async () => { + useMovePaymentRequestsQueries.mockReturnValue(useMovePaymentRequestQueriesReturnValueAllDocs); + + renderWithProviders(); + + expect(screen.getByText('Review weights')).toBeInTheDocument(); + expect(screen.getByText('Max billable weight')).toBeInTheDocument(); + expect(screen.getByText('Actual weight')).toBeInTheDocument(); + expect(screen.getByTestId('maxBillableWeight').textContent).toBe( + formatWeight(useMovePaymentRequestQueriesReturnValueAllDocs.order.entitlement.authorizedWeight), + ); + expect(screen.getByTestId('weightAllowance').textContent).toBe( + formatWeight(useMovePaymentRequestQueriesReturnValueAllDocs.order.entitlement.totalWeight), + ); + + expect(screen.getByText('Weight allowance')).toBeInTheDocument(); + expect(screen.getByText('Actual billable weight')).toBeInTheDocument(); + }); + }); });