diff --git a/packages/template-retail-react-app/CHANGELOG.md b/packages/template-retail-react-app/CHANGELOG.md index 8d3da4cd77..846880969f 100644 --- a/packages/template-retail-react-app/CHANGELOG.md +++ b/packages/template-retail-react-app/CHANGELOG.md @@ -5,6 +5,7 @@ - Integrate Order Details page to display orders data from OMS [#3573](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/3573) - Integrate Order History page to display data from OMS [#3581](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/3581) - Add shipping display support for OMS [#3588](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/3588) + - BOPIS multishipment with OMS [#3613] (https://github.com/SalesforceCommerceCloud/pwa-kit/pull/3613) - [Feature] Update passwordless login and password reset to use email mode by default. The mode can now be configured across the login page, auth modal, and checkout page [#3525](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/3525) - Update "Continue Securely" button text to "Continue" for passwordless login [#3556](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/3556) diff --git a/packages/template-retail-react-app/app/pages/account/order-detail.jsx b/packages/template-retail-react-app/app/pages/account/order-detail.jsx index 27733626f1..b5987f0cfc 100644 --- a/packages/template-retail-react-app/app/pages/account/order-detail.jsx +++ b/packages/template-retail-react-app/app/pages/account/order-detail.jsx @@ -134,6 +134,15 @@ const AccountOrderDetail = () => { ) const isLoading = isOrderLoading || !order + // Check if order has OMS data + const isOmsOrder = useMemo(() => !!order?.omsData, [order?.omsData]) + + // Check if order is multi-shipment order + const isMultiShipmentOrder = useMemo( + () => (order?.omsData?.shipments?.length ?? 0) > 1 || (order?.shipments?.length ?? 0) > 1, + [isOmsOrder, order?.omsData?.shipments?.length, order?.shipments?.length] + ) + const {pickupShipments, deliveryShipments} = useMemo(() => { return storeLocatorEnabled ? groupShipmentsByDeliveryOption(order) @@ -164,6 +173,67 @@ const AccountOrderDetail = () => { [storeData?.data] ) + const renderShippingMethod = ( + shippingMethodName, + shippingStatus, + trackingNumber, + trackingUrl, + shipmentsLength, + index + ) => ( + + + {shipmentsLength > 1 ? ( + + ) : ( + + )} + + + + {{ + not_shipped: formatMessage({ + defaultMessage: 'Not shipped', + id: 'account_order_detail.shipping_status.not_shipped' + }), + part_shipped: formatMessage({ + defaultMessage: 'Partially shipped', + id: 'account_order_detail.shipping_status.part_shipped' + }), + shipped: formatMessage({ + defaultMessage: 'Shipped', + id: 'account_order_detail.shipping_status.shipped' + }) + }[shippingStatus] || shippingStatus} + + {shippingMethodName} + {trackingNumber && ( + + + :{' '} + {trackingUrl ? ( + + {trackingNumber} + + ) : ( + trackingNumber + )} + + )} + + + ) + const paymentCard = order?.paymentInstruments?.[0]?.paymentCard const CardIcon = getCreditCardIcon(paymentCard?.cardType) const itemCount = order?.productItems?.reduce((count, item) => item.quantity + count, 0) || 0 @@ -325,86 +395,31 @@ const AccountOrderDetail = () => { ) })} + {/* Any type of Non-OMS or any type of single shipment order: show DeliveryMethods and Shipments info*/} + {(!isOmsOrder || !isMultiShipmentOrder) && + deliveryShipments.map((shipment, index) => { + const omsShipment = isOmsOrder + ? order.omsData.shipments?.[index] + : null - {/* Delivery Shipments */} - {deliveryShipments.map((shipment, index) => { - // Hide shipping address for OMS multi-shipment orders - // Can't reliably correlate OMS shipments to ECOM addresses by index - const isOmsOrder = !!order?.omsData - const omsShipment = isOmsOrder - ? order.omsData.shipments?.[index] - : null - const isOmsMultiShipment = - isOmsOrder && - (order.omsData.shipments?.length > 1 || - order.shipments?.length > 1) - - const shippingMethodName = - omsShipment?.provider || shipment.shippingMethod.name - const shippingStatus = - omsShipment?.status || shipment.shippingStatus - const trackingNumber = - omsShipment?.trackingNumber || shipment.trackingNumber - const trackingUrl = omsShipment?.trackingUrl + const shippingMethodName = + omsShipment?.provider || shipment.shippingMethod.name + const shippingStatus = + omsShipment?.status || shipment.shippingStatus + const trackingNumber = + omsShipment?.trackingNumber || shipment.trackingNumber + const trackingUrl = omsShipment?.trackingUrl - return ( - - - - {deliveryShipments.length > 1 ? ( - - ) : ( - - )} - - - - {{ - not_shipped: formatMessage({ - defaultMessage: 'Not shipped', - id: 'account_order_detail.shipping_status.not_shipped' - }), - part_shipped: formatMessage({ - defaultMessage: 'Partially shipped', - id: 'account_order_detail.shipping_status.part_shipped' - }), - shipped: formatMessage({ - defaultMessage: 'Shipped', - id: 'account_order_detail.shipping_status.shipped' - }) - }[shippingStatus] || shippingStatus} - - {shippingMethodName} - {trackingNumber && ( - - - :{' '} - {trackingUrl ? ( - - {trackingNumber} - - ) : ( - trackingNumber - )} - - )} - - - {!isOmsMultiShipment && ( + return ( + + {renderShippingMethod( + shippingMethodName, + shippingStatus, + trackingNumber, + trackingUrl, + deliveryShipments.length, + index + )} {deliveryShipments.length > 1 ? ( @@ -437,10 +452,25 @@ const AccountOrderDetail = () => { + + ) + })} + + {/* Any OMS multi-shipment: Only show OMS Shipments info;*/} + {isOmsOrder && + isMultiShipmentOrder && + order?.omsData?.shipments?.map((shipment, index) => ( + + {renderShippingMethod( + shipment.provider, + shipment.status, + shipment.trackingNumber, + shipment.trackingUrl, + order?.omsData?.shipments?.length ?? 0, + index )} - ) - })} + ))} {/* Payment Method */} {paymentCard && ( diff --git a/packages/template-retail-react-app/app/pages/account/orders.test.js b/packages/template-retail-react-app/app/pages/account/orders.test.js index 09d586b66a..051c16b0e2 100644 --- a/packages/template-retail-react-app/app/pages/account/orders.test.js +++ b/packages/template-retail-react-app/app/pages/account/orders.test.js @@ -795,7 +795,7 @@ describe('OMS Single shipment with partial data (missing provider, trackingUrl)' }) }) -describe('BOPIS Order with OMS - Pickup and Delivery', () => { +describe('BOPIS Order with OMS Single Pickup and Single Delivery', () => { // Helper to create BOPIS order with OMS data const createBopisOmsOrder = (overrides = {}) => ({ orderNo: 'BOPIS-OMS-001', @@ -825,7 +825,6 @@ describe('BOPIS Order with OMS - Pickup and Delivery', () => { shippingMethod: { name: 'Ground' }, - trackingNumber: 'ECOM-TRACK-OLD', shippingAddress: { fullName: 'Sarah Johnson', address1: '456 Delivery St', @@ -887,18 +886,8 @@ describe('BOPIS Order with OMS - Pickup and Delivery', () => { expect(await screen.findByText('Shipping Method')).toBeInTheDocument() expect(await screen.findByText(/FedEx/i)).toBeInTheDocument() expect(screen.queryByText(/Ground/i)).not.toBeInTheDocument() - }) - - test('should display OMS tracking number as clickable link for delivery shipment', async () => { - setupOrderDetailsPage(createBopisOmsOrder()) - expect(await screen.findByTestId('account-order-details-page')).toBeInTheDocument() const trackingLink = await screen.findByRole('link', {name: /BOPIS-TRACK-123/i}) expect(trackingLink).toHaveAttribute('href', 'https://tracking.fedex.com/BOPIS-TRACK-123') - }) - - test('should display OMS shipment status for delivery shipment', async () => { - setupOrderDetailsPage(createBopisOmsOrder()) - expect(await screen.findByTestId('account-order-details-page')).toBeInTheDocument() expect(await screen.findByText(/SHIPPED/i)).toBeInTheDocument() }) @@ -1131,10 +1120,11 @@ describe('BOPIS Order with OMS - Multiple Pickup Locations', () => { expect(await screen.findByText(/Partially Ready/i)).toBeInTheDocument() }) - test('should display OMS provider and tracking for delivery shipment', async () => { + test('should display OMS shipment 1 with UPS provider and tracking', async () => { setupOrderDetailsPage(createMultiPickupBopisOmsOrder()) expect(await screen.findByTestId('account-order-details-page')).toBeInTheDocument() expect(await screen.findByText(/UPS/i)).toBeInTheDocument() + expect(await screen.findByText(/SHIPPED/i)).toBeInTheDocument() const trackingLink = await screen.findByRole('link', {name: /MULTI-TRACK-456/i}) expect(trackingLink).toHaveAttribute( 'href', @@ -1142,6 +1132,15 @@ describe('BOPIS Order with OMS - Multiple Pickup Locations', () => { ) }) + test('should display OMS shipment 2 with FedEx provider and tracking', async () => { + setupOrderDetailsPage(createMultiPickupBopisOmsOrder()) + expect(await screen.findByTestId('account-order-details-page')).toBeInTheDocument() + expect(await screen.findByText(/FedEx/i)).toBeInTheDocument() + expect(await screen.findByText(/PENDING/i)).toBeInTheDocument() + const trackingLink = await screen.findByRole('link', {name: /MULTI-TRACK-789/i}) + expect(trackingLink).toHaveAttribute('href', 'https://tracking.fedex.com/MULTI-TRACK-789') + }) + test('should NOT display shipping address for multi-pickup BOPIS OMS order', async () => { setupOrderDetailsPage(createMultiPickupBopisOmsOrder()) expect(await screen.findByTestId('account-order-details-page')).toBeInTheDocument() @@ -1164,6 +1163,12 @@ describe('BOPIS Order with OMS - Pickup and Delivery Shipments', () => { provider: 'FedEx', trackingNumber: 'OMS-TRACK-456', trackingUrl: 'https://tracking.fedex.com/OMS-TRACK-456' + }, + { + status: 'NEW', + provider: 'UPS', + trackingNumber: 'OMS-TRACK-123', + trackingUrl: 'https://tracking.fedex.com/OMS-TRACK-123' } ] }, @@ -1180,8 +1185,6 @@ describe('BOPIS Order with OMS - Pickup and Delivery Shipments', () => { shippingMethod: { name: 'Ground' }, - shippingStatus: 'shipped', - trackingNumber: 'ECOM-TRACK-OLD', shippingAddress: { firstName: 'John', lastName: 'Doe' @@ -1230,31 +1233,27 @@ describe('BOPIS Order with OMS - Pickup and Delivery Shipments', () => { expect(await screen.findByText(/100 Market St/i)).toBeInTheDocument() }) - test('should display OMS provider for delivery shipment', async () => { - setupOrderDetailsPage(createOmsBopisWithDelivery()) - expect(await screen.findByTestId('account-order-details-page')).toBeInTheDocument() - expect(await screen.findByText(/FedEx/i)).toBeInTheDocument() - expect(screen.queryByText(/Ground/i)).not.toBeInTheDocument() - }) - - test('should display OMS tracking number as clickable link for delivery', async () => { + test('should display OMS shipment 1 and link', async () => { setupOrderDetailsPage(createOmsBopisWithDelivery()) expect(await screen.findByTestId('account-order-details-page')).toBeInTheDocument() + expect(await screen.findByText(/FEDEX/i)).toBeInTheDocument() + expect(await screen.findByText(/SHIPPED/i)).toBeInTheDocument() const trackingLink = await screen.findByRole('link', {name: /OMS-TRACK-456/i}) expect(trackingLink).toHaveAttribute('href', 'https://tracking.fedex.com/OMS-TRACK-456') }) - test('should display OMS shipment status for delivery', async () => { + test('should display OMS shipment 2 and link', async () => { setupOrderDetailsPage(createOmsBopisWithDelivery()) expect(await screen.findByTestId('account-order-details-page')).toBeInTheDocument() - expect(await screen.findByText(/SHIPPED/i)).toBeInTheDocument() + expect(await screen.findByText(/UPS/i)).toBeInTheDocument() + expect(await screen.findByText(/NEW/i)).toBeInTheDocument() + const trackingLink = await screen.findByRole('link', {name: /OMS-TRACK-123/i}) + expect(trackingLink).toHaveAttribute('href', 'https://tracking.fedex.com/OMS-TRACK-123') }) test('should NOT display shipping address for OMS BOPIS order with multiple shipments', async () => { setupOrderDetailsPage(createOmsBopisWithDelivery()) expect(await screen.findByTestId('account-order-details-page')).toBeInTheDocument() - // Shipping address should be hidden for OMS multi-shipment orders - // (order has 2 shipments: 1 pickup + 1 delivery, so order.shipments?.length > 1) expect(screen.queryByRole('heading', {name: /^shipping address$/i})).not.toBeInTheDocument() expect(screen.queryByText(/John Doe/i)).not.toBeInTheDocument() })